ANSIBLE SERIES: h.t.wrt* … tasks, plays e books: building loops
CONT. ☝
👈 ANTERIOR: BECOME
O que fazer quando é necessário repetir determinada ação mais de uma vez ??? Automatizar, é claro, você diria … Tudo bem, já entendi que pegou o espírito da coisa 🙃 E fico deveras feliz por isso, diga-se de passagem! Mas, o que eu quis dizer é como fazê-lo em pormenores? Como executar valendo-se da lógica da programação? Há duas respostas válidas, contudo uma é mais inteligente e menos trabalhosa, e a outra é mais lenta (para não desmerecê-la 😬 haha) e braçal. A primeira delas seria utilizar um monte de IF’s e ELSE’s para cada entrada, interação ou condição. Em contrapartida, a segunda seria criar um laço (daí seu nome loop, em inglês) com uma variável “contador” interna. Essa controlaria a execução do mesmo em N vezes, e até que a condição X fosse satisfeita. Resultado? Um código mais bonito, limpo, enxuto, com menos linhas, e mais otimizado.
No contexto do Ansible, ou de qualquer outra ferramenta de automação, tais ações “repetitivas” abrangeriam desde adicionar vários usuários, replicar uma configuração em um pool de máquinas, até mudar a propriedade/permissão (ou dono) de uma série de arquivos e pastas, por exemplo. Para tal o ansible nos fornece duas palavras-chave: loop
e with_<lookup>
(lookup em português pode ser traduzido como busca, pesquisa, chave ou valor).
Antes de expor e debater os tópicos a seguir, cabe aqui, e quero destacar algumas notas acerca do recurso loop
:
- Foi introduzido a partir do Ansible 2.5
- Está presente em todas as versões sucessoras (2.5+)
- Ainda não substitui completamente o
with_<lookup>
, embora a ideia seja exatamente essa no futuro. - Na maioria dos casos recomenda-se, e satisfaz muito bem, o uso do
loop
. Somente em situações extremamente específicas owith_<lookup>
dá pleno suporte, o que acaba “vencendo” seu concorrente amigo, e portanto o uso dele é o único meio viável para solucionar. - Não está obsoleta ou tão pouco depreciada a sintaxe do
with_lookup
, continuando assim totalmente válida, embora isso possa mudar no médio-longo prazo. - O
loop
encontra-se em constante mudança visando principalmente seu aperfeiçoamento. Para atualizações e alterações do mesmo, por favor consultar o CHANGELOG https://github.com/ansible/ansible/tree/devel/changelogs
02-a. Comparativo entre loop e with_*
As únicas palavras-chave capazes de complementar o with_
são aquelas que estão disponíveis em https://docs.ansible.com/ansible/latest/plugins/lookup.html#lookup-plugins Ou seja, na prática, qualquer uma delas quando utilizadas correspondem ao asterisco (presente no título desta sessão). Para refrescar a memória, * é conhecido também como o caractere-curinga na computação, especificamente quando falamos em interpretadores de comandos nos sistemas operacionais.
A palavra loop
é o mesmo que usar a expressão with_list
, sendo a primeira uma melhor escolha para loops que não possuem tanta complexidade. Porém, o loop
não aceita como entrada nenhuma ‘string’. Veja mais em https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#query-vs-lookup
Genericamente, e simplificando bastante, qualquer with_*
pode facilmente ser trocado por loop
. Claro que salva as devidas proporções e ressalvas, todas essas descritas aqui https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#migrating-to-loop
02-b. Loops Tradicionais (ou padrão/modelos)
! ! ! Interagindo sobre uma lista simples ! ! !
Se uma task precisa ser executada mais de uma vez em determinado host, recomenda-se usar como padrão o próprio loop, assim este agiria de acordo e sobre uma lista de strings, por exemplo. Em outras palavras, defina a lista diretamente, de uma vez só e já estando dentro da task em questão:
- name: Add users and give them root powers ansible.builtin.user: name: "{{item
}}" state: present group: "wheel"loop:
- victor - viktor - vitor - vicrlda
O código acima ☝ seria equivalente ao de baixo 👇 (só que mais elegante! e com menos tec-tec-tec-tec 🔉💻💬)
- name: Add user vitor ansible.builtin.user: name: "vitor
" state: present groups: "wheel" - name: Add user vicrlda ansible.builtin.user: name: "vicrlda
" state: present groups: "wheel". .
. .
! ! ! Interagindo sobre uma lista de hashes ! ! !
Caso tenha uma lista de hashes para trabalhar, utilize referências no formato de chave:subchave
dentro do loop.
- name: Add multiple users ansible.builtin.user: name: "{{item.name
}}" state: present groups: "{{item.groups
}}" loop: - { name:'victor'
, groups:'wheel'
} - { name:'viktor'
, groups:'root'
}
! ! ! Interagindo sobre um dictionary ! ! !
Neste caso, simplesmente use o dict2items
https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#dict-filter :
- name: Using dict2items ansible.builtin.debug: msg:"{{ item.key }} - {{ item.value }}"
loop:"{{ tag_data | dict2items }}"
vars: tag_data: Environment:dev
Application:payment
02-c. Registrando variáveis com um loop
Esse artifício é comumente usado na lógica da programação, em termos genéricos, e desde sempre, remontando ao seu início e concepção. Naturalmente, por tabela, também é observado nas linguagens. Em outras palavras, o que temos é, basicamente um evento partilhado ocorrendo tanto no macro quanto no micro ecossistema. Respeitando, é claro, as particularidades de cada um (limitações, capacidades e sintaxe).
E com os playbooks não poderia ser diferente. Somos capazes de armazenar a saída de um loop dentro de uma variável. Por exemplo,
- name: Register loop output as a variable ansible.builtin.shell: "echo {{item
}}" loop: -"apple"
-"banana"
register: echo
Loops que vem logo após a variável registrada, com o intuito de inspecionar eventuais resultados, talvez se assemelhem ao trecho a seguir:
- name: Fail if return code is not 0 ansible.builtin.fail: msg: "The command ({{item.cmd
}}) did not have a 0 return code" when: item.rc != 0 loop: "{{echo.results
}}"
Por padrão, o comportamento da variável durante a execução/interação do loop é sempre armazenar o valor do item atual, indiferente se são números, strings ou caracteres.
- name: Place the result of the current item in the variable ansible.builtin.shell: echo "{{item
}}" loop: -one
-two
register: echo changed_when:echo.stdout != "one"
02-d. Loops Complexos
! ! ! LISTAS ANINHADAS ! ! !
Aninhado(a), conceitualmente restrito ao universo da programação, implica dizer que um dado elemento está contido em outro elemento. Se preferir mais sinônimos, seria o mesmo que alojado, hospedado, instalado. Isso compreende desde funções, sub-rotinas, até vetores e matrizes. Exemplo: um vetor de tamanho três [0, 1, 2] cujas posições na memória são na verdade ponteiros para outros três vetores distintos. Talvez o mais importante aqui, e que vale destacar, é o tamanho da jurisprudência do primeiro ou o limite imposto ao segundo … ❓❓❓ Certo, certo, eu explico. Vamos imaginar que os sujeitos (elementos) em questão sejam rotinas e sub-rotinas. Então, partindo do pressuposto que apresentei, uma sub-rotina “filha” somente poderia ser chamada para rodar durante o programa única e exclusivamente por sua rotina “mãe”. Caso uma rotina “madrasta”, por exemplo, tentasse invocar essa mesma sub-rotina “filha”, não haveria êxito algum. Isso porque, de antemão, a primeira (madrasta) nunca iria enxergar a segunda (filha) pois ela apenas fica visível para a terceira (mãe). Isso é o que determinados autores chamam de “fazer sentido localmente”, e que é considerada uma forma de encapsulamento.
Ufa! 🥴 Agora sim, voltando ao contexto dos playbooks, e finalizando esse tópico antes de passar para o próximo (…)
Em situações de listas aninhadas recomenda-se o uso de expressões Jinja2
para melhor tratá-las. Como segue abaixo, um loop que combina várias listas:
- name: Give users access to multiple databases community.mysql.mysql_user: name: "{{item[0]
}}" priv: "{{item[1]
}}.*:ALL" append_privs: yes password: "foo" loop: "{{ ['alice'
,'bob'
] |product
(['clientdb'
,'employeedb'
,'providerdb'
])|list
}}"
! ! ! INVENTÁRIOS ! ! !
Mais uma opção para uso dos loops no Ansible ocorre quando se trata de arquivos de inventário. Isso nos possibilita varrê-los ou construí-los sem sair do laço. E melhor, não é obrigatório usar todo o arquivo e sim apenas uma parte dele, caso prefira assim. Para tal, basta informar a palavra-chave loop
com a variável ansible_play_batch
ou a variável groups
- name: Show all the hosts in the inventory ansible.builtin.debug: msg: "{{item
}}" loop: "{{groups
['all'
] }}" - name: Show all the hosts in the current play ansible.builtin.debug: msg: "{{item
}}" loop: "{{ansible_play_batch
}}"
02-e. Adicionando controles aos loops
(A partir da versão 2.1+
)
(Palavra-chave: loop_control
)
! ! ! LIMITANDO A TELA/SAÍDA DO LOOP ! ! !
Para estruturas de dados muito complexas, executar um laço pode ser algo extenso, e consequentemente, a saída gerada (console) se torna igualmente enorme. Evite dores de cabeça e olhos cansados utilizando a diretriz label
com loop_control
:
- name: Create servers digital_ocean: name: "{{item.name
}}" state: present loop: - name: server1 disks: 3gb ram: 15Gb network: nic01: 100Gb nic02: 10Gb ...loop_control
:label
: "{{item.name
}}"
! ! ! Pausa dentro de um loop ! ! !
Controle o tempo, aqui medido em segundos, entre a execução de cada item dentro de uma loop task (tarefa repetitiva). Use a diretriz pause
com a palavra loop_control
# main.yml
- name: Create servers, pause 3s before creating next community.digitalocean.digital_ocean: name: "{{item
}}" state: present loop: - server1 - server2loop_control
:pause
: 3
! ! ! Definindo nomes internos e externos ao loop com loop_var ! ! !
Você pode aninhar duas tarefas de loop usando include_tasks
. No entanto, por padrão, Ansible define o item de variável de loop para cada loop. Isso significa que o loop interno aninhado sobrescreverá o valor do item do loop externo. Você pode especificar o nome da variável para cada loop usando loop_var
com loop_control
:
# main.yml
-include_tasks
:inner.yml
loop:- 1 - 2 - 3
loop_control:loop_var
: outer_item# inner.yml
- name: Print outer and inner items ansible.builtin.debug: msg: "outer item={{outer_item
}} inner item={{item
}}" loop:- a - b - c
CONTINUA (…)
Próximo post >>> WHERE TASKS RUN?
REFERÊNCIAS:
https://docs.ansible.com/core.html
https://docs.ansible.com/ansible/latest/user_guide/index.html
https://docs.ansible.com/ansible/latest/user_guide/index.html#writing-tasks-plays-and-playbooks
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#playbooks-loops