heat_template_version: rocky

description: >
  OpenStack containerized HAproxy service

parameters:
  ContainerHAProxyImage:
    description: image
    type: string
  ContainerHAProxyConfigImage:
    description: The container image to use for the haproxy config_volume
    type: string
  ServiceData:
    default: {}
    description: Dictionary packing service data
    type: json
  ServiceNetMap:
    default: {}
    description: Mapping of service_name -> network name.  Typically set
                 via parameter_defaults in the resource registry.   This
                 mapping overrides those in ServiceNetMapDefaults. 
    type: json
  DefaultPasswords:
    default: {}
    type: json
  EndpointMap:
    default: {}
    description: Mapping of service endpoint -> protocol.  Typically set
                 via parameter_defaults in the resource registry. 
    type: json
  HAProxyStatsPassword:
    description: Password for HAProxy stats endpoint
    hidden: true
    type: string
  HAProxyStatsUser:
    description: User for HAProxy stats endpoint
    default: admin
    type: string
  HAProxySyslogAddress:
    default: /dev/log
    description: Syslog address where HAproxy will send its log
    type: string
  HAProxySyslogFacility:
    default: local0
    description: Syslog facility HAProxy will use for its logs
    type: string
  SSLCertificate:
    default: ''
    description: >
      The content of the SSL certificate (without Key) in PEM format. 
    type: string
  PublicSSLCertificateAutogenerated:
    default: false
    description: >
      Whether the public SSL certificate was autogenerated or not. 
    type: boolean
  EnablePublicTLS:
    default: true
    description: >
      Whether to enable TLS on the public interface or not. 
    type: boolean
  DeployedSSLCertificatePath:
    default: '/etc/pki/tls/private/overcloud_endpoint.pem'
    description: >
        The filepath of the certificate as it will be stored in the controller. 
    type: string
  RedisPassword:
    description: The password for the redis service account. 
    type: string
    hidden: true
  MonitoringSubscriptionHaproxy:
    default: 'overcloud-haproxy'
    type: string
  RoleName:
    default: ''
    description: Role name on which the service is applied
    type: string
  RoleParameters:
    default: {}
    description: Parameters specific to the role
    type: json
  EnableInternalTLS:
    type: boolean
    default: false
  InternalTLSCAFile:
    default: '/etc/ipa/ca.crt'
    type: string
    description: Specifies the default CA cert to use if TLS is used for
                 services in the internal network. 
  ConfigDebug:
    default: false
    description: Whether to run config management (e.g. Puppet) in debug mode. 
    type: boolean
  EnableLoadBalancer:
    default: true
    description: Whether to deploy a LoadBalancer, set to false when an external load balancer is used. 
    type: boolean
  HAProxyStatsEnabled:
    default: true
    description: Whether or not to enable the HAProxy stats interface. 
    type: boolean
  InternalTLSCRLPEMFile:
    default: '/etc/pki/CA/crl/overcloud-crl.pem'
    type: string
    description: Specifies the default CRL PEM file to use for revocation if
                 TLS is used for services in the internal network. 
  InternalTLSCRLPEMDir:
    default: '/etc/pki/CA/crl/'
    type: string
    description: The directory of the CRL PEM file to be mounted. 
  ZaqarWsTimeout:
    default: '14400'
    type: string
    description: The tunnel timeout in seconds for the Zaqar API. 

conditions:
  internal_tls_enabled: {equals: [{get_param: EnableInternalTLS}, true]}
  public_tls_enabled:
    and:
    - {get_param: EnablePublicTLS}
    - or:
      - not:
          equals:
          - {get_param: SSLCertificate}
          - ""
      - equals:
          - {get_param: PublicSSLCertificateAutogenerated}
          - true

resources:

  ContainersCommon:
    type: ../containers-common.yaml

  HAProxyLogging:
    type: OS::TripleO::Services::Logging::HAProxy

  HAProxyPublicTLS:
    type: OS::TripleO::Services::HAProxyPublicTLS
    properties:
      ServiceData: {get_param: ServiceData}
      ServiceNetMap: {get_param: ServiceNetMap}
      DefaultPasswords: {get_param: DefaultPasswords}
      EndpointMap: {get_param: EndpointMap}
      RoleName: {get_param: RoleName}
      RoleParameters: {get_param: RoleParameters}

  HAProxyInternalTLS:
    type: OS::TripleO::Services::HAProxyInternalTLS
    properties:
      ServiceData: {get_param: ServiceData}
      ServiceNetMap: {get_param: ServiceNetMap}
      DefaultPasswords: {get_param: DefaultPasswords}
      EndpointMap: {get_param: EndpointMap}
      RoleName: {get_param: RoleName}
      RoleParameters: {get_param: RoleParameters}

outputs:
  role_data:
    description: Role data for the HAproxy role. 
    value:
      service_name: haproxy
      firewall_rules:
        '107 haproxy stats':
          dport: 1993
      monitoring_subscription: {get_param: MonitoringSubscriptionHaproxy}
      config_settings:
        map_merge:
          - get_attr: [HAProxyLogging, config_settings]
          - tripleo::haproxy::haproxy_service_manage: false
            tripleo::haproxy::haproxy_log_address: {get_param: HAProxySyslogAddress}
            tripleo::haproxy::haproxy_log_facility: {get_param: HAProxySyslogFacility}
            tripleo::haproxy::haproxy_stats_user: {get_param: HAProxyStatsUser}
            tripleo::haproxy::haproxy_stats_password: {get_param: HAProxyStatsPassword}
            tripleo::haproxy::haproxy_stats_bind_address:
              str_replace:
                template:
                  "%{hiera('$NETWORK')}"
                params:
                  $NETWORK: {get_param: [ServiceNetMap, HaproxyNetwork]}
            tripleo::haproxy::redis_password: {get_param: RedisPassword}
            tripleo::haproxy::crl_file: {get_param: InternalTLSCRLPEMFile}
            tripleo::haproxy::haproxy_stats: {get_param: HAProxyStatsEnabled}
            tripleo::haproxy::zaqar_ws_timeout_tunnel: {get_param: ZaqarWsTimeout}
            enable_load_balancer: {get_param: EnableLoadBalancer}
            tripleo::profile::base::haproxy::certificates_specs:
              map_merge:
                - get_attr: [HAProxyPublicTLS, role_data, certificates_specs]
                - get_attr: [HAProxyInternalTLS, role_data, certificates_specs]
          - if:
              - public_tls_enabled
              - tripleo::haproxy::service_certificate: {get_param: DeployedSSLCertificatePath}
              - {}
          - if:
              - internal_tls_enabled
              - tripleo::haproxy::ca_bundle: {get_param: InternalTLSCAFile}
              - null
          - get_attr: [HAProxyPublicTLS, role_data, config_settings]
          - get_attr: [HAProxyInternalTLS, role_data, config_settings]
      # BEGIN DOCKER SETTINGS
      puppet_config:
        config_volume: haproxy
        puppet_tags: haproxy_config
        step_config: |
          class {'tripleo::profile::base::haproxy': manage_firewall => false}
        config_image: {get_param: ContainerHAProxyConfigImage}
        volumes:
          list_concat:
          - if:
            - public_tls_enabled
            - - list_join:
                - ':'
                - - {get_param: DeployedSSLCertificatePath}
                  - {get_param: DeployedSSLCertificatePath}
                  - 'ro,shared'
            - null
          - if:
            - internal_tls_enabled
            - - /etc/pki/tls/certs/haproxy:/etc/pki/tls/certs/haproxy:ro,shared
              - /etc/pki/tls/private/haproxy:/etc/pki/tls/private/haproxy:ro,shared
              - list_join:
                  - ':'
                  - - {get_param: InternalTLSCAFile}
                    - {get_param: InternalTLSCAFile}
                    - 'ro,shared'
              - list_join:
                  - ':'
                  - - {get_param: InternalTLSCRLPEMDir}
                    - {get_param: InternalTLSCRLPEMDir}
                    - 'ro,shared'
            - null
      kolla_config:
        /var/lib/kolla/config_files/haproxy.json:
          # HAProxy 1.8 doesn't ship haproxy-systemd-wrapper, we have
          # to use a new dedicated option for live config reload. 
          # Note: we can't use quotes in kolla command, hence the workaround
          command: bash -c $* -- eval if [ -f /usr/sbin/haproxy-systemd-wrapper ]; then exec /usr/sbin/haproxy-systemd-wrapper -f /etc/haproxy/haproxy.cfg; else exec /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -Ws; fi
          config_files:
            - source: "/var/lib/kolla/config_files/src/*"
              dest: "/"
              merge: true
              preserve_properties: true
            - source: "/var/lib/kolla/config_files/src-tls/*"
              dest: "/"
              merge: true
              preserve_properties: true
              optional: true
          permissions:
            - path: /var/lib/haproxy
              owner: haproxy:haproxy
              recurse: true
            - path: /etc/pki/tls/certs/haproxy
              owner: haproxy:haproxy
              recurse: true
              optional: true
      container_config_scripts: {get_attr: [ContainersCommon, container_config_scripts]}
      docker_config:
        step_1:
          map_merge:
          - get_attr: [HAProxyLogging, docker_config, step_1]
          - haproxy:
              start_order: 1
              image: {get_param: ContainerHAProxyImage}
              net: host
              restart: always
              security_opt: label=disable
              volumes:
                list_concat:
                  - {get_attr: [ContainersCommon, volumes]}
                  - {get_attr: [HAProxyLogging, volumes]}
                  -
                    - /var/lib/kolla/config_files/haproxy.json:/var/lib/kolla/config_files/config.json:ro
                    - /var/lib/config-data/puppet-generated/haproxy:/var/lib/kolla/config_files/src:ro
                    - /var/lib/haproxy:/var/lib/haproxy:rw,z
                  - if:
                    - public_tls_enabled
                    - - list_join:
                        - ':'
                        - - {get_param: DeployedSSLCertificatePath}
                          - list_join:
                              - ''
                              - - /var/lib/kolla/config_files/src-tls/
                                - {get_param: DeployedSSLCertificatePath}
                          - 'ro,shared'
                    - null
                  - if:
                    - internal_tls_enabled
                    - - /etc/pki/tls/certs/haproxy:/var/lib/kolla/config_files/src-tls/etc/pki/tls/certs/haproxy:ro,shared
                      - /etc/pki/tls/private/haproxy:/var/lib/kolla/config_files/src-tls/etc/pki/tls/private/haproxy:ro,shared
                      - list_join:
                          - ':'
                          - - {get_param: InternalTLSCRLPEMDir}
                            - {get_param: InternalTLSCRLPEMDir}
                            - 'ro'
                    - null
              environment:
                KOLLA_CONFIG_STRATEGY: COPY_ALWAYS
      deploy_steps_tasks:
        - name: Configure rsyslog for HAproxy container
          when: step|int == 1
          block:
            - name: Check if rsyslog exists
              shell: systemctl is-active rsyslog
              register: rsyslog_config
            - when:
                - rsyslog_config is changed
                - rsyslog_config.rc == 0
              block:
                - name: Forward logging to haproxy.log file
                  blockinfile:
                    content: |
                      if $syslogfacility-text == '{{facility}}' and $programname == 'haproxy' then -/var/log/containers/haproxy/haproxy.log
                      & stop
                    create: yes
                    path: /etc/rsyslog.d/openstack-haproxy.conf
                  vars:
                    facility: {get_param: HAProxySyslogFacility}
                  register: logconfig
                - name: restart rsyslog service after logging conf change
                  service:
                    name: rsyslog
                    state: restarted
                  when: logconfig is changed
        - name: Run puppet on the host to apply IPtables rules
          no_log: true
          when: step|int == 1
          register: puppet_host_outputs
          shell: |
            puppet apply {{ (puppet_debug|bool) | ternary('--debug --verbose', '') }} --detailed-exitcodes --summarize --color=false \
                --modulepath '{{ puppet_modulepath }}' --tags '{{ puppet_tags }}' -e '{{ puppet_execute }}'
          register: puppet_host_outputs
          changed_when: puppet_host_outputs.rc == 2
          failed_when: false
          vars:
            puppet_execute: include tripleo::profile::base::haproxy
            puppet_tags: tripleo::firewall::rule
            puppet_modulepath: /etc/puppet/modules:/opt/stack/puppet-modules:/usr/share/openstack-puppet/modules
            puppet_debug: {get_param: ConfigDebug}
        - name: "Debug output for task: Run puppet on the host to apply IPtables rules"
          debug:
            var: puppet_host_outputs.stdout_lines | default([]) | union(puppet_host_outputs.stderr_lines | default([]))
          when:
            - not (ansible_check_mode | bool)
            - puppet_host_outputs.rc is defined
          failed_when: puppet_host_outputs.rc not in [0, 2]
      upgrade_tasks:
        - name: ensure we have haproxy log dir with the correct setype
          file:
            path: /var/log/containers/haproxy
            state: directory
            setype: var_log_t
            recurse: yes
          when: step|int == 1
      external_upgrade_tasks:
        - when:
            - step|int == 1
          tags:
            - never
            - system_upgrade_transfer_data
            - system_upgrade_stop_services
          block:
            - name: Stop haproxy container
              import_role:
                name: tripleo_container_stop
              vars:
                tripleo_containers_to_stop:
                  - haproxy
                tripleo_delegate_to: "{{ groups['haproxy'] | default([]) }}"
      host_prep_tasks:
        - {get_attr: [HAProxyPublicTLS, role_data, host_prep_tasks]}
        - name: create persistent directories
          file:
            path: "{{ item.path }}"
            state: directory
            setype: "{{ item.setype }}"
            mode: "{{ item.mode|default(omit) }}"
          with_items:
            - { 'path': /var/log/containers/haproxy, 'setype': var_log_t, 'mode': '0750' }
            - { 'path': /var/lib/haproxy, 'setype': container_file_t }
      metadata_settings:
        list_concat:
          - {get_attr: [HAProxyPublicTLS, role_data, metadata_settings]}
          - {get_attr: [HAProxyInternalTLS, role_data, metadata_settings]}
