Usage as a role dependency

The debops.postfix role can be used as a dependency by other Ansible roles to manage contents of the /etc/postfix/main.cf and /etc/postfix/master.cf configuration files idempotently. Configuration options from multiple roles can be merged together and included in the Postfix configuration, or removed conditionally.

Dependent role variable

The role exposes the postfix__dependent_maincf and postfix__dependent_mastercf variables which can be used to define Postfix configuration options and services by other Ansible roles through the role dependent variables.

The variables are an YAML lists with YAML dictionaries as entries. A short format of the configuration uses the dictionary key as a name of the dependent role and dictionary value as that role's configuration, in the format defined by Default variable details: postfix__maincf and Default variable details: postfix__mastercf variables, respectively (see playbook excerpt below):

roles:

  - role: debops.postfix
    postfix__dependent_maincf:
      - role_name: '{{ role_name__postfix__dependent_maincf }}'
    postfix__dependent_mastercf:
      - role_name: '{{ role_name__postfix__dependent_mastercf }}'

The extended version of the configuration uses YAML dictionaries with specific parameters:

role

Required. Name of the role, used to save its configuration in a YAML dictionary on the Ansible Controller. Shouldn't be changed once selected, otherwise the configuration will be desynchronized.

config

Required. YAML list with configuration of the Postfix options and services in the same format defined by Default variable details: postfix__maincf and Default variable details: postfix__mastercf variables.

state

Optional. If not specified or present, the configuration will be included in the generated configuration files. If absent, the configuration will be removed from the configuration files. If ignore, a given configuration entries will be skipped during data evaluation and won't affect any existing entries.

An example extended configuration (playbook excerpt):

roles:

  - role: debops.postfix
    postfix__dependent_maincf:
      - role: 'role_name'
        config: '{{ role_name__postfix__dependent_maincf }}'
    postfix__dependent_mastercf:
      - role: 'role_name'
        config: '{{ role_name__postfix__dependent_mastercf }}'

The above configuration layout allows for use of the multiple role dependencies in one playbook by providing configuration of each role in a separate configuration entry.

Dependent configuration storage and retrieval

The dependent configuration from other roles is stored in the secret/ directory on the Ansible Controller (see debops.secret for more details) in a JSON file (one for each variable), with each role configuration in a separate dictionary. The debops.postfix role reads these files when Ansible local facts indicate that the Postfix is installed, otherwise empty files are created. This ensures that the stale configuration is not present on a new or re-installed host.

The YAML dictionaries from different roles are merged with the main configuration in the postfix__combined_maincf and postfix__combined_mastercf variables that are used to generate the final configuration. The merge order of the different postfix__*_maincf and postfix__*_mastercf variables allows to further affect the dependent configuration through Ansible inventory if necessary, therefore the Ansible roles that use this method don't need to provide additional variables for this purpose themselves.

Example role default variables

---

# This is a set of default variables in an example 'application' role that uses
# dependent variables to pass configuration to 'debops.postfix' role.


# Additional APT packages to install for Postfix
application__postfix__dependent_packages:
  - 'postfix-pgsql'


# Postfix main.cf configuration
application__postfix__dependent_maincf:

  - name: 'application_destination_recipient_limit'
    value: 1


# Postfix master.cf configuration
application__postfix__dependent_mastercf:

  - name: 'application'
    type: 'unix'
    unpriv: False
    chroot: False
    command: 'pipe'
    args: |
      flags=FR user=application argv=/usr/local/lib/application/bin/in-pipe
      ${nexthop} ${user}

Example role playbook

---

# This is a playbook for an example 'application' role which uses
# 'debops.postfix' as a dependency and passes its own set of
# configuration options to it.

- name: Manage application
  collections: [ 'debops.debops' ]
  hosts: [ 'debops_service_application' ]
  become: True

  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'

  pre_tasks:

    # This role along with 'debops.etc_aliases' can be used to maintain the
    # /etc/aliases database.
    #
    #- name: Prepare etc_aliases environment
    #  import_role:
    #    name: 'etc_aliases'
    #    tasks_from: 'main_env'
    #  tags: [ 'role::etc_aliases', 'role::secret', 'role::postfix' ]

    - name: Prepare postfix environment
      ansible.builtin.import_role:
        name: 'postfix'
        tasks_from: 'main_env'
      tags: [ 'role::postfix', 'role::secret', 'role::ferm' ]

  roles:

    - role: secret
      tags: [ 'role::secret', 'role::postfix' ]
      secret__directories:
        - '{{ postfix__secret__directories }}'

    # Normally a 'debops.ferm' role would be here for 'debops.postfix'
    # to manage the firewall. You don't need it if you run the main
    # 'debops.postfix' playbook before yours.
    #
    #- role: ferm
    #  tags: [ 'role::ferm', 'skip::ferm' ]
    #  ferm__dependent_rules:
    #    - '{{ etc_aliases__secret__directories }}'
    #    - '{{ postfix__ferm__dependent_rules }}'

    #- role: etc_aliases
    #  tags: [ 'role::etc_aliases' ]

    - role: postfix
      tags: [ 'role::postfix' ]

      postfix__dependent_packages:
        - '{{ application__postfix__dependent_packages }}'

      postfix__dependent_maincf:

        # Short form of dependent configuration
        - application: '{{ application__postfix__dependent_maincf }}'

      postfix__dependent_mastercf:

        # Expanded form of dependent configuration
        - role: 'application'
          config: '{{ application__postfix__dependent_mastercf }}'
          state: 'present'

    - role: application
      tags: [ 'role::application' ]