diff --git a/.gitignore b/.gitignore index dd6d285..495ff29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ +*.egg-info/ *.py[co] *.swp +.cache/ +.coverage .kitchen +.tox/ __pycache__/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4b43363 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +--- +sudo: false +language: python +python: + - '2.7' + - '3.5' + - '3.6' +install: pip install tox-travis +script: tox diff --git a/README.md b/README.md index f3c8a74..cc11877 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ haproxy -======== +======= Installs and configures [HAProxy 1.5](http://www.haproxy.org/). @@ -13,6 +13,7 @@ The **last release** in the 1.x series is [1.2.0](https://github.com/devops-coop Features -------- +* Offers flexible, structured configuration syntax. * Supports Alpine, CentOS, Debian, and Ubuntu. * Installs HAProxy 1.5 from official repositories on Debian and Ubuntu. * Installs EPEL repository on CentOS. @@ -26,40 +27,193 @@ Role Variables * `haproxy_defaults` Default settings for frontends, backends, and listen proxies. +* `haproxy_mailers` + + A map of [`mailers` configurations](https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#3.6) sections. +* `haproxy_peers` + + A map of [`peer` configurations](https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#3.5). * `haproxy_resolvers` - A list of HAProxy resolvers. + A map of [`resolvers` configurations](https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#5.3.2). +* `haproxy_userlists` + + A map of [`userlist` configurations](https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#3.4). * `haproxy_backends` - A list of HAProxy backends. + A map of [`backend` configurations](https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#4). * `haproxy_frontends` - A list of HAProxy frontends. + A map of [`frontend` configurations](https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#4). * `haproxy_listen` - A list of listen proxies. + A map of [`listen` configurations](https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#4). + +Configuration +------------- + +HAProxy is a complex piece of software and supports hundreds of configuration options. + +Rather than support individual options, this role offers a flexible, structured configuration syntax for HAProxy concepts. + +For example, instead of declaring `errorfile` lines like this: + +```yaml +haproxy_defaults: + errorfile: + - code: 400 + file: /etc/haproxy/errors/400.http + - code: 403 + file: /etc/haproxy/errors/403.http +``` + +You would use the following natural syntax: + +```yaml +haproxy_defaults: + errorfile: + 400: /etc/haproxy/errors/400.http + 403: /etc/haproxy/errors/403.http +``` + +The descriptions below detail how the role handles different data types. + +### Maps + +The template expands maps (dictionaries, hashes) according to three simple rules: + +1. Prefix every value in the map with the key name. +2. Sort option names by relative weight. This ensures HAProxy processes `acl` rules before `http-request` rules, for example. +3. Within each weighted group, sort option names alphabetically. + +This ensures predictable output and eliminates runtime warnings. + +```yaml +--- +haproxy_defaults: + timeout: + server: 500ms + connect: 1000s + client: 5000s + retries: 5 + option: + - forwardfor + - dontlognull +``` + +``` +defaults + option forwardfor + option dontlognull + retries 5 + timeout client 5000s + timeout connect 1000s + timeout server 500ms +``` -See [`vars/main.yml`](vars/main.yml) for a complete list of configurable . +### Sequences + +The template will preserve sequence (list, array) order. This is important in some contexts, such as `http-request` rules. + +```yaml +--- +haproxy_frontends: + http-in: + acl: + - is_admin path_beg /admin + - is_api path_beg /api + default_backend: http +``` + +``` +frontend http-in + acl is_admin path_beg /admin + acl is_api path_beg /api + default_backend http +``` + +### Booleans + +True values (`true`, `yes`, `y`, etc.) will expand to simple flags: + +```yaml +--- +haproxy_backends: + http: + disabled: true + server: + www: 192.0.2.1:80 +``` + +``` +backend http + disabled + server www 192.0.2.1:80 +``` + +False values will omit the flag: + +```yaml +--- +haproxy_backends: + http: + disabled: false + server: + www: 192.0.2.1:80 +``` + +``` +backend http + server www 192.0.2.1:80 +``` + +### Strings, integers, floats, and other primitives + +Other primitives will be rendered as-is: + +```yaml +haproxy_listens: + tcp-in: + backlog: 10000 + maxconn: 1000 + mode: tcp +``` + +``` +listen tcp-in + backlog 10000 + maxconn 1000 + mode tcp +``` Example ------- ```yaml -- hosts: loadbalancers - roles: - - role: haproxy - haproxy_frontends: - - name: 'fe-mysupersite' - ip: '123.123.123.120' - port: '80' - maxconn: '1000' - default_backend: 'be-mysupersite' - haproxy_backends: - - name: 'be-mysupersite' - description: 'mysupersite is really cool' - servers: - - name: 'be-mysupersite-01' - ip: '192.168.1.100' +--- +haproxy_frontends: + http: + bind: + - :80 + - :443 ssl crt /etc/ssl/certs/star.example.org.pem + acl: + - is_api hdr(Host) api.example.org + use_backend: + - api if is_api + redirect: + - scheme https if { !ssl_fc } + default_backend: app + +haproxy_backends: + app: + server: + app-01: 192.51.100.10:80 check + app-02: 192.51.100.11:80 check + + api: + server: + api-01: 192.51.100.100:80 check + api-02: 192.51.100.101:80 check ``` License diff --git a/defaults/main.yml b/defaults/main.yml index cad51cd..696d17d 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -14,37 +14,29 @@ _haproxy_ssl_ciphers: 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305 haproxy_global: log: - - address: /dev/log - facility: local0 - - address: /dev/log - facility: local1 - level: notice + /dev/log: + - local0 + - local1 notice chroot: /var/lib/haproxy user: haproxy group: haproxy daemon: true - ssl_default_bind_options: '{{ _haproxy_ssl_options }}' - ssl_default_bind_ciphers: '{{ _haproxy_ssl_ciphers }}' - ssl_default_server_options: '{{ _haproxy_ssl_options }}' - ssl_default_server_ciphers: '{{ _haproxy_ssl_ciphers }}' - tune: - ssl: - default-dh-param: 2048 + ssl-default-bind-options: '{{ _haproxy_ssl_options }}' + ssl-default-bind-ciphers: '{{ _haproxy_ssl_ciphers }}' + ssl-default-server-options: '{{ _haproxy_ssl_options }}' + ssl-default-server-ciphers: '{{ _haproxy_ssl_ciphers }}' + tune.ssl.default-dh-param: 2048 haproxy_defaults: mode: http log: - - address: /dev/log - facility: local1 - level: notice + /dev/log: + - local1 notice timeout: - - param: 'connect' - value: '5000ms' - - param: 'client' - value: '50000ms' - - param: 'server' - value: '50000ms' - options: + connect: 5000ms + client: 50000ms + server: 50000ms + option: - httpclose - forwardfor except 127.0.0.0/8 - redispatch @@ -52,23 +44,10 @@ haproxy_defaults: - httplog - dontlognull errorfile: - - code: 400 - file: /etc/haproxy/errors/400.http - - code: 403 - file: /etc/haproxy/errors/403.http - - code: 408 - file: /etc/haproxy/errors/408.http - - code: 500 - file: /etc/haproxy/errors/500.http - - code: 502 - file: /etc/haproxy/errors/502.http - - code: 503 - file: /etc/haproxy/errors/503.http - - code: 504 - file: /etc/haproxy/errors/504.http - -haproxy_resolvers: [] -haproxy_backends: [] -haproxy_frontends: [] -haproxy_listen: [] -haproxy_userlists: [] + 400: /etc/haproxy/errors/400.http + 403: /etc/haproxy/errors/403.http + 408: /etc/haproxy/errors/408.http + 500: /etc/haproxy/errors/500.http + 502: /etc/haproxy/errors/502.http + 503: /etc/haproxy/errors/503.http + 504: /etc/haproxy/errors/504.http diff --git a/filter_plugins/__init__.py b/filter_plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/filter_plugins/haproxy.py b/filter_plugins/haproxy.py new file mode 100644 index 0000000..f8aae85 --- /dev/null +++ b/filter_plugins/haproxy.py @@ -0,0 +1,91 @@ +from __future__ import unicode_literals + +import collections +import sys + + +if sys.version_info.major < 3: + string_types = basestring +else: + string_types = str + + +# Certain options need to precede others in the configuration file. Options with +# larger weights sink to the bottom. +# Weights taken from Puppetlabs HAProxy module: +# +OPTION_WEIGHTS = { + 'acl': -1, + 'tcp-request': 2, + 'block': 3, + 'http-request': 4, + 'reqallow': 5, + 'reqdel': 5, + 'reqdeny': 5, + 'reqidel': 5, + 'reqideny': 5, + 'reqipass': 5, + 'reqirep': 5, + 'reqitarpit': 5, + 'reqpass': 5, + 'reqrep': 5, + 'reqtarpit': 5, + 'reqadd': 6, + 'redirect': 7, + 'use_backend': 8, + 'use-server': 9, + 'server': 100, +} + + +def sort_by_weight(mapping, weights=None): + """ + Sort mapping keys first by weight, then alphabetically. + """ + if weights: + key = lambda kv: (weights.get(kv[0], 0), kv[0]) + else: + key = None + return collections.OrderedDict(sorted(mapping.items(), key=key)) + + +def expand(options): + """ + Expand a nested configuration dictionary. + """ + for key, value in sort_by_weight(options, weights=OPTION_WEIGHTS).items(): + if not isinstance(value, collections.Container) or isinstance(value, string_types): + yield (key, value) + continue + elif isinstance(value, collections.Sequence): + for item in value: + yield (key, item) + continue + elif isinstance(value, collections.Mapping): + yield (key, list(expand((value)))) + continue + yield (key, value) + + +def to_haproxy(options): + """ + Yield HAProxy configuration lines from a nested configuration dictionary. + """ + options = expand(options) + for key, value in options: + if value is True: + yield str(key) + elif value is False: + continue + elif isinstance(value, collections.Sequence) and not isinstance(value, string_types): + for sub_key, sub_value in value: + yield '{} {} {}'.format(key, sub_key, sub_value) + else: + yield '{} {}'.format(key, value) + + +class FilterModule(object): + def filters(self): + return { + 'to_haproxy': to_haproxy, + } diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6e27808 --- /dev/null +++ b/setup.py @@ -0,0 +1,10 @@ +from setuptools import ( + find_packages, + setup, +) + + +setup( + name='ansible-haproxy', + packages=find_packages(), +) diff --git a/tasks/configure.yml b/tasks/configure.yml index 4d9042d..3b2aadd 100644 --- a/tasks/configure.yml +++ b/tasks/configure.yml @@ -1,199 +1,7 @@ --- - -## ASSEMBLE CONFIG - RESOLVERS - -- name: 'Create directory for the resolvers' - file: - path: "{{ haproxy_config_dir }}/resolvers.d" - state: directory - -- name: "List files for the resolvers" - find: - paths: "{{ haproxy_config_dir }}/resolvers.d" - patterns: "*.cfg" - register: directory_contents - changed_when: false - -- name: "Remove unmanaged files for the resolvers" - file: - path: "{{ item.path }}" - state: absent - when: (item.path | basename) not in (haproxy_resolvers | json_query('[*].name') | map('regex_replace', '(.*)', '\\1.cfg') | list) - with_items: "{{ directory_contents.files }}" - -- name: 'Build up the resolvers' +- name: Configure HAProxy template: - src: "resolvers.cfg" - dest: "{{ haproxy_config_dir }}/resolvers.d/{{ item.name }}.cfg" - with_items: "{{ haproxy_resolvers }}" - when: haproxy_resolvers is defined - -## ASSEMBLE CONFIG - FRONTEND - -- name: 'Create directory for the frontend' - file: - path: "{{ haproxy_config_dir }}/frontends.d" - state: directory - -- name: "List files for the frontends" - find: - paths: "{{ haproxy_config_dir }}/frontends.d" - patterns: "*.cfg" - register: directory_contents - changed_when: false - -- name: "Remove unmanaged files for the frontends" - file: - path: "{{ item.path }}" - state: absent - when: (item.path | basename) not in (haproxy_frontends | json_query('[*].name') | map('regex_replace', '(.*)', '\\1.cfg') | list) - with_items: "{{ directory_contents.files }}" - -- name: 'Build up the frontends' - template: - src: "frontend.cfg" - dest: "{{ haproxy_config_dir }}/frontends.d/{{ item.name }}.cfg" - with_items: "{{ haproxy_frontends }}" - when: haproxy_frontends is defined - -## ASSEMBLE CONFIG - BACKEND - -- name: 'Create directory for the backends' - file: - path: "{{ haproxy_config_dir }}/backends.d" - state: directory - -- name: "List files for the backends" - find: - paths: "{{ haproxy_config_dir }}/backends.d" - patterns: "*.cfg" - register: directory_contents - changed_when: false - -- name: "Remove unmanaged files for the backends" - file: - path: "{{ item.path }}" - state: absent - when: (item.path | basename) not in (haproxy_backends | json_query('[*].name') | map('regex_replace', '(.*)', '\\1.cfg') | list) - with_items: "{{ directory_contents.files }}" - -- name: 'Build up the backends' - template: - src: "backend.cfg" - dest: "{{ haproxy_config_dir }}/backends.d/{{ item.name }}.cfg" - with_items: "{{ haproxy_backends }}" - when: haproxy_backends is defined - -## ASSEMBLE CONFIG - LISTEN - -- name: 'Create directory for the listen sections' - file: - path: "{{ haproxy_config_dir }}/listen.d" - state: directory - -- name: "List files the listen sections" - find: - paths: "{{ haproxy_config_dir }}/listen.d" - patterns: "*.cfg" - register: directory_contents - changed_when: false - -- name: "Remove unmanaged files the listen sections" - file: - path: "{{ item.path }}" - state: absent - when: (item.path | basename) not in (haproxy_listen | json_query('[*].name') | map('regex_replace', '(.*)', '\\1.cfg') | list) - with_items: "{{ directory_contents.files }}" - -- name: 'Build up the listen sections' - template: - src: "listen.cfg" - dest: "{{ haproxy_config_dir }}/listen.d/{{ item.name }}.cfg" - with_items: "{{ haproxy_listen }}" - when: haproxy_listen is defined - -## ASSEMBLE CONFIG - USERLIST - -- name: 'Create directory for the userlists' - file: path={{ haproxy_config_dir }}/userlists.d state=directory - -- name: "List files for the userlists" - find: - paths: "{{ haproxy_config_dir }}/userlists.d" - patterns: "*.cfg" - register: directory_contents - changed_when: false - -- name: "Remove unmanaged files for the userlists" - file: - path: "{{ item.path }}" - state: absent - when: (item.path | basename) not in (haproxy_userlists | json_query('[*].name') | map('regex_replace', '(.*)', '\\1.cfg') | list) - with_items: "{{ directory_contents.files }}" - -- name: 'Build up the userlist sections' - template: - src: userlist.cfg - dest: "{{ haproxy_config_dir }}/userlists.d/{{ item.name }}.cfg" - with_items: "{{ haproxy_userlists }}" - when: haproxy_userlists is defined - -## ASSEMBLE CONFIG - GLOBAL & DEFAULT - -- name: 'Delete the compiled folder' - file: - path: "{{ haproxy_config_dir }}/compiled" - state: absent - -- name: 'Create the compiled folder' - file: - path: "{{ haproxy_config_dir }}/compiled" - state: directory - -- name: 'Build up the global config' - template: - src: "global.cfg" - dest: "{{ haproxy_config_dir }}/compiled/01-global.cfg" - when: haproxy_global is defined - tags: 'test' - -- name: 'Build up the default config' - template: - src: "defaults.cfg" - dest: "{{ haproxy_config_dir }}/compiled/02-defaults.cfg" - when: haproxy_defaults is defined - -## ASSEMBLE FINAL CONFIG - -- name: 'Assemble the resolvers sections configuration file' - assemble: - src: "{{ haproxy_config_dir }}/resolvers.d" - dest: "{{ haproxy_config_dir }}/compiled/03-resolvers.cfg" - -- name: 'Assemble the backends configuration file' - assemble: - src: "{{ haproxy_config_dir }}/backends.d" - dest: "{{ haproxy_config_dir }}/compiled/04-backends.cfg" - -- name: 'Assemble the frontends configuration file' - assemble: - src: "{{ haproxy_config_dir }}/frontends.d" - dest: "{{ haproxy_config_dir }}/compiled/05-frontends.cfg" - -- name: 'Assemble the listen sections configuration file' - assemble: - src: "{{ haproxy_config_dir }}/listen.d" - dest: "{{ haproxy_config_dir }}/compiled/06-listen.cfg" - -- name: 'Assemble the userlists sections configuration file' - assemble: - src: "{{ haproxy_config_dir }}/userlists.d" - dest: "{{ haproxy_config_dir }}/compiled/07-userlists.cfg" - -- name: 'Assemble the final configuration file' - assemble: - src: "{{ haproxy_config_dir }}/compiled" + src: haproxy.cfg.j2 dest: "{{ haproxy_config_file }}" - backup: yes - validate: "haproxy -c -f %s" + validate: haproxy -c -f %s notify: restart haproxy diff --git a/templates/_macros.j2 b/templates/_macros.j2 index 0d99675..88c2aff 100644 --- a/templates/_macros.j2 +++ b/templates/_macros.j2 @@ -1,13 +1,17 @@ -{%- macro http_response(responses = []) -%} - {%- for response in responses -%} - http-response {{ response.action }}{% if response.param is defined %} {{ response.param }}{% endif %}{% if response.condition is defined %} {{ response.condition }}{% endif %} +{% macro section(section, instances, single=False) %} +{% if instances is defined %} +{% if single %} +{{ section }} +{{ instances|to_haproxy|join('\n')|indent(indentfirst=True) }} - {% endfor -%} -{%- endmacro -%} -{%- macro http_request(requests = []) -%} - {%- for request in requests -%} - http-request {{ request.action }}{% if request.param is defined %} {{ request.param }}{% endif %}{% if request.condition is defined %} {{ request.condition }}{% endif %} +{% else %} +{% for name, options in (instances|dictsort(case_sensitive=True)).items() %} +{{ section }} {{ name }} +{{ options|to_haproxy|join('\n')|indent(indentfirst=True) }} - {% endfor -%} -{%- endmacro -%} +{% endfor %} + +{% endif %} +{% endif %} +{% endmacro %} diff --git a/templates/backend.cfg b/templates/backend.cfg deleted file mode 100644 index 0041608..0000000 --- a/templates/backend.cfg +++ /dev/null @@ -1,134 +0,0 @@ -{%- import '_macros.j2' as macros with context -%} - -#{{ ansible_managed }} - backend {{ item.name }} - {% if item.disabled is defined and item.disabled == true -%} - disabled - {% endif -%} - - {% if item.description is defined -%} - description {{ item.description }} - {% endif -%} - - {%- if item.servers is defined -%} - {%- for server in item.servers -%} - server {{ server.name }} {{ server.ip }}{% if server.port is defined %}:{{server.port }}{% endif %} {% if server.maxconn is defined %}maxconn {{server.maxconn }} {% endif %}{% if server.params is defined %}{% for param in server.params %}{{ param }} {% endfor %}{% endif %} - - {% endfor -%} - {% endif -%} - - {% if item.balance is defined -%} - balance {{ item.balance }} - {% endif -%} - - {% if item.cookie is defined -%} - cookie {{ item.cookie }} - {% endif -%} - - {% if item.mode is defined -%} - mode {{ item.mode }} - {% endif -%} - - {% if item.log is defined -%} - log {{ item.log }} - {% endif -%} - - {% if item.retries is defined -%} - retries {{ item.retries }} - {% endif -%} - - {% if item.acl is defined -%} - {% for acl in item.acl -%} - acl {{ acl.name }} {{ acl.condition }} - {% endfor -%} - {% endif -%} - - {%- if item.redirects is defined -%} - {%- for redirect in item.redirects -%} - redirect {{ redirect }} - {% endfor -%} - {% endif -%} - - {% if item.contimeout is defined -%} - contimeout {{ item.contimeout }} - {% endif -%} - - {% if item.http_send_name_header is defined -%} - http-send-name-header {{ item.http_send_name_header }} - {% endif -%} - - {%- if item.http_check_expect is defined -%} - {%- for check_expect in item.http_check_expect -%} - http-check expect {{ check_expect }} - {% endfor -%} - {% endif -%} - - {%- if item.http_request is defined -%} - {{ macros.http_request(item.http_request) }} - {%- endif -%} - - {%- if item.http_response is defined -%} - {{ macros.http_response(item.http_response) }} - {%- endif -%} - - {%- if item.options is defined -%} - {%- for option in item.options -%} - option {{ option }} - {% endfor -%} - {% endif -%} - - {%- if item.timeout is defined -%} - {%- for entry in item.timeout -%} - timeout {{ entry.param }} {{ entry.value }} - {% endfor -%} - {% endif -%} - - {%- if item.reqadd is defined -%} - {%- for reqadd in item.reqadd -%} - reqadd {{ reqadd }} - {% endfor -%} - {% endif -%} - - {%- if item.rspadd is defined -%} - {%- for rspadd in item.rspadd -%} - rspadd {{ rspadd }} - {% endfor -%} - {% endif -%} - - {%- if item.reqrep is defined -%} - {%- for reqrep in item.reqrep -%} - reqrep {{ reqrep }} - {% endfor -%} - {% endif -%} - - {%- if item.reqirep is defined -%} - {%- for reqirep in item.reqirep -%} - reqirep {{ reqirep }} - {% endfor -%} - {% endif -%} - - {%- if item.rsprep is defined -%} - {%- for rsprep in item.rsprep -%} - rsprep {{ rsprep }} - {% endfor -%} - {% endif -%} - - {%- if item.rspirep is defined -%} - {%- for rspirep in item.rspirep -%} - rspirep {{ rspirep }} - {% endfor -%} - {% endif -%} - - {% if item.appsession is defined -%} - appsession {{ item.appsession }} - {% endif -%} - - {%- if item.errorfile is defined -%} - {%- for errorfile in item.errorfile -%} - errorfile {{ errorfile.code }} {{ errorfile.file }} - {% endfor -%} - {% endif -%} - - {% if item.force_persist is defined -%} - force-persist {{ item.force_persist }} - {% endif -%} diff --git a/templates/defaults.cfg b/templates/defaults.cfg deleted file mode 100644 index 132c82f..0000000 --- a/templates/defaults.cfg +++ /dev/null @@ -1,88 +0,0 @@ -#{{ ansible_managed }} -defaults -{% if haproxy_defaults.mode is defined %} - mode {{ haproxy_defaults.mode }} -{% endif -%} -{% if haproxy_defaults.log is defined %} -{% for log in haproxy_defaults.log %} - log {{ log.address }}{% if log.facility is defined %} {{ log.facility }} {% endif %}{% if log.level is defined %} {{log.level }} {% endif %}{% if log.minlevel is defined %} {{ log.minlevel }}{% endif %} - -{% if log.format is defined %} - log-format {{ log.format }} -{% endif %} - -{% endfor %} -{% endif %} -{% if haproxy_defaults.retries is defined %} - retries {{ haproxy_defaults.retries }} -{% endif -%} -{% if haproxy_defaults.timeout is defined -%} -{% for condition in haproxy_defaults.timeout %} - timeout {{ condition.param }} {{ condition.value }} -{% endfor %} -{% endif -%} -{% if haproxy_defaults.maxconn is defined %} - maxconn {{ haproxy_defaults.maxconn }} -{% endif -%} -{% if haproxy_defaults.stats is defined %} -{% if haproxy_defaults.stats.enabled is defined and haproxy_defaults.stats.enabled == True %} - stats enable -{% endif -%} -{% if haproxy_defaults.stats.hide_version is defined and haproxy_defaults.stats.hide_version == true %} - stats hide-version -{% endif -%} -{% if haproxy_defaults.stats.uri is defined %} - stats uri {{ haproxy_defaults.stats.uri }} -{% endif -%} -{% if haproxy_defaults.stats.realm is defined %} - stats realm {{ haproxy_defaults.stats.realm }} -{% endif -%} -{% if haproxy_defaults.stats.auth is defined %} - stats auth {{ haproxy_defaults.stats.auth }} -{% endif -%} -{% if haproxy_defaults.stats.refresh is defined %} - stats refresh {{ haproxy_defaults.stats.refresh }} -{% endif -%} -{% endif %} -{% if haproxy_defaults.options is defined %} -{% for option in haproxy_defaults.options %} - option {{ option }} -{% endfor -%} -{% endif -%} -{% if ansible_distribution != 'CentOS' and ansible_distribution != 'Alpine' %} -{% if haproxy_defaults.errorfile is defined %} -{% for item in haproxy_defaults.errorfile %} - errorfile {{ item.code }} {{ item.file }} -{% endfor %} -{% endif -%} -{% endif -%} -{% if haproxy_defaults.balance is defined %} - balance {{ haproxy_defaults.balance }} -{% endif -%} -{% if haproxy_defaults.cookie is defined -%} - cookie {{ haproxy_defaults.cookie }} -{% endif -%} -{% if haproxy_defaults.compression is defined %} -{% if haproxy_defaults.compression.algo is defined %} - compression algo {{ haproxy_defaults.compression.algo }} -{% endif -%} -{% if haproxy_defaults.compression.type is defined %} - compression type {{ haproxy_defaults.compression.type }} -{% endif -%} -{% if haproxy_defaults.compression.offload is defined and haproxy_defaults.compression.offload == true %} - compression offload -{% endif -%} -{% endif -%} - -{% if haproxy_defaults.http_check is defined %} -{% if haproxy_defaults.http_check.disable_on_404 is defined and haproxy_defaults.http_check.disable_on_404 == true %} - http-check disable-on-404 -{% endif -%} -{% if haproxy_defaults.http_check.expect is defined %} - http-check expect {{ haproxy_defaults.http_check.expect }} -{% endif -%} -{% if haproxy_defaults.http_check.send_state is defined and haproxy_defaults.http_check.send_state == true %} - http-check send-state -{% endif -%} -{% endif -%} - diff --git a/templates/frontend.cfg b/templates/frontend.cfg deleted file mode 100644 index d4dd55a..0000000 --- a/templates/frontend.cfg +++ /dev/null @@ -1,134 +0,0 @@ -{%- import '_macros.j2' as macros with context -%} - -#{{ ansible_managed }} -frontend {{ item.name }} {%if item.ip is defined %}{{ item.ip }}{% endif %}{%if item.port is defined %}:{{ item.port }}{% endif %} - - {% if item.bind is defined -%} - {%- for bind in item.bind -%} - bind {{ bind }}{% if item.ssl is defined %}{% if item.ssl.cert is defined %} ssl crt {{ item.ssl.cert }}{% if item.ssl.ciphers is defined %} ciphers {{ item.ssl.ciphers }}{% endif %}{% endif %}{% endif %} - - {% endfor -%} - {% endif -%} - - {% if item.mode is defined -%} - mode {{ item.mode }} - {% endif -%} - - {% if item.maxconn is defined -%} - maxconn {{ item.maxconn }} - {% endif -%} - - {% if item.timeout is defined -%} - {% for entry in item.timeout %} - timeout {{ entry.param }} {{ entry.value }} - {% endfor %} - {% endif -%} - - {%- if item.acl is defined -%} - {%- for acl in item.acl -%} - acl {{ acl.name }} {{ acl.condition }} - {% endfor -%} - {% endif -%} - - {%- if item.monitor is defined -%} - {% if item.monitor.uri is defined -%} - monitor-uri {{ item.monitor.uri }} - {% endif %} - {%- if item.monitor.fail is defined -%} - {%- for condition in item.monitor.fail -%} - monitor fail {{ condition }} - {% endfor %} - {% endif %} - {% endif -%} - - {%- if item.capture is defined -%} - {%- for capture in item.capture -%} - capture {{ capture }} - {% endfor %} - {% endif -%} - - {%- if item.reqadd is defined -%} - {%- for reqadd in item.reqadd -%} - reqadd {{ reqadd }} - {% endfor -%} - {% endif -%} - - {%- if item.rspadd is defined -%} - {%- for rspadd in item.rspadd -%} - rspadd {{ rspadd }} - {% endfor -%} - {% endif -%} - - {%- if item.reqrep is defined -%} - {%- for reqrep in item.reqrep -%} - reqrep {{ reqrep }} - {% endfor -%} - {% endif -%} - - {%- if item.reqirep is defined -%} - {%- for reqirep in item.reqirep -%} - reqirep {{ reqirep }} - {% endfor -%} - {% endif -%} - - {%- if item.rsprep is defined -%} - {%- for rsprep in item.rsprep -%} - rsprep {{ rsprep }} - {% endfor -%} - {% endif -%} - - {%- if item.rspirep is defined -%} - {%- for rspirep in item.rspirep -%} - rspirep {{ rspirep }} - {% endfor -%} - {% endif -%} - - {%- if item.rate_limit_sessions is defined -%} - rate-limit sessions {{ item.rate_limit_sessions }} - {% endif %} - - {%- if item.block is defined -%} - {%- for block in item.block -%} - block {{ block }} - {% endfor -%} - {% endif -%} - - {% if item.options is defined %} - {% for option in item.options %} - option {{ option }} - {% endfor %} - {% endif -%} - - {%- if item.redirects is defined -%} - {%- for redirect in item.redirects -%} - redirect {{ redirect }} - {% endfor -%} - {% endif -%} - - {%- if item.tcp_request is defined -%} - {%- for request in item.tcp_request -%} - tcp-request {{ request.param }} {{ request.value }}{% if request.condition is defined %} {{ request.condition }}{% endif %} - {% endfor -%} - {% endif -%} - - {%- if item.http_request is defined -%} - {{ macros.http_request(item.http_request) }} - {%- endif -%} - - {%- if item.http_response is defined -%} - {{ macros.http_response(item.http_response) }} - {%- endif -%} - - {%- if item.default_backend is defined -%} - default_backend {{ item.default_backend }} - {% endif -%} - - {%- if item.use_backend is defined -%} - {%- for backend in item.use_backend -%} - use_backend {{ backend.name }} {{ backend.condition }} - {% endfor -%} - {% endif -%} - - {% if item.force_persist is defined -%} - force-persist {{ item.force_persist }} - {% endif -%} diff --git a/templates/global.cfg b/templates/global.cfg deleted file mode 100644 index 2034f8c..0000000 --- a/templates/global.cfg +++ /dev/null @@ -1,64 +0,0 @@ -#{{ ansible_managed }} -global -{% if haproxy_global.chroot is defined and haproxy_global.chroot != false %} - chroot {{ haproxy_global.chroot }} -{% endif -%} -{% if haproxy_global.pidfile is defined %} - pidfile {{ haproxy_global.pidfile }} -{% endif -%} -{% if haproxy_global.maxconn is defined %} - maxconn {{ haproxy_global.maxconn }} -{% endif -%} -{% if haproxy_global.uid is defined %} - uid {{ haproxy_global.uid }} -{% elif haproxy_global.user is defined %} - user {{ haproxy_global.user }} -{% endif -%} -{% if haproxy_global.gid is defined %} - gid {{ haproxy_global.gid }} -{% elif haproxy_global.group is defined %} - group {{ haproxy_global.group }} -{% endif -%} -{% if haproxy_global.daemon is defined and haproxy_global.daemon == true %} - daemon -{% endif -%} -{% if haproxy_global.nbproc is defined %} - nbproc {{ haproxy_global.nbproc }} -{% endif -%} -{% if haproxy_global.spread_checks is defined %} - spread-checks {{ haproxy_global.spread_checks }} -{% endif -%} -{% if haproxy_global.stats is defined %} -{% if haproxy_global.stats.socket is defined %} - stats socket {{ haproxy_global.stats.socket }} -{% endif -%} -{% if haproxy_global.stats.timeout is defined %} - stats timeout {{ haproxy_global.stats.timeout }} -{% endif -%} -{% endif %} -{% if haproxy_global.log is defined %} -{% for log in haproxy_global.log %} - log {{ log.address }} {{ log.facility }}{% if log.level is defined %} {{log.level }}{% endif %}{% if log.minlevel is defined %} {{ log.minlevel }}{% endif %} - -{% if log.format is defined %} - log-format {{ log.format }} -{% endif %} -{% endfor %} -{% endif %} -{% if haproxy_global.ssl_default_bind_options is defined %} - ssl-default-bind-options {{ haproxy_global.ssl_default_bind_options }} -{% endif -%} -{% if haproxy_global.ssl_default_bind_ciphers is defined %} - ssl-default-bind-ciphers {{ haproxy_global.ssl_default_bind_ciphers }} -{% endif -%} -{% if haproxy_global.ssl_default_server_options is defined %} - ssl-default-server-options {{ haproxy_global.ssl_default_server_options }} -{% endif -%} -{% if haproxy_global.ssl_default_server_ciphers is defined %} - ssl-default-server-ciphers {{ haproxy_global.ssl_default_server_ciphers }} -{% endif -%} -{% if haproxy_global.tune is defined %} - {% for param, value in (haproxy_global.tune | flatten).items() -%} - tune.{{ param }} {{ value }} - {% endfor -%} -{% endif %} diff --git a/templates/haproxy.cfg.j2 b/templates/haproxy.cfg.j2 new file mode 100644 index 0000000..9b58126 --- /dev/null +++ b/templates/haproxy.cfg.j2 @@ -0,0 +1,13 @@ +{%- import '_macros.j2' as macros with context -%} +# {{ ansible_managed }} + + +{{ macros.section('global', haproxy_global, single=True) -}} +{{- macros.section('defaults', haproxy_defaults, single=True) -}} +{{- macros.section('peers', haproxy_peers) -}} +{{- macros.section('resolvers', haproxy_resolvers) -}} +{{- macros.section('mailers', haproxy_mailers) -}} +{{- macros.section('userlist', haproxy_userlists) -}} +{{- macros.section('frontend', haproxy_frontends) -}} +{{- macros.section('backend', haproxy_backends) -}} +{{- macros.section('listen', haproxy_listens) -}} diff --git a/templates/listen.cfg b/templates/listen.cfg deleted file mode 100644 index ab1c5ad..0000000 --- a/templates/listen.cfg +++ /dev/null @@ -1,115 +0,0 @@ -#{{ ansible_managed }} -listen {{ item.name }} -{% if item.bind is defined %} -{% for binding in item.bind %} - bind {{ binding }}{% if item.ssl is defined %}{% if item.ssl.cert is defined %} ssl crt {{ item.ssl.cert }}{% if item.ssl.ciphers is defined %} ciphers {{ item.ssl.ciphers }}{% endif %}{% endif %}{% endif %} - -{% endfor %} -{% endif -%} -{% if item.disabled is defined and item.disabled | bool %} - disabled -{% endif -%} -{% if item.description is defined %} - description {{ item.description }} -{% endif -%} -{% if item.servers is defined %} -{% for server in item.servers %} - server {{ server.name }} {{ server.ip }}{% if server.port is defined %}:{{server.port }}{% endif %} {% if server.maxconn is defined %}maxconn {{server.maxconn }} {% endif %}{% if server.params is defined %}{% for param in server.params %}{{ param }} {% endfor %}{% endif %} - -{% endfor %} -{% endif %} -{% if item.maxconn is defined -%} - maxconn {{ item.maxconn }} -{% endif -%} -{% if item.balance is defined %} - balance {{ item.balance }} -{% endif -%} -{% if item.cookie is defined -%} - cookie {{ item.cookie }} -{% endif -%} -{% if item.mode is defined %} - mode {{ item.mode }} -{% endif -%} -{% if item.log is defined %} - log {{ item.log }} -{% endif -%} -{% if item.retries is defined %} - retries {{ item.retries }} -{% endif -%} -{% if item.contimeout is defined %} - contimeout {{ item.contimeout }} -{% endif -%} -{% if item.http_send_name_header is defined %} - http-send-name-header {{ item.http_send_name_header }} -{% endif -%} -{% if item.http_check_expect is defined %} -{% for check_expect in item.http_check_expect %} - http-check expect {{ check_expect }} -{% endfor %} -{% endif -%} -{% if item.options is defined %} -{% for option in item.options %} - option {{ option }} -{% endfor %} -{% endif -%} -{% if item.timeout is defined -%} -{% for entry in item.timeout %} - timeout {{ entry.param }} {{ entry.value }} -{% endfor %} -{% endif -%} -{%- if item.reqadd is defined -%} -{%- for reqadd in item.reqadd -%} - reqadd {{ reqadd }} -{% endfor -%} -{% endif -%} -{%- if item.rspadd is defined -%} -{%- for rspadd in item.rspadd -%} - rspadd {{ rspadd }} -{% endfor -%} -{% endif -%} -{%- if item.reqrep is defined -%} -{%- for reqrep in item.reqrep -%} - reqrep {{ reqrep }} -{% endfor -%} -{% endif -%} -{%- if item.reqirep is defined -%} -{%- for reqirep in item.reqirep -%} - reqirep {{ reqirep }} -{% endfor -%} -{% endif -%} -{%- if item.rsprep is defined -%} -{%- for rsprep in item.rsprep -%} - rsprep {{ rsprep }} -{% endfor -%} -{% endif -%} -{%- if item.rspirep is defined -%} -{%- for rspirep in item.rspirep -%} - rspirep {{ rspirep }} -{% endfor -%} -{% endif -%} -{% if item.appsession is defined %} - appsession {{ item.appsession }} -{% endif -%} -{% if item.stats is defined %} -{% if item.stats.enabled is defined and item.stats.enabled | bool %} - stats enable -{% endif -%} -{% if item.stats.hide_version is defined and item.stats.hide_version | bool %} - stats hide-version -{% endif -%} -{% if item.stats.uri is defined %} - stats uri {{ item.stats.uri }} -{% endif -%} -{% if item.stats.realm is defined %} - stats realm {{ item.stats.realm }} -{% endif -%} -{% if item.stats.auth is defined %} - stats auth {{ item.stats.auth }} -{% endif -%} -{% if item.stats.refresh is defined %} - stats refresh {{ item.stats.refresh }} -{% endif -%} -{% endif %} -{% if item.force_persist is defined -%} - force-persist {{ item.force_persist }} -{% endif -%} diff --git a/templates/resolvers.cfg b/templates/resolvers.cfg deleted file mode 100644 index 1c7ef55..0000000 --- a/templates/resolvers.cfg +++ /dev/null @@ -1,18 +0,0 @@ -#{{ ansible_managed }} -resolvers {{ item.name }} - {% if item.nameservers is defined -%} - {%- for nameserver in item.nameservers -%} - nameserver {{ nameserver.name }} {{ nameserver.ip }}:{{ nameserver.port }} - {% endfor -%} - {% endif -%} - {% if item.hold is defined -%} - {%- for hold in item.hold -%} - hold {{ hold.status }} {{ hold.period }} - {% endfor -%} - {% endif -%} - {% if item.resolve_retries is defined -%} - resolve_retries {{ item.resolve_retries }} - {% endif -%} - {% if item.timeout_retry is defined -%} - timeout retry {{ timeout.timeout_retry }} - {% endif -%} diff --git a/templates/userlist.cfg b/templates/userlist.cfg deleted file mode 100644 index 5507d85..0000000 --- a/templates/userlist.cfg +++ /dev/null @@ -1,14 +0,0 @@ -#{{ ansible_managed }} -userlist {{ item.name }} -{% if item.groups is defined %} -{% for group in item.groups %} - group {{ group.name }} {% if group.users is defined %} users {{ ','.join(group.users) }}{% endif %} - -{% endfor %} -{% endif %} -{% if item.users is defined %} -{% for user in item.users %} - user {{ user.name }} password {{ user.password }}{% if user.groups is defined %} groups {{ ','.join(user.groups) }}{% endif %} - -{% endfor %} -{% endif %} diff --git a/test/filter_plugins/conftest.py b/test/filter_plugins/conftest.py new file mode 100644 index 0000000..6b390e7 --- /dev/null +++ b/test/filter_plugins/conftest.py @@ -0,0 +1,4 @@ +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) diff --git a/test/filter_plugins/fixtures/config.yml b/test/filter_plugins/fixtures/config.yml new file mode 100644 index 0000000..f9b27a2 --- /dev/null +++ b/test/filter_plugins/fixtures/config.yml @@ -0,0 +1,154 @@ +--- +haproxy_defaults: + log: global + mode: http + option: + - httplog + - dontlognull + timeout: + connect: 5000 + client: 50000 + server: 50000 + errorfile: + 400: /etc/haproxy/errors/400.http + 403: /etc/haproxy/errors/403.http + 408: /etc/haproxy/errors/408.http + 500: /etc/haproxy/errors/500.http + 502: /etc/haproxy/errors/502.http + 503: /etc/haproxy/errors/503.http + 504: /etc/haproxy/errors/504.http + + +haproxy_global: + log: + /dev/log: + - local0 + - local1 notice + chroot: /var/lib/haproxy + stats: + socket: /run/haproxy/admin.sock mode 660 level admin + timeout: 30s + user: haproxy + group: haproxy + daemon: true + ca-base: /etc/ssl/certs + crt-base: /etc/ssl/private + ssl-default-bind-ciphers: ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS + ssl-default-bind-options: no-sslv3 + + +haproxy_mailers: + mailers1: + mailer: + smtp1: 192.0.2.1:587 + smtp2: 192.0.2.2:587 + mailers2: + mailer: + smtp1: 192.51.100.1:587 + smtp2: 192.51.100.2:587 + + +haproxy_peers: + peers1: + peer: + haproxy1: 192.0.2.1:1024 + haproxy2: 192.0.2.2:1024 + haproxy3: 192.0.2.3:1024 + peers2: + peer: + haproxy1: 192.51.100.1:1024 + haproxy2: 192.51.100.2:1024 + haproxy3: 192.51.100.3:1024 + + +haproxy_resolvers: + resolvers1: + nameserver: + dns1: 192.0.2.1:53 + dns2: 192.0.2.2:53 + resolve_retries: 3 + timeout: + retry: 1s + hold: + valid: 10s + resolvers2: + nameserver: + dns1: 192.51.100.1:53 + dns2: 192.51.100.2:53 + resolve_retries: 3 + timeout: + retry: 1s + hold: + valid: 10s + + +haproxy_userlists: + userlist1: + group: + - G1 users tiger,scott + - G2 users xdb,scott + user: + - tiger password $6$k6y3o.eP$JlKBx9za9667qe4(...)xHSwRv6J.C0/D7cV91 + - scott insecure-password elgato + - xdb insecure-password hello + userlist2: + group: + - G1 + - G2 + user: + - tiger password $6$k6y3o.eP$JlKBx(...)xHSwRv6J.C0/D7cV91 groups G1 + - scott insecure-password elgato groups G1,G2 + - xdb insecure-password hello groups G2 + + +haproxy_listens: + listen1: + mode: http + bind: + - :80 + - :443 ssl crt /etc/haproxy/site.pem + acl: + - invalid_src src 0.0.0.0/7 224.0.0.0/3 + - invalid_src src_port 0:1023 + - local_dst hdr(host) -i localhost + - es req.fhdr(accept-language),language(es;fr;en) -m str es + - fr req.fhdr(accept-language),language(es;fr;en) -m str fr + - en req.fhdr(accept-language),language(es;fr;en) -m str en + tcp-request: + connection: + - accept if { src -f /etc/haproxy/whitelist.lst } + - reject if { src_conn_rate gt 10 } + - track-sc0 src + block: + - if invalid_src || local_dst + http-request: + deny: + - if invalid_src || local_dst + reqdel: + - ^X-Forwarded-For:.* + - ^Cookie:.*SERVER= + reqpass: + - ^Host:\ www.private\.local + reqadd: + - X-Proto:\ SSL if is-ssl + redirect: + - prefix https://mysite.com set-cookie SEEN=1 if !cookie_set + - prefix https://mysite.com if login_page !secure + - prefix http://mysite.com drop-query if login_page !uid_given + - location http://mysite.com/ if !login_page secure + - location / clear-cookie USERID= if logout + use_backend: + - backend1 if is-app1 + - backend2 if is-app2 + use-server: + - www if { req_ssl_sni -i www.example.com } + - mail if { req_ssl_sni -i mail.example.com } + - imap if { req_ssl_sni -i imap.example.com } + server: + www: 192.0.2.1:443 weight 0 + mail: 192.0.2.1:587 weight 0 + imap: 192.0.2.1:993 weight 0 + default: 192.0.2.2:443 check + listen2: + no log: true + disabled: false diff --git a/test/filter_plugins/fixtures/expected.yml b/test/filter_plugins/fixtures/expected.yml new file mode 100644 index 0000000..74b0ded --- /dev/null +++ b/test/filter_plugins/fixtures/expected.yml @@ -0,0 +1,125 @@ +--- +haproxy_defaults: + - errorfile 400 /etc/haproxy/errors/400.http + - errorfile 403 /etc/haproxy/errors/403.http + - errorfile 408 /etc/haproxy/errors/408.http + - errorfile 500 /etc/haproxy/errors/500.http + - errorfile 502 /etc/haproxy/errors/502.http + - errorfile 503 /etc/haproxy/errors/503.http + - errorfile 504 /etc/haproxy/errors/504.http + - log global + - mode http + # Preserve order within YAML sequences. + - option httplog + - option dontlognull + - timeout client 50000 + - timeout connect 5000 + - timeout server 50000 + +haproxy_global: + - ca-base /etc/ssl/certs + - chroot /var/lib/haproxy + - crt-base /etc/ssl/private + - daemon + - group haproxy + - log /dev/log local0 + - log /dev/log local1 notice + - ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS + - ssl-default-bind-options no-sslv3 + - stats socket /run/haproxy/admin.sock mode 660 level admin + - stats timeout 30s + - user haproxy + +haproxy_mailers: + mailers1: + - mailer smtp1 192.0.2.1:587 + - mailer smtp2 192.0.2.2:587 + mailers2: + - mailer smtp1 192.51.100.1:587 + - mailer smtp2 192.51.100.2:587 + +haproxy_peers: + peers1: + - peer haproxy1 192.0.2.1:1024 + - peer haproxy2 192.0.2.2:1024 + - peer haproxy3 192.0.2.3:1024 + peers2: + - peer haproxy1 192.51.100.1:1024 + - peer haproxy2 192.51.100.2:1024 + - peer haproxy3 192.51.100.3:1024 + +haproxy_resolvers: + resolvers1: + - hold valid 10s + - nameserver dns1 192.0.2.1:53 + - nameserver dns2 192.0.2.2:53 + - resolve_retries 3 + - timeout retry 1s + resolvers2: + - hold valid 10s + - nameserver dns1 192.51.100.1:53 + - nameserver dns2 192.51.100.2:53 + - resolve_retries 3 + - timeout retry 1s + +haproxy_userlists: + userlist1: + - group G1 users tiger,scott + - group G2 users xdb,scott + - user tiger password $6$k6y3o.eP$JlKBx9za9667qe4(...)xHSwRv6J.C0/D7cV91 + - user scott insecure-password elgato + - user xdb insecure-password hello + userlist2: + - group G1 + - group G2 + - user tiger password $6$k6y3o.eP$JlKBx(...)xHSwRv6J.C0/D7cV91 groups G1 + - user scott insecure-password elgato groups G1,G2 + - user xdb insecure-password hello groups G2 + +haproxy_listens: + listen1: + # rank -1 + - acl invalid_src src 0.0.0.0/7 224.0.0.0/3 + - acl invalid_src src_port 0:1023 + - acl local_dst hdr(host) -i localhost + - acl es req.fhdr(accept-language),language(es;fr;en) -m str es + - acl fr req.fhdr(accept-language),language(es;fr;en) -m str fr + - acl en req.fhdr(accept-language),language(es;fr;en) -m str en + # rank 0, in alphabetical order + - bind :80 + - bind :443 ssl crt /etc/haproxy/site.pem + - mode http + # rank 2 + - tcp-request connection accept if { src -f /etc/haproxy/whitelist.lst } + - tcp-request connection reject if { src_conn_rate gt 10 } + - tcp-request connection track-sc0 src + # rank 3 + - block if invalid_src || local_dst + # rank 4 + - http-request deny if invalid_src || local_dst + # rank 5, in order + - reqdel ^X-Forwarded-For:.* + - reqdel ^Cookie:.*SERVER= + - reqpass ^Host:\ www.private\.local + # rank 6 + - reqadd X-Proto:\ SSL if is-ssl + # rank 7 + - redirect prefix https://mysite.com set-cookie SEEN=1 if !cookie_set + - redirect prefix https://mysite.com if login_page !secure + - redirect prefix http://mysite.com drop-query if login_page !uid_given + - redirect location http://mysite.com/ if !login_page secure + - redirect location / clear-cookie USERID= if logout + # rank 8 + - use_backend backend1 if is-app1 + - use_backend backend2 if is-app2 + # rank 9 + - use-server www if { req_ssl_sni -i www.example.com } + - use-server mail if { req_ssl_sni -i mail.example.com } + - use-server imap if { req_ssl_sni -i imap.example.com } + # rank 100 + - server default 192.0.2.2:443 check + - server imap 192.0.2.1:993 weight 0 + - server mail 192.0.2.1:587 weight 0 + - server www 192.0.2.1:443 weight 0 + listen2: + - no log diff --git a/test/filter_plugins/haproxy_test.py b/test/filter_plugins/haproxy_test.py new file mode 100644 index 0000000..2949c26 --- /dev/null +++ b/test/filter_plugins/haproxy_test.py @@ -0,0 +1,44 @@ +import os + +import pytest +import yaml + +from filter_plugins import haproxy + + +@pytest.fixture +def config(): + config_file = os.path.join(os.path.dirname(__file__), 'fixtures', 'config.yml') + return yaml.load(open(config_file)) + + +@pytest.fixture +def expected(): + results_file = os.path.join(os.path.dirname(__file__), 'fixtures', 'expected.yml') + return yaml.load(open(results_file)) + + +@pytest.mark.parametrize( + ('section',), + [ + ('haproxy_defaults',), + ('haproxy_global',), + ] +) +def test_haproxy_single_sections(config, expected, section): + assert list(haproxy.to_haproxy(config[section])) == expected[section] + + +@pytest.mark.parametrize( + ('section',), + [ + ('haproxy_mailers',), + ('haproxy_peers',), + ('haproxy_resolvers',), + ('haproxy_userlists',), + ('haproxy_listens',), + ] +) +def test_haproxy_multiple_sections(config, expected, section): + for group, options in config[section].items(): + assert list(haproxy.to_haproxy(options)) == expected[section][group] diff --git a/test/integration/default/default.yml b/test/integration/default/default.yml index f65f4bc..ecac5e0 100644 --- a/test/integration/default/default.yml +++ b/test/integration/default/default.yml @@ -1,26 +1,15 @@ --- - hosts: localhost - roles: - - { role: ansible-haproxy, - haproxy_frontends: [ - { - name: 'fe-mysupersite', - ip: '*', - port: '80', - maxconn: '1000', - default_backend: 'be-mysupersite', - } - ], - haproxy_backends: [ - { - name: 'be-mysupersite', - description: 'mysupersite is really cool', - servers: [ - { - name: 'be-mysupersite-01', - ip: '192.168.1.100' - } - ] - } - ] } \ No newline at end of file + - role: ansible-haproxy + haproxy_frontends: + fe-mysupersite: + bind: '*:80' + maxconn: 1000 + default_backend: be-mysupersite + + haproxy_backends: + be-mysupersite: + description: mysupersite is really cool + server: + be-mysupersite-01: 192.168.1.100 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..0bb429b --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py27,py35,py36 + +[testenv] +deps = + PyYAML + pytest + pytest-cov +commands = + pytest -vv --cov=filter_plugins diff --git a/vars/main.yml b/vars/main.yml index 3baea50..0161f47 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -1,235 +1,2 @@ --- - empty: true -#haproxy_global: -# chroot: -# pidfile: -# maxconn: -# user: -# uid: -# group: -# gid: -# daemon: -# nbproc: -# spread_checks: -# stats: -# socket: -# timeout: -# log: -# - address: -# facility: -# level: -# minlevel: -# format: -# ssl_default_bind_options: -# ssl_default_bind_ciphers: -# tune: -# chksize: 32768 -# ssl: -# default-dh-param: 2048 -# zlib: -# memlevel: 9 -# -#haproxy_defaults: -# mode: -# log: -# - address: -# facility: -# level: -# minlevel: -# format: -# options: -# -