# SPDX-License-Identifier: MIT
---
# We always need to create CIB to see whether it's the same as what is already
# present in the cluster. However, we don't want to report it as a change since
# the only thing which matters is pushing the resulting CIB to the cluster.


# Prepare CIB files

- name: Create a tempfile for original CIB
  tempfile:
    state: file
    suffix: _ha_cluster_original_cib_xml
  register: __ha_cluster_tempfile_original_cib_xml
  check_mode: false
  changed_when: not ansible_check_mode

- name: Create a tempfile for new CIB
  tempfile:
    state: file
    suffix: _ha_cluster_cib_xml
  register: __ha_cluster_tempfile_cib_xml
  check_mode: false
  changed_when: not ansible_check_mode

- name: Fetch CIB configuration
  command:
    cmd: cibadmin --query
  register: __ha_cluster_fetch_cib
  check_mode: false
  changed_when: false  # this is a read-only command

- name: Write CIB configuration
  copy:
    content: "{{ __ha_cluster_fetch_cib.stdout }}"
    dest: "{{ item }}"
    owner: root
    group: root
    mode: "0600"
  loop:
    - "{{ __ha_cluster_tempfile_cib_xml.path }}"
    - "{{ __ha_cluster_tempfile_original_cib_xml.path }}"
  check_mode: false
  changed_when: not ansible_check_mode

# Starting with an empty CIB would remove all nodes and other parts of CIB
# automatically created by pacemaker. That would effectively make the role to
# report changed == True every time. Therefore, we start with the current
# cluster CIB and purge it instead, thus keeping all the automatically created
# parts in place and report only actual configuration changes.
- name: Purge new CIB configuration
  command:
    cmd: >
      cibadmin --force --delete-all --xpath
      '/cib/configuration/*[not(
        self::crm_config or
        self::nodes or
        self::resources or
        self::constraints
      )]
      | /cib/configuration/*[self::resources or self::constraints]/*
      | /cib/configuration/nodes/*/*
      | /cib/configuration/crm_config//nvpair[not(
        @name="cluster-infrastructure" or
        @name="cluster-name" or
        @name="dc-version" or
        @name="have-watchdog" or
        @name="last-lrm-refresh" or
        @name="stonith-watchdog-timeout"
      )]'
  environment:
    CIB_file: "{{ __ha_cluster_tempfile_cib_xml.path }}"
  check_mode: false
  changed_when: not ansible_check_mode


# Build the new CIB
- name: Build the new CIB
  vars:
    __ha_cluster_pcs_cli_role:
      promoted: "{{
        ('pcmk.cib.roles.promoted-unpromoted' in __ha_cluster_pcs_capabilities)
        | ternary('Promoted', 'Master') }}"  # wokeignore:rule=master
      unpromoted: "{{
        ('pcmk.cib.roles.promoted-unpromoted' in __ha_cluster_pcs_capabilities)
        | ternary('Unpromoted', 'Slave') }}"  # wokeignore:rule=slave
  block:
    ## Cluster properties
    - name: Configure cluster properties
      include_tasks: pcs-cluster-properties.yml
      # pcs-0.11 supports only one set of attributes, that's the reason for
      # 'ha_cluster_cluster_properties[0]' and 'when' instead of looping over
      # ha_cluster_cluster_properties
      vars:
        properties_set: "{{ ha_cluster_cluster_properties[0] }}"
      when: ha_cluster_cluster_properties[0].attrs | d([])

    ## Node attributes
    - name: Configure node attributes
      include_tasks: pcs-node-attributes.yml
      loop: "{{ ha_cluster_node_options | selectattr('attributes', 'defined') |
        list }}"
      loop_control:
        loop_var: node_options

    ## Node utilization
    - name: Configure node utilization
      include_tasks: pcs-node-utilization.yml
      loop: >-
        {{
          ha_cluster_node_options | selectattr('utilization', 'defined') | list
        }}
      loop_control:
        loop_var: node_options

    ## Resource and operation defaults
    # RHEL 8.3, which is the oldest version supported by the role, supports
    # multiple sets of resources and resources operations defaults. Therefore,
    # we don't need any checks for pcs capabilities.
    - name: Configure resource defaults
      include_tasks: pcs-rsc-op-defaults.yml
      vars:
        operations: false
      loop: "{{ ha_cluster_resource_defaults.meta_attrs | d([]) }}"
      loop_control:
        index_var: defaults_set_index
        loop_var: defaults_set

    # RHEL 8.3, which is the oldest version supported by the role, supports
    # multiple sets of resources and resources operations defaults. Therefore,
    # we don't need any checks for pcs capabilities.
    - name: Configure resource operation defaults
      include_tasks: pcs-rsc-op-defaults.yml
      vars:
        operations: true
      loop: "{{
        ha_cluster_resource_operation_defaults.meta_attrs | d([]) }}"
      loop_control:
        index_var: defaults_set_index
        loop_var: defaults_set

    ## Resources
    - name: Extract primitive to bundle mapping
      set_fact:
        __ha_cluster_primitive_bundle_map: "{{
            ha_cluster_resource_bundles | selectattr('resource_id', 'defined')
            | list | items2dict(key_name='resource_id', value_name='id')
          }}"

    - name: Configure cluster bundle resources
      include_tasks: pcs-cib-resource-bundle.yml
      vars:
        resource_bundle: "{{ item }}"
      loop: "{{ ha_cluster_resource_bundles }}"

    - name: Configure cluster resources
      include_tasks: pcs-cib-resource-primitive.yml
      vars:
        resource: "{{ item }}"
        resource_is_stonith: "{{ item.agent.startswith('stonith:') }}"
      loop: "{{ ha_cluster_resource_primitives }}"

    - name: Configure cluster resource groups
      include_tasks: pcs-cib-resource-group.yml
      vars:
        resource_group: "{{ item }}"
      loop: "{{ ha_cluster_resource_groups }}"

    - name: Configure cluster resource clones
      include_tasks: pcs-cib-resource-clone.yml
      vars:
        resource_clone: "{{ item }}"
      loop: "{{ ha_cluster_resource_clones }}"

    ## Stonith levels
    - name: Configure stonith levels
      include_tasks: pcs-cib-stonith-level.yml
      loop: "{{ ha_cluster_stonith_levels }}"
      loop_control:
        index_var: stonith_level_index
        loop_var: stonith_level

    ## Constraints
    - name: Configure resource location constraints
      include_tasks: pcs-cib-constraint-location.yml
      loop: "{{ ha_cluster_constraints_location }}"
      loop_control:
        index_var: constraint_index
        loop_var: constraint

    - name: Configure resource colocation constraints
      include_tasks: pcs-cib-constraint-colocation.yml
      when: not constraint.resource_sets | d()
      loop: "{{ ha_cluster_constraints_colocation }}"
      loop_control:
        index_var: constraint_index
        loop_var: constraint

    - name: Configure resource set colocation constraints
      include_tasks: pcs-cib-constraint-set.yml
      vars:
        constraint_type: colocation
      when: constraint.resource_sets | d()
      loop: "{{ ha_cluster_constraints_colocation }}"
      loop_control:
        index_var: constraint_index
        loop_var: constraint

    - name: Configure resource order constraints
      include_tasks: pcs-cib-constraint-order.yml
      when: not constraint.resource_sets | d()
      loop: "{{ ha_cluster_constraints_order }}"
      loop_control:
        index_var: constraint_index
        loop_var: constraint

    - name: Configure resource set order constraints
      include_tasks: pcs-cib-constraint-set.yml
      vars:
        constraint_type: order
      when: constraint.resource_sets | d()
      loop: "{{ ha_cluster_constraints_order }}"
      loop_control:
        index_var: constraint_index
        loop_var: constraint

    - name: Configure resource ticket constraints
      include_tasks: pcs-cib-constraint-ticket.yml
      when: not constraint.resource_sets | d()
      loop: "{{ ha_cluster_constraints_ticket }}"
      loop_control:
        index_var: constraint_index
        loop_var: constraint

    - name: Configure resource set ticket constraints
      include_tasks: pcs-cib-constraint-set.yml
      vars:
        constraint_type: ticket
      when: constraint.resource_sets | d()
      loop: "{{ ha_cluster_constraints_ticket }}"
      loop_control:
        index_var: constraint_index
        loop_var: constraint

    - name: Configure acls
      include_tasks: pcs-cib-acls.yml
      vars:
        acls: "{{ ha_cluster_acls | d({}) }}"

    - name: Configure alerts
      include_tasks: pcs-cib-alerts.yml
      loop: "{{ ha_cluster_alerts | d([]) }}"
      loop_control:
        loop_var: alert

# Push the new CIB into the cluster

- name: Create a tempfile for CIB diff
  tempfile:
    state: file
    suffix: _ha_cluster_cib_diff
  register: __ha_cluster_tempfile_cib_diff
  check_mode: false
  changed_when: not ansible_check_mode

- name: Compare new and original CIB
  command:
    cmd: >
      crm_diff --no-version
      --original {{ __ha_cluster_tempfile_original_cib_xml.path }}
      --new {{ __ha_cluster_tempfile_cib_xml.path }}
  register: __ha_cluster_cib_diff
  check_mode: false
  changed_when: not ansible_check_mode
  failed_when:
    - __ha_cluster_cib_diff.rc != 0  # success, CIBs are the same
    - __ha_cluster_cib_diff.rc != 1  # success, CIBs are not the same

- name: Write CIB diff to its tempfile
  copy:
    content: "{{ __ha_cluster_cib_diff.stdout }}"
    dest: "{{ __ha_cluster_tempfile_cib_diff.path }}"
    owner: root
    group: root
    mode: "0600"
  check_mode: false
  changed_when: not ansible_check_mode
  when: __ha_cluster_cib_diff.rc == 1

- name: Push CIB diff to the cluster if it has any changes
  command:
    cmd: >
      cibadmin --verbose --patch
      --xml-file {{ __ha_cluster_tempfile_cib_diff.path | quote }}
  run_once: true  # noqa: run_once[task]
  changed_when: not ansible_check_mode
  when: __ha_cluster_cib_diff.rc == 1

- name: Remove CIB tempfiles
  file:
    path: "{{ item.path }}"
    state: absent
  loop:
    - "{{ __ha_cluster_tempfile_cib_xml }}"
    - "{{ __ha_cluster_tempfile_original_cib_xml }}"
    - "{{ __ha_cluster_tempfile_cib_diff }}"
  check_mode: false
  changed_when: not ansible_check_mode
