From 59b3811324a37cbab83983f05be293dae90a8854 Mon Sep 17 00:00:00 2001 From: Michael Buluma Date: Sat, 18 Nov 2023 10:18:39 +0300 Subject: [PATCH] Lots of updates. --- .ansible-lint | 1 - defaults/main.yml | 10 +++ handlers/main.yml | 2 - meta/argument_specs.yml | 73 +++++++++++++++ meta/exception.yml | 6 -- meta/main.yml | 2 + molecule/default/converge.yml | 33 ++++++- molecule/default/prepare.yml | 18 ++++ molecule/default/verify.yml | 8 +- tasks/assert.yml | 162 ++++++++++++++++++++++++++++++---- tasks/main.yml | 8 +- templates/haproxy.cfg.j2 | 43 +++++++-- 12 files changed, 327 insertions(+), 39 deletions(-) create mode 100644 meta/argument_specs.yml delete mode 100644 meta/exception.yml diff --git a/.ansible-lint b/.ansible-lint index fa67b68..937b336 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -3,7 +3,6 @@ # Ansible managed # exclude_paths: - - meta/preferences.yml - molecule/default/prepare.yml - molecule/default/converge.yml - molecule/default/verify.yml diff --git a/defaults/main.yml b/defaults/main.yml index 7bcbae0..b101efc 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -4,6 +4,7 @@ # Configure stats in HAProxy? haproxy_stats: yes haproxy_stats_port: 1936 +haproxy_stats_bind_addr: "0.0.0.0" # Default setttings for HAProxy. haproxy_retries: 3 @@ -14,3 +15,12 @@ haproxy_timeout_server: 1m haproxy_timeout_http_keep_alive: 10s haproxy_timeout_check: 10s haproxy_maxconn: 3000 + +# A list of frontends. See `molecule/ +haproxy_frontends: [] +haproxy_backend_default_balance: roundrobin +haproxy_backends: [] + +# For the listening lists: +haproxy_listen_default_balance: roundrobin +haproxy_listens: [] diff --git a/handlers/main.yml b/handlers/main.yml index e72e345..7535b09 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -8,5 +8,3 @@ ansible.builtin.service: name: haproxy state: restarted - when: - - not ansible_check_mode | bool diff --git a/meta/argument_specs.yml b/meta/argument_specs.yml new file mode 100644 index 0000000..5367aaf --- /dev/null +++ b/meta/argument_specs.yml @@ -0,0 +1,73 @@ +--- + +argument_specs: + main: + short_description: "Install and configure haproxy on your system." + description: > + Install and configure haproxy on your system. + author: Shadow Walker + options: + haproxy_stats: + type: "bool" + default: yes + description: "Configure stats in HAProxy?" + haproxy_stats_port: + type: "int" + default: 1936 + description: "The port for HAProxy stats." + haproxy_stats_bind_addr: + type: "str" + default: "0.0.0.0" + description: "The address to bind HAProxy stats to." + haproxy_retries: + type: "int" + default: 3 + description: "The number of retries." + haproxy_timeout_http_request: + type: "str" + default: "10s" + description: "The timeout for HTTP requests." + haproxy_timeout_connect: + type: "str" + default: "10s" + description: "The timeout for connecting." + haproxy_timeout_client: + type: "str" + default: "1m" + description: "The timeout for clients." + haproxy_timeout_server: + type: "str" + default: "1m" + description: "The timeout for servers." + haproxy_timeout_http_keep_alive: + type: "str" + default: "10s" + description: "The interval for HTTP keep-alive." + haproxy_timeout_check: + type: "str" + default: "10s" + description: "The timeout for checks." + haproxy_maxconn: + type: "int" + default: 3000 + description: "The maximum number of connections." + haproxy_frontends: + type: "list" + default: [] + description: "A list of frontends." + haproxy_backend_default_balance: + type: "str" + default: "roundrobin" + description: "The default balance for backends." + haproxy_backends: + type: "list" + default: [] + description: "A list of backends." + haproxy_listen_default_balance: + type: "str" + default: "roundrobin" + description: "The default balance for listens." + haproxy_listens: + type: "list" + default: [] + description: "A list of listens." diff --git a/meta/exception.yml b/meta/exception.yml deleted file mode 100644 index 6d6af94..0000000 --- a/meta/exception.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -exceptions: - - variation: amazonlinux:1 - reason: "/etc/init.d/haproxy: line 17: /etc/sysconfig/network: No such file or directory" - - variation: ubuntu:xenial - reason: "Setup script exited with error: command 'x86_64-linux-gnu-gcc' failed with exit status 1" diff --git a/meta/main.yml b/meta/main.yml index 9fc08e6..3e2fd22 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -11,6 +11,7 @@ galaxy_info: - name: EL versions: - "8" + - "9" - name: Debian versions: - all @@ -27,5 +28,6 @@ galaxy_info: galaxy_tags: - cloud - haproxy + - networking dependencies: [] diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml index 5a3e2eb..3ec3e34 100644 --- a/molecule/default/converge.yml +++ b/molecule/default/converge.yml @@ -27,6 +27,13 @@ haproxy_backends: - name: backend httpcheck: yes + # You can tell how the health check must be done. + # This requires haproxy version 2 + # http_check: + # send: + # method: GET + # uri: /health.html + # expect: status 200 balance: roundrobin # You can refer to hosts in an Ansible group. # The `ansible_default_ipv4` will be used as an address to connect to. @@ -35,8 +42,8 @@ options: - check - name: smtp - httpcheck: yes - balance: roundrobin + balance: leastconn + mode: tcp # You can also refer to a list of servers. servers: - name: first @@ -46,5 +53,27 @@ address: "127.0.0.2" port: 25 port: 25 + - name: vault + mode: tcp + httpcheck: GET /v1/sys/health HTTP/1.1 + servers: "{{ groups['all'] }}" + http_send_name_header: Host + port: 8200 options: - check + - check-ssl + - ssl verify none + + haproxy_listen_default_balance: roundrobin + haproxy_listens: + - name: listen + address: "*" + httpcheck: yes + listen_port: 8081 + balance: roundrobin + # You can refer to hosts in an Ansible group. + # The `ansible_default_ipv4` will be used as an address to connect to. + servers: "{{ groups['all'] }}" + port: 8080 + options: + - maxconn 100000 diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml index ef1686f..85f754f 100644 --- a/molecule/default/prepare.yml +++ b/molecule/default/prepare.yml @@ -18,3 +18,21 @@ # This role is applied to serve as a mock "backend" server. See `molecule/default/verify.yml`. - role: buluma.httpd httpd_port: 8080 + + vars: + _httpd_data_directory: + default: /var/www/html + Alpine: /var/www/localhost/htdocs + Suse: /srv/www/htdocs + + httpd_data_directory: "{{ _httpd_data_directory[ansible_os_family] | default(_httpd_data_directory['default'] ) }}" + post_tasks: + - name: Place health check + ansible.builtin.copy: + content: 'ok' + dest: "{{ httpd_data_directory }}/health.html" + + - name: Place sample page + ansible.builtin.copy: + content: 'Hello world!' + dest: "{{ httpd_data_directory }}/index.html" diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml index 619f668..f060790 100644 --- a/molecule/default/verify.yml +++ b/molecule/default/verify.yml @@ -9,8 +9,10 @@ ansible.builtin.package: name: socat state: present + notify: + - Uninstall testing requirements - - name: Check if ports responds + - name: Check if port responds ansible.builtin.wait_for: port: "{{ item }}" loop: @@ -23,18 +25,16 @@ validate_certs: no status_code: - 200 - - 403 loop: - "http://localhost/" - "https://localhost/" - name: Check if stats are available ansible.builtin.shell: - cmd: echo "show stat" | socat unix-connect:/var/lib/haproxy/stats stdio + cmd: "echo 'show stat' | socat unix-connect:/var/lib/haproxy/stats stdio" changed_when: no handlers: - - name: Uninstall testing requirements ansible.builtin.package: name: socat diff --git a/tasks/assert.yml b/tasks/assert.yml index 6be971c..9211cca 100644 --- a/tasks/assert.yml +++ b/tasks/assert.yml @@ -1,13 +1,13 @@ --- -- name: assert | Test if haproxy_stats is set correctly +- name: assert | Test haproxy_stats ansible.builtin.assert: that: - haproxy_stats is defined - haproxy_stats is boolean quiet: yes -- name: assert | Test if haproxy_stats_port is set correctly +- name: assert | Test haproxy_stats_port ansible.builtin.assert: that: - haproxy_stats_port is defined @@ -16,7 +16,15 @@ - haproxy_stats_port < 65536 quiet: yes -- name: assert | Test if haproxy_retries is set correctly +- name: assert | Test haproxy_stats_bind_addr + ansible.builtin.assert: + that: + - haproxy_stats_bind_addr is defined + - haproxy_stats_bind_addr is string + - haproxy_stats_bind_addr is not none + quiet: yes + +- name: assert | Test haproxy_retries ansible.builtin.assert: that: - haproxy_retries is defined @@ -24,11 +32,12 @@ - haproxy_retries >= 0 quiet: yes -- name: assert | Test if timeouts are set correctly +- name: assert | Test timeouts are set correctly ansible.builtin.assert: that: - item is defined - item is string + - item is not none quiet: yes loop: - "{{ haproxy_timeout_http_request }}" @@ -38,7 +47,7 @@ - "{{ haproxy_timeout_http_keep_alive }}" - "{{ haproxy_timeout_check }}" -- name: assert | Test if haproxy_maxconn is set correctly +- name: assert | Test haproxy_maxconn ansible.builtin.assert: that: - haproxy_maxconn is defined @@ -46,32 +55,35 @@ - haproxy_maxconn > 0 quiet: yes -- name: assert | Test if haproxy_frontends is set correctly +- name: assert | Test haproxy_frontends ansible.builtin.assert: that: - haproxy_frontends is defined - haproxy_frontends is iterable quiet: yes -- name: assert | Test if item in haproxy_frontends is set correctly +- name: assert | Test item in haproxy_frontends ansible.builtin.assert: that: - item.name is defined - item.name is string + - item.name is not none - item.address is defined - item.address is string + - item.address is not none - item.port is defined - item.port is number - item.port > 0 - item.port < 65536 - item.default_backend is defined - item.default_backend is string + - item.default_backend is not none quiet: yes loop: "{{ haproxy_frontends }}" loop_control: label: "{{ item.name }}" -- name: assert | Test if item in haproxy_frontends with ssl is set correctly +- name: assert | Test item in haproxy_frontends with ssl ansible.builtin.assert: that: - item.ssl is boolean @@ -84,7 +96,7 @@ when: - item.ssl is defined -- name: assert | Test if optional item haproxy_frontends is set correctly +- name: assert | Test optional item haproxy_frontends ansible.builtin.assert: that: - item.mode is string @@ -96,14 +108,14 @@ when: - item.mode is defined -- name: assert | Test if haproxy_backends is set correctly +- name: assert | Test haproxy_backends ansible.builtin.assert: that: - haproxy_backends is defined - haproxy_backends is iterable quiet: yes -- name: assert | Test if haproxy_backend_default_balance is set correctly +- name: assert | Test haproxy_backend_default_balance ansible.builtin.assert: that: - haproxy_backend_default_balance is defined @@ -111,11 +123,12 @@ - haproxy_backend_default_balance in [ "roundrobin", "static-rr", "leastconn", "first", "source", "uri", "url_param", "hdr", "rdp-cookie" ] quiet: yes -- name: assert | Test if item in haproxy_backends is set correctly +- name: assert | Test item in haproxy_backends ansible.builtin.assert: that: - item.name is defined - item.name is string + - item.name is not none - (item.balance is defined and item.balance is string and item.balance in [ "roundrobin", "static-rr", "leastconn", "first", "source", "uri", "url_param", "hdr", "rdp-cookie" ]) @@ -132,7 +145,7 @@ loop_control: label: "{{ item.name }}" -- name: assert | Test if optional item haproxy_backends is set correctly +- name: assert | Test optional item haproxy_backends ansible.builtin.assert: that: - item.mode is string @@ -144,10 +157,11 @@ when: - item.mode is defined -- name: assert | Test if httpcheck item in haproxy_backends is set correctly +- name: assert | Test httpcheck item in haproxy_backends ansible.builtin.assert: that: - item.httpcheck is boolean + or item.httpcheck is string quiet: yes loop: "{{ haproxy_backends }}" loop_control: @@ -155,10 +169,11 @@ when: - item.httpcheck is defined -- name: assert | Test if httpcheck_method item in haproxy_backends is set correctly +- name: assert | Test httpcheck_method item in haproxy_backends ansible.builtin.assert: that: - item.httpcheck_method is string + - item.httpcheck_method is not none - item.httpcheck is defined quiet: yes loop: "{{ haproxy_backends }}" @@ -166,3 +181,120 @@ label: "{{ item.name }}" when: - item.httpcheck_method is defined + +- name: assert | Test http_check item in haproxy_backends + ansible.builtin.assert: + that: + - item.http_check is mapping + quiet: yes + loop: "{{ haproxy_backends }}" + loop_control: + label: "{{ item.name }}" + when: + - item.http_check is defined + +- name: assert | Test http_check.send item in haproxy_backends + ansible.builtin.assert: + that: + - item.http_check.send is mapping + - item.http_check.send.method is defined + - item.http_check.send.method in [ "GET", "HEAD" ] + - item.http_check.send.uri is defined + - item.http_check.send.uri is not none + quiet: yes + loop: "{{ haproxy_backends }}" + loop_control: + label: "{{ item.name }}" + when: + - item.http_check.send is defined + +- name: assert | Test http_check.expect item in haproxy_backends + ansible.builtin.assert: + that: + - item.http_check.expect is string + - item.http_check.expect is not none + quiet: yes + loop: "{{ haproxy_backends }}" + loop_control: + label: "{{ item.name }}" + when: + - item.http_check.expect is defined + +- name: assert | Test haproxy_listens + ansible.builtin.assert: + that: + - haproxy_listens is defined + - haproxy_listens is iterable + quiet: yes + +- name: assert | Test item in haproxy_listens + ansible.builtin.assert: + that: + - item.name is defined + - item.name is string + - item.name is not none + - item.address is defined + - item.address is string + - item.address is not none + - (item.balance is defined + and item.balance is string + and item.balance in [ "roundrobin", "static-rr", "leastconn", "first", "source", "uri", "url_param", "hdr", "rdp-cookie" ]) + or item.balance is undefined + - item.listen_port is defined + - item.listen_port is number + - item.listen_port > 0 + - item.listen_port < 65536 + - item.port is defined + - item.port is number + - item.port > 0 + - item.port < 65536 + - item.servers is defined + - item.servers is iterable + quiet: yes + loop: "{{ haproxy_listens }}" + loop_control: + label: "{{ item.name }}" + +- name: assert | Test item in haproxy_listens with ssl + ansible.builtin.assert: + that: + - item.ssl is boolean + - item.crts is defined + - item.crts is iterable + quiet: yes + loop: "{{ haproxy_listens }}" + loop_control: + label: "{{ item.name }}" + when: + - item.ssl is defined + +- name: assert | Test optional item haproxy_listens + ansible.builtin.assert: + that: + - item.mode is string + - item.mode in [ "http", "tcp" ] + quiet: yes + loop: "{{ haproxy_listens }}" + loop_control: + label: "{{ item.name }}" + when: + - item.mode is defined + +- name: assert | Test haproxy_listen_default_balance + ansible.builtin.assert: + that: + - haproxy_listen_default_balance is defined + - haproxy_listen_default_balance is string + - haproxy_listen_default_balance in [ "roundrobin", "static-rr", "leastconn", "first", "source", "uri", "url_param", "hdr", "rdp-cookie" ] + quiet: yes + +- name: assert | Test httpcheck item in haproxy_listens + ansible.builtin.assert: + that: + - item.httpcheck is boolean + quiet: yes + loop: "{{ haproxy_listens }}" + loop_control: + label: "{{ item.name }}" + when: + - item.httpcheck is defined diff --git a/tasks/main.yml b/tasks/main.yml index 90d3707..d41006b 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -2,7 +2,8 @@ # tasks file for haproxy - name: Import assert.yml - ansible.builtin.import_tasks: assert.yml + ansible.builtin.import_tasks: + file: assert.yml run_once: yes delegate_to: localhost @@ -27,12 +28,11 @@ ansible.builtin.template: src: haproxy.cfg.j2 dest: /etc/haproxy/haproxy.cfg - validate: haproxy -c -f %s + # validate: haproxy -c -f %s mode: "0640" + backup: yes notify: - Restart haproxy - when: - - ansible_default_ipv4 is defined - name: Modify selinux settings when: diff --git a/templates/haproxy.cfg.j2 b/templates/haproxy.cfg.j2 index 82b9ae9..f06afcb 100644 --- a/templates/haproxy.cfg.j2 +++ b/templates/haproxy.cfg.j2 @@ -1,4 +1,5 @@ {{ ansible_managed | comment }} + global log 127.0.0.1 local2 chroot /var/lib/haproxy @@ -29,7 +30,7 @@ defaults {% if haproxy_stats == true %} listen stats - bind :{{ haproxy_stats_port }} + bind {{haproxy_stats_bind_addr }}:{{ haproxy_stats_port }} mode http stats enable stats uri /stats @@ -41,19 +42,51 @@ frontend {{ frontend.name }} default_backend {{ frontend.default_backend }} mode {{ frontend.mode | default('http') }} + option {{ frontend.option | default('httplog') }} {% endfor %} + {% for backend in haproxy_backends %} backend {{ backend.name }} -{% if backend.httpcheck %} - option httpchk {% if backend.httpcheck_method is defined %}{{ backend.httpcheck_method }}{% endif %}{% endif %} +{% if backend.httpcheck is defined and backend.httpcheck_method is defined and backend.httpcheck %} + option httpchk {% if backend.httpcheck_method is defined %}{{ backend.httpcheck_method }}{% endif %} +{% elif backend.httpcheck is defined and backend.httpcheck %} + option httpchk +{% endif %} +{% if backend.http_check is defined %} + http-check send meth {{ backend.http_check.send.method }} uri {{ backend.http_check.send.uri }} + http-check expect {{ backend.http_check.expect }} +{% endif %} balance {{ backend.balance | default(haproxy_backend_default_balance) }} mode {{ backend.mode | default('http') }} +{% if backend.http_send_name_header is defined %} + http-send-name-header {{ backend.http_send_name_header }} +{% endif %} {% for server in backend.servers %} {% if server.name is defined %} - server {{ server.name }} {{ server.address | default(hostvars[server.name]['ansible_default_ipv4']['address']) }}:{{ server.port | default(backend.port) }} {{ backend.options | join(' ') }} + server {{ server.name }} {{ server.address | default(hostvars[server.name]['ansible_default_ipv4']['address']) }}:{{ server.port | default(backend.port) }} {% if backend.options is defined %}{{ backend.options | join(' ') }}{% endif %} +{% elif ansible_default_ipv4 is defined %} + server {{ server }} {{ hostvars[server]['ansible_default_ipv4']['address'] }}:{{ backend.port }} {% if backend.options is defined %}{{ backend.options | join(' ') }}{% endif %} +{% endif %} + +{% endfor %} +{% endfor %} + +{% for listen in haproxy_listens %} +listen {{ listen.name }} + bind {{ listen.address }}:{{ listen.listen_port }}{% if listen.ssl is defined and listen.ssl == true %} ssl{% endif %}{% if listen.crts is defined and listen.crts | length >0 %}{% for crt in listen.crts %} crt {{ crt }}{% endfor %}{% endif %} + +{% if listen.httpcheck %} + option httpchk {% if listen.httpcheck_method is defined %}{{ listen.httpcheck_method }}{% endif %} +{% endif %} + + balance {{ listen.balance | default(haproxy_listen_default_balance) }} + mode {{ listen.mode | default('tcp') }} +{% for server in listen.servers %} +{% if server.name is defined %} + server {{ server.name }} {{ server.address | default(hostvars[server.name]['ansible_default_ipv4']['address']) }}:{{ server.port | default(listen.port) }} {{ listen.options | join(' ') }} {% else %} - server {{ server }} {{ hostvars[server]['ansible_default_ipv4']['address'] }}:{{ backend.port }} {{ backend.options | join(' ') }} + server {{ server }} {{ hostvars[server]['ansible_default_ipv4']['address'] }}:{{ listen.port }} {{ listen.options | join(' ') }} {% endif %} {% endfor %} {% endfor %}