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.