---
- name: Setup firewalld
  include_tasks: firewalld.yml

- name: Check which conflicting services are enabled
  # noqa command-instead-of-module
  command: systemctl is-enabled "{{ item }}"
  register: __firewall_conflicting_services_status
  changed_when: false
  failed_when: false
  loop: "{{ __firewall_conflicting_services }}"
  when: firewall_disable_conflicting_services | bool

- name: Attempt to stop and disable conflicting services
  service:
    name: "{{ item.item }}"
    state: "{{ 'stopped' if __firewall_is_booted else omit }}"
    enabled: false
  loop: "{{ __firewall_conflicting_services_status.results }}"
  when:
    - firewall_disable_conflicting_services | bool
    - item.rc == 0

- name: Unmask firewalld service
  systemd:
    name: "{{ __firewall_service }}"
    masked: false
  when: ansible_facts.service_mgr == "systemd"

- name: Enable and start firewalld service
  service:
    name: "{{ __firewall_service }}"
    enabled: true
    state: "{{ 'started' if __firewall_is_booted else omit }}"

- name: Check if previous replaced is defined
  set_fact:
    __firewall_previous_replaced: "{{ firewall | d([]) |
      selectattr('previous', 'defined') |
      selectattr('previous', 'match', '^replaced$') |
      list | length > 0 }}"
    __firewall_python_cmd: "{{ ansible_python_interpreter |
      d(discovered_interpreter_python) }}"
    __firewall_report_changed: true

- name: Handle previous replaced
  when: __firewall_previous_replaced | bool
  block:
    - name: Get config files, checksums before and remove
      script:
        cmd: >
          files/get_files_checksums.sh {{ __firewall_python_cmd | quote }}
          {{ __firewall_firewalld_dir | quote }}
          {{ ansible_check_mode | ternary('false', 'true') }}
          {{ __firewall_package_with_conf | quote }}
          {{ __firewall_usr_lib_dir | quote }}
          {{ "booted" if __firewall_is_booted else "" }}
      register: __firewall_config_files_before
      changed_when: false
      check_mode: false

    - name: Tell firewall module it is able to report changed
      set_fact:
        __firewall_report_changed: "{{
          __firewall_config_files_before.stdout == '' }}"

# Explanation of loop - `firewall` is a list of dict.  We want
# to remove any key: value matching previous: replaced from every
# dict in the firewall list.  The `difference` filter removes
# items from lists, not dicts.  So we use `map` with `dict2items`
# to convert each dict in the `firewall` list to a list of key/value
# pairs.  Then, we use `map` with `difference` to remove all
# occurrences of previous: replaced from all dicts.  The `select`
# removes any lists which are now empty (e.g. the item contained
# only previous: replaced).  Then we use `map` with `items2dict`
# to convert each item in the list back to a dict.
# Also handle the case where firewall is a dict instead of a list by
# "casting" it into a list.
- name: Configure firewall
  vars:
    __previous:
      - key: previous
        value: replaced
    __detailed:
      - key: detailed
        value: true
      - key: detailed
        value: false
  firewall_lib:
    firewalld_conf: "{{ item.firewalld_conf | default(omit) }}"
    zone: "{{ item.zone | default(omit) }}"
    set_default_zone: "{{ item.set_default_zone | default(omit) }}"
    service: "{{ item.service | default(omit) }}"
    port: "{{ item.port | default(omit) }}"
    source_port: "{{ item.source_port | default(omit) }}"
    forward_port: "{{ item.forward_port | default(omit) }}"
    masquerade: "{{ item.masquerade | default(omit) }}"
    rich_rule: "{{ item.rich_rule | default(omit) }}"
    source: "{{ item.source | default(omit) }}"
    destination: "{{ item.destination | default(omit) }}"
    interface: "{{ item.interface | default(omit) }}"
    interface_pci_id: "{{ item.interface_pci_id | default(omit) }}"
    icmp_block: "{{ item.icmp_block | default(omit) }}"
    icmp_block_inversion: "{{ item.icmp_block_inversion | default(omit) }}"
    timeout: "{{ item.timeout | default(omit) }}"
    target: "{{ item.target | default(omit) }}"
    ipset: "{{ item.ipset | default(omit) }}"
    ipset_type: "{{ item.ipset_type | default(omit) }}"
    ipset_entries: "{{ item.ipset_entries | default(omit) }}"
    short: "{{ item.short | default(omit) }}"
    description: "{{ item.description | default(omit) }}"
    protocol: "{{ item.protocol | default(omit) }}"
    helper_module: "{{ item.helper_module | default(omit) }}"
    permanent: "{{ item.permanent | default(True) }}"
    runtime: "{{ item.runtime | default(__firewall_is_booted) }}"
    state: "{{ item.state | default(omit) }}"
    includes: "{{ item.includes | default(omit) }}"
    __report_changed: "{{ __firewall_report_changed }}"
    online: "{{ __firewall_is_booted }}"
  loop: "{{ firewall is mapping | ternary([firewall], firewall) |
    map('dict2items') | map('difference', __previous) |
    map('difference', __detailed) | select |
    map('items2dict') | list }}"
  register: firewall_lib_result

- name: Gather firewall ansible fact with args
  vars:
    fw: "{{ firewall is mapping | ternary([firewall], firewall) }}"
  when:
    - firewall != None
    - firewall | length == 1
    - "'detailed' in fw[0]"
  block:
    - name: Gather firewall config information
      vars:
        __previous:
          - key: previous
            value: replaced
      firewall_lib_facts:
        detailed: "{{ item.detailed | default(False) }}"
        online: "{{ __firewall_is_booted }}"
      loop: "{{ fw | map('dict2items') | map('difference', __previous) |
        select | map('items2dict') | list }}"
      register: __firewalld_facts

    - name: Update firewalld_config fact
      vars:
        firewalld_facts: "{{ __firewalld_facts.results[0] }}"
      set_fact:
        firewall_config: "{{ firewalld_facts.firewall_config }}"

- name: Gather firewall ansible fact if no args
  when: firewall == None or firewall | length == 0
  block:
    - name: Gather firewall config if no arguments
      firewall_lib_facts:
        online: "{{ __firewall_is_booted }}"
        detailed: false
      register: __firewalld_facts

    - name: Update firewalld_config fact
      set_fact:
        firewall_config: "{{ __firewalld_facts.firewall_config }}"

- name: Check config files after removal
  when:
    - __firewall_previous_replaced | bool
    - not ansible_check_mode
  block:
    - name: Get config files, checksums after
      script:
        cmd: >
          files/get_files_checksums.sh {{ __firewall_python_cmd | quote }}
          {{ __firewall_firewalld_dir | quote }} false
          {{ __firewall_package_with_conf | quote }}
          {{ __firewall_usr_lib_dir | quote }}
          {{ __firewall_is_booted | quote }}
      register: __firewall_config_files_after
      changed_when: false

    - name: Calculate what has changed
      set_fact:
        firewall_lib_result:
          changed: "{{
            __firewall_config_files_after.stdout_lines |
            symmetric_difference(__firewall_config_files_before.stdout_lines) |
            length > 0 }}"
      changed_when: firewall_lib_result.changed

    - name: Show diffs
      when: __firewall_debug | d(false)
      debug:
        msg: |
          __firewall_config_files_before {{
            __firewall_config_files_before.stdout_lines | to_nice_json }}
          __firewall_config_files_after {{
            __firewall_config_files_after.stdout_lines | to_nice_json }}
