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
This commit is contained in:
Jonathan Rosser 2023-03-07 15:06:07 +00:00 committed by Damian Dabrowski
parent 56fef3de83
commit d548b7e5ff
6 changed files with 114 additions and 0 deletions

View File

@ -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

View File

@ -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"

View File

@ -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.

View File

@ -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>.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

3
templates/map.j2 Normal file
View File

@ -0,0 +1,3 @@
{% for m in item.1.entries %}
{{ m }}
{% endfor %}

View File

@ -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 %}