From d548b7e5ff8ea377d8640fee8e4ab41cbdede2b6 Mon Sep 17 00:00:00 2001 From: Jonathan Rosser Date: Tue, 7 Mar 2023 15:06:07 +0000 Subject: [PATCH] Add support for haproxy map files HAProxy supports the use of map files for selecting backends, or a number of other functions. See [1] and [2]. This patch adds the key `maps` for each service definition allowing fragments of a complete map to be defined across all the services, with each service contributing some elements to the overall map file. The service enabled/disabled and state flags are observed to add and remove entries from the map file, and individual map entries can also be marked as present/absent to make inclusion conditional. [1] https://www.haproxy.com/blog/introduction-to-haproxy-maps/ [2] https://www.haproxy.com/documentation/hapee/latest/configuration/map-files/syntax/ Change-Id: I755c18a4d33ee69c42d68a50daa63614a2b2feb7 --- defaults/main.yml | 42 +++++++++++++++++ handlers/main.yml | 9 ++++ .../notes/haproxy-maps-787084d7f161c27e.yaml | 11 +++++ tasks/haproxy_service_config.yml | 46 +++++++++++++++++++ templates/map.j2 | 3 ++ templates/service.j2 | 3 ++ 6 files changed, 114 insertions(+) create mode 100644 releasenotes/notes/haproxy-maps-787084d7f161c27e.yaml create mode 100644 templates/map.j2 diff --git a/defaults/main.yml b/defaults/main.yml index bf757a2..b8ced74 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -90,6 +90,48 @@ haproxy_service_configs: [] # haproxy_service_enabled: True # haproxy_default_backend: other_backend +# HAProxy maps (unrelated keys are omitted but are required as the previous service example) +# Example: +# haproxy_service_configs: +# - service: +# state: present # state 'absent' will remove map entries defined in this service +# haproxy_service_enabled: true # haproxy_service_enabled 'false' will remove map entries defined in this service +# haproxy_service_name: "one" +# haproxy_maps: +# - 'use_backend %[req.hdr(host),lower,map(/etc/haproxy/route.map)]' +# haproxy_map_entries: +# - name: 'route' # this service contributes entries to the map called 'route' +# order: 10 # prefix the name of the map fragment wih this string to control ordering of the assembled map +# entries: +# - compute.example.com nova-api +# - dashboard.example.com horizon +# - service: # this service has no map definitions +# haproxy_service_name: "two" +# - service: +# haproxy_service_name: "three" +# haproxy_map_entries: +# - name: 'route' # this service contributes to the map called 'route' +# entries: +# - s3.example.com radosgw +# - sso.example.com keycloak +# - name: 'rate' # and also to the map called 'rate' +# state: present # individual map entries can be removed with state 'absent' +# entries: +# - /api/foo 20 +# - /api/bar 40 +# +# Results: +# +# /etc/haproxy/route.map +# s3.example.com radosgw +# sso.example.com keycloak +# compute.example.com nova-api +# dashboard.example.com horizon +# +# /etc/haproxy/rate.map +# /api/foo 20 +# /api/bar 40 + galera_monitoring_user: monitoring haproxy_bind_on_non_local: False diff --git a/handlers/main.yml b/handlers/main.yml index aa55ae1..3355e81 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -25,6 +25,15 @@ listen: - cert installed +- name: regenerate maps + vars: + all_changed_results: "{{ (map_create.results + map_delete.results) | select('changed') }}" + assemble: + src: "/etc/haproxy/map.conf.d/{{ item }}" + dest: "/etc/haproxy/{{ item }}.map" + notify: Reload haproxy + with_items: "{{ all_changed_results | map(attribute='item') | flatten | selectattr('name', 'defined') | map(attribute='name') | unique }}" + - name: Regenerate haproxy configuration assemble: src: "/etc/haproxy/conf.d" diff --git a/releasenotes/notes/haproxy-maps-787084d7f161c27e.yaml b/releasenotes/notes/haproxy-maps-787084d7f161c27e.yaml new file mode 100644 index 0000000..dce2b54 --- /dev/null +++ b/releasenotes/notes/haproxy-maps-787084d7f161c27e.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + A new key `haproxy_map_entries` is now able to be configured for each + haproxy service definition to allow arbitrary entries to be placed in + any number of haproxy map files which may then be referenced in other + directives in the haproxy config file such as ``use_backend`` or + ``http-request``. The complete map files are constructed from the + fragments defined across all the service definitions and are assembled + into a complete map file in alphanumeric sort order, or optionally with + a user defined ordering. diff --git a/tasks/haproxy_service_config.yml b/tasks/haproxy_service_config.yml index a900539..e7ee560 100644 --- a/tasks/haproxy_service_config.yml +++ b/tasks/haproxy_service_config.yml @@ -13,6 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. + +########################################################################### +# Service frontends and backends assembled from fragments into haproxy.conf +########################################################################### + - name: Create haproxy service config files template: src: service.j2 @@ -41,3 +46,44 @@ (item.service.state is defined and item.service.state == 'absent') tags: - haproxy-service-config + +########################################################################### +# Map files assembled from fragments from each service into .map +########################################################################### + +- name: Create haproxy map fragment directories + file: + state: directory + path: "/etc/haproxy/map.conf.d/{{ item }}" + with_items: "{{ haproxy_service_configs | selectattr('service.haproxy_map_entries', 'defined') | map(attribute='service.haproxy_map_entries') | flatten | map(attribute='name') | unique }}" + +# create map entries when the service is enabled and an existing map fragment is not absent +- name: Create haproxy map files + vars: + map_file: "/etc/haproxy/map.conf.d/{{ item.1.name }}/{{ item.1.order | default('00') }}-{{ item.0.service.haproxy_service_name }}.map" + template: + src: map.j2 + dest: "{{ map_file }}" + with_subelements: + - "{{ haproxy_service_configs | selectattr('service.haproxy_map_entries', 'defined') }}" + - service.haproxy_map_entries + when: + - (item.0.service.haproxy_service_enabled | default(True)) | bool + - item.1.state | default('present') != 'absent' + notify: regenerate maps + register: map_create + +# remove map entries when the service is not enabled, the service is absent or the map is absent +- name: Delete unused map entries + file: + state: absent + path: "/etc/haproxy/map.conf.d/{{ item.1.name }}/{{ item.1.order | default('00') }}-{{ item.0.service.haproxy_service_name }}.map" + when: + - (item.0.service.haproxy_service_enabled | default('True')) | bool is falsy or + (item.0.service.state is defined and item.0.service.state == 'absent') or + (item.1.state | default('present') == 'absent') + with_subelements: + - "{{ haproxy_service_configs | selectattr('service.haproxy_map_entries', 'defined') }}" + - service.haproxy_map_entries + notify: regenerate maps + register: map_delete diff --git a/templates/map.j2 b/templates/map.j2 new file mode 100644 index 0000000..b7f4482 --- /dev/null +++ b/templates/map.j2 @@ -0,0 +1,3 @@ +{% for m in item.1.entries %} +{{ m }} +{% endfor %} diff --git a/templates/service.j2 b/templates/service.j2 index 3d7c7fb..543d97f 100644 --- a/templates/service.j2 +++ b/templates/service.j2 @@ -79,6 +79,9 @@ frontend {{ item.service.haproxy_service_name }}-front-{{ loop.index }} {% endif %} {% endfor %} {% endif %} +{% for entry in item.service.haproxy_maps | default([]) %} + {{ entry }} +{% endfor %} {% if (item.service.haproxy_ssl | default(false) | bool) and request_option == 'http' and (loop.index == 1 or vip_address in extra_lb_tls_vip_addresses or (item.service.haproxy_ssl_all_vips | default(false) | bool and vip_address not in extra_lb_vip_addresses)) %} http-request add-header X-Forwarded-Proto https {% endif %}