We cover Ansible IaC playbooks in terms of providing access

Infrastructure as code (Infrastructure-as-Code; Iac) is an approach for managing and describing data center infrastructure through configuration files, rather than through manually editing configurations on servers or interacting interactively.

So, we have already learned how to quickly raise infrastructure using IaC, we have created a bunch of repositories and are ready to restore the damaged or broken infrastructure in terms of service readiness. The next step might be to consider granting access to resources and documenting the provision of such access. This approach will not only allow you to quickly find on what basis Vasya dropped the sign on the product, but also speed up the provision of access and reduce the number of errors.

I will show my vision of the structure of such an ansible role (hereinafter referred to as the role).

Let's start with tasks/main.yml:

---
# Проверка корректности конфигурационных файлов описанных в IaC
# Проверки корректности и имен и взаимосвязей 
- name: Check
  include_tasks:
    file: check.yml
  tags: always
  run_once: true

# Установка на целевой хост пакетов необходимых для работы 
# ansible-модулей 

- name: installing the required packages
  apt:
    name: "{{ item }}"
  loop:
    - required_package_1
    - required_package_2

# Получение имени последнего коммита.
# Его можно использовать в качестве темы письма, а также
# проводить различные проверки на его содержание.

- name: get latest commit message
  shell: git log --pretty="%s" -n1
  register: latest_commit_message
  tags: always
  run_once: true
  delegate_to: localhost
  changed_when: false

# Сбор фактов о существующих сущностях на которые выдаются права 
# и правах предоставленных пользователям

- name: Collect info about entities, roles
  become: true
  become_user: postgres
  community.postgresql.postgresql_info:
    login_unix_socket: '{{ pg_login_unix_socket }}'
    db: postgres
    filter:
      - "databases"
      - "roles"
  register: "pg_exist"
# Тегирование -  важный элемент формирования роли. 
# Позволит нам изорировать работу с различным видом сущностей.
  tags:
    - pg_db_config
    - pg_roles_config
  run_once: true

# Последовательная работа с ролями

- name: Work with roles
  include_tasks:
    file: pg_roles.yml
    apply:
      tags: pg_roles_config
  when: pg_roles_config
  tags: pg_roles_config
  run_once: true

# работа с сущностями

- name: Work with db
  include_tasks:
    file: pg_db.yml
    apply:
      tags: pg_db_config
  when: pg_db_config
  tags: pg_db_config
  run_once: true

# работа с дополнительными механизмами, обеспечмвающими доступ 

- name: Work with pg_hba
  include_tasks:
    file: pg_hba.yml
    apply:
      tags: pg_hba_config
  when: pg_hba_config
  tags: pg_hba_config
  # run_once: true

# Вывод ошибок полученных в рамках работ, естественно красным цветом

- name: Fail message
  debug:
    msg: "Не все конфиги были применены \n{{ rescue_msgs|to_nice_yaml }}"
  failed_when: true
  when: rescue_msgs != []
  tags: always
  run_once: true

Here is an example of collecting facts using a ready-made module (line 35), however, if there is none, you will have to work with shell and zhinja filters. After running a command with the command/shell module, you can store the output in a variable using the register parameter. This will allow you to use the output in further playbook tasks. Ansible allows you to use Jinja2 filters to process the stored output. Filters allow you to parse strings, extract substrings, split text into individual elements, and much more.

- name: Run a shell command and parse the output using regex
  hosts: your_target_hosts
  tasks:
    - name: Execute the shell command
      command: your_shell_command
      register: shell_output

    - name: Process the output using regex
      debug:
        msg: "{{ shell_output.stdout | regex_search('pattern') }}"

When collecting facts, we form dictionary lists identical to those described in IaC. That is, for subsequent comparison we must obtain pairs:

pg_db_exist # существующие сущности
pg_db_iac # сущности, описанные в IaC

Using the example of working with databases, we will consider the procedure for working with entities.

---
# Формирование идентичного словаря
- name: Parse db from output
  set_fact:
    pg_db_exist: |
      {{pg_db_exist|combine({item.key: {'access_priv': item.value.access_priv|regex_replace("/.*\n","/")|split("/")|map('regex_search', '.*=.*')|select('string')|list, 'owner': item.value.owner}})}}
  loop: "{{ pg_exist.databases|dict2items }}"
  when: pg_exist.databases != {}

# Формирование словаря на сущностей для создания
- set_fact:
    db_dict_to_create: '{{ db_dict_to_create | combine({ item.key : item.value })}}'
  loop: "{{ pg_db_iac|dict2items }}"
  when: item.key not in pg_db_exist

# Формирование словаря на сущностей для изменения
- set_fact:
    db_dict_to_alter: '{{ db_dict_to_alter | combine({ item.key : item.value })}}'
  loop: "{{ pg_db_iac|dict2items }}"
  when: item.key in pg_db_exist and item.value != pg_db_exist[item.key]

# Формирование словаря на сущностей для удаления
- set_fact:
    db_dict_to_delete: '{{ db_dict_to_delete | combine({ item.key : item.value })}}'
  loop: "{{ pg_db_exist|dict2items }}"
  when: item.key not in pg_db_iac

# Защита от автоматического удаления сущностей на проде
- name: Verify required db
  assert:
    that: db_dict_to_delete == {}
    fail_msg: "You need to delete databases. Set db_auto_delete=true"
  when: not db_auto_delete
# Вывод информации о том, что было, что будет, чем сердце успокоится
# он поможет нам не только отлаживаться в рамках рутинной работы, 
# но и сформировать первичный словарь/список, который будет внесен в IaC
- debug:
    msg: "pg_db_exist\n {{ pg_db_exist|to_yaml }}\n\n pg_db_iac\n {{ pg_db_iac|to_yaml }}\n\n/
  {%- if db_dict_to_alter != {} -%}DB will be altered\n{{ db_dict_to_alter|to_yaml }}\n\n{% endif %}
  {%- if db_dict_to_create != {} -%}DB will be created\n{{ db_dict_to_create|to_yaml }}\n\n{% endif %}
  {%- if db_dict_to_delete != {} -%}DB will be deleted\n{{ db_dict_to_delete|to_yaml }}\n\n{% endif %}"

# Сами действия с сущностями
- name: Delete databases
  include: pg_db_action.yml
  vars:
    pg_db_action: 'absent'
  loop: "{{ db_dict_to_delete|dict2items }}"
  when: db_auto_delete and db_dict_to_delete != {}

- name: Create databases
  include: pg_db_action.yml
  vars:
    pg_db_action: 'present'
    pg_db_create: true
    loop_access_priv: "{{ item.value.access_priv }}"
  loop: "{{ db_dict_to_create|dict2items }}"
  when: db_dict_to_create != {}

- name: Update databases
  include: pg_db_action.yml
  vars:
    pg_db_action: 'present'
    pg_db_update: "{{ pg_db_exist }}"
    access_priv_out: "{{ pg_db_exist[item.key].access_priv | difference(item.value.access_priv) }}"
    access_priv_in: "{{ item.value.access_priv | difference(pg_db_exist[item.key].access_priv) }}"
  loop: "{{ db_dict_to_alter|dict2items }}"
  when: db_dict_to_alter != {}

Generating debugging information is an important stage that will help you understand why the scripts did not work and where the error crept in. It’s clear that the more code we cover with such processing, the more debugging information we get. Let's look at an example.

# Работаем с блоками ansible
- name: Block
  block:
    - name: Action {{ pg_db_action }} a database with name {{ item.key }}
      become: true
      become_user: postgres
      community.postgresql.postgresql_db:
        login_unix_socket: '{{ pg_login_unix_socket }}'
        name: "{{ item.key }}"
        state: "{{ pg_db_action }}"
        owner: "{{ item.value.owner }}"
      register: actionresult
# В случае неудачи мы не падаем  и выходим из роли, 
# а продолжаем выполнение, записав ошибку в список ошибок,
# который мы выводим в конце роли 
  rescue:
    - set_fact:
        rescue_msgs: "{{ rescue_msgs|default([]) + 
        [ 'Action '+pg_db_action+' a database with name ' 
        + item.key+' unsucsessful because '+actionresult.msg] }}"

I hope that this article will help structure your thoughts and design your infrastructure.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *