Chef: Uso De Condicionais not_if E only_if
Pare e volte uma casa
Se você nunca utilizou ou sabe pouco sobre o Chef é importante que você pare aqui mesmo e volte uma casa. Sugiro que leia o post Chef: Automação e Gerenciamento de Configuração antes de seguir este, uma vez que através dele você entenderá o que é, e como funciona o Chef, bem como sua instalação básica.
Resources
Conforme dito no post anterior, resource é uma descrição de estado que:
- Descreve o estado desejado para um item de configuração
- Declara os passos necessários para levar o item especificado ao estado desejado
- Especifica o tipo de resource - por exemplo,
package
,template
ouservice
- Lita detalhes adicionais (também conhecidos como propriedades de resources), conforme necessário
- São agrupados em recipes, que descrevem configurações em geral
Utilizando (Guardas) not_if e only_if
Todos os resources (incluindo os personalizados) no Chef compartilham um conjunto de opções comuns: ações, propriedades, condicionais, notificações e paths relativos.
Guards
Propriedades de guarda, ou guards
, como são chamadas no Chef, podem ser utilizadas para avaliar o estado de um node durante a fase de execução do chef-client. Esta avaliação funciona como uma condicional e, baseando-se nos resultados da mesma, a propriedade guard é então utilizada para indicar ao chef-client se ele deve ou não continuar a execução de um resource. Uma propriedade guard aceita tanto um valor string quanto um bloco de código Ruby.
- A string é executada como um comando shell. Caso o comando retorne
0
(true), a propriedade guard é aplicada. Caso o comando retorne qualquer outro valor a propriedade guard não será aplicada. - Um bloco é executado como um código Ruby que deve retornar
true
oufalse
. Da mesma forma, caso o comando retornetrue
, a propriedade guard é aplicada. Caso o comando retornefalse
a propriedade guard não será aplicada.
Uma guard é importante para garantir que um resource seja idempotente ao permitir um teste no próprio resource certificando-se de que o mesmo se encontra no estado desejado de forma que o chef-client não faça nada.
Atributos
Os seguintes atributos podem ser utilizados para definir uma guard que é avaliada durante a fase de execução do chef-client:
not_if
- Impede a execução de um resource quando a condição retornar true
.
only_if
- Permite a execução de um resource apenas quando a condição retornar true
.
Mãos à obra
Para simplificar seguirei utilizando a recipe utilizada no post anterior. Não sabe o que é uma recipe? ->
Novamente, caso não entenda o que estou dizendo, volte uma casa e leia o post anterior, no qual explico o que é uma recipe, bem como cada elemento da mesma, visto que a utilizaremos aqui.
Nossa recipe era a seguinte:
package 'apache' do
package_name 'httpd'
action :install
end
service 'httpd' do
action [:enable, :start]
end
file '/var/www/html/index.html' do
content 'Hello World!'
mode '0755'
owner 'root'
group 'apache'
end
Como primeira alteração vamos editar o arquivo /etc/motd
. Este arquivo nada mais é do que a definição de um baner que será apresentado sempre que alguém se logar em seu servidor.
Por padrão, este arquivo costuma vir vazio. Este é o caso da máquina utilizada para este exemplo:
[root@kalib6 ~]# cat /etc/motd
[root@kalib6 ~]#
Começaremos definindo o conteúdo que deverá existir em nosso arquivo /etc/motd
incluindo um resource do tipo file
no final de nossa recipe exemplo.rb
:
package 'apache' do
package_name 'httpd'
action :install
end
service 'httpd' do
action [:enable, :start]
end
file '/var/www/html/index.html' do
content 'Hello World!'
mode '0755'
owner 'root'
group 'apache'
end
file '/etc/motd' do
content 'Bem Vindo!'
end
Até aqui nenhuma novidade (caso você tenha lido de fato o post anterior ou já tenha utilizado Chef anteriormente), apenas incluímos mais um resource em nossa recipe exemplo.rb
informando que o arquivo /etc/motd
deve existir e que seu conteúdo deverá ser Bem Vindo!.
Validando e executando localmente nossa recipe conforme feito anteriormente veremos que todos os demais passos ou resources serão ignorados por já estarem estarem no estado desejado (idempotência), restando apenas a inclusão do conteúdo no /etc/motd
:
Validando
[root@kalib6 ~]# ruby -c exemplo.rb && foodcritic exemplo.rb
Syntax OK
Checking 1 files
x
FC011: Missing README in markdown format: ../README.md:1
FC031: Cookbook without metadata.rb file: ../metadata.rb:1
FC071: Missing LICENSE file: ../LICENSE:1
Executando
[root@kalib6 ~]# chef-client --local-mode exemplo.rb
[2018-04-15T22:03:26+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-15T22:03:26+00:00] WARN: No cookbooks directory found at or above current directory. Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-15T22:03:29+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 4 resources
Recipe: @recipe_files::/root/exemplo.rb
* yum_package[apache] action install (up to date)
* service[httpd] action enable (up to date)
* service[httpd] action start (up to date)
* file[/var/www/html/index.html] action create (up to date)
* file[/etc/motd] action create
- update content in file /etc/motd from e3b0c4 to f13843
--- /etc/motd 2018-04-15 20:51:00.411479476 +0000
+++ /etc/.chef-motd20180415-1681-1p1fm7m 2018-04-15 22:03:42.142091791 +0000
@@ -1 +1,2 @@
+Bem Vindo!
- restore selinux security context
Running handlers:
Running handlers complete
Chef Client finished, 1/5 resources updated in 15 seconds
Para termos certeza de que o nosso arquivo foi corretamente alterado…
[root@kalib6 ~]# cat /etc/motd
Bem Vindo!
Novamente, nenhuma novidade até aqui.
Vamos agora utilizar o tipo de resource execute
, o qual nos permite executar um comando a cada execução do chef-client. Vale lembrar que uma das características do Chef é a idempotência, portanto o execute
pode ser considerado uma exceção, já que o comando será executado sempre, mesmo que já tenha sido executado anteriormente. E é neste tipo de situação que os guards
se mostram importantes.
Comecemos inserindo um resource do tipo execute
em nossa recipe:
package 'apache' do
package_name 'httpd'
action :install
end
https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
service 'httpd' do
action [:enable, :start]
end
file '/var/www/html/index.html' do
content 'Hello World!'
mode '0755'
owner 'root'
group 'apache'
end
file '/etc/motd' do
content 'Bem Vindo!'
end
execute 'meu-comando' do
command 'echo " Obrigado!" >> /etc/motd'
only_if 'test -r /etc/motd'
end
O que adicionamos aqui?
- Resource Type: execute (Para que possamos executar um comando)
- Resource Name: meu-comando (Poderíamos ter utilizado qualquer nome)
- Command: ‘echo “ Obrigado!” >> /etc/motd’ (O comando que desejamos executar. Estou utilizando
echo
para inserir * Obrigado!* ao meu arquivo/etc/motd
) - Guard: only_if ‘test -r /etc/motd’ (
only_if
implica que meu resource meu-comando será executado apenas caso o resultado detest -r /etc/motd
seja positivo. test -r irá verificar se o arquivo/etc/motd
existe no sistema. Caso sim, meu comandoecho " Obrigado!" >> /etc/motd
será executado conforme planejado, do contrário será ignorado)
Nós já sabemos que o arquivo existe, portanto esperamos que * Obrigado!* seja adicionada ao mesmo após execução do chef-client.
Verificando nosso código
[root@kalib6 ~]# ruby -c exemplo.rb && foodcritic exemplo.rb
Syntax OK
Checking 1 files
x
FC011: Missing README in markdown format: ../README.md:1
FC031: Cookbook without metadata.rb file: ../metadata.rb:1
FC071: Missing LICENSE file: ../LICENSE:1
Tudo ok. Executando…
[root@kalib6 ~]# chef-client --local-mode exemplo.rb
[2018-04-15T23:08:54+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-15T23:08:54+00:00] WARN: No cookbooks directory found at or above current directory. Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-15T23:09:03+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 5 resources
Recipe: @recipe_files::/root/exemplo.rb
* yum_package[apache] action install (up to date)
* service[httpd] action enable (up to date)
* service[httpd] action start (up to date)
* file[/var/www/html/index.html] action create (up to date)
* file[/etc/motd] action create (up to date)
* execute[meu-comando] action run
- execute echo " Obrigado!" >> /etc/motd
Running handlers:
Running handlers complete
Chef Client finished, 1/6 resources updated in 31 seconds
Repare que o Chef executou o comando para inserir Obrigado!
.
[root@kalib6 ~]# cat /etc/motd
Bem Vindo! Obrigado!
Conforme dito anteriormente, o resource execute
irá executar o meu comando SEMPRE que o chef-client rodar. Vejamos o que acontece ao executar novamente o chef-client sem alterar a recipe.
[root@kalib6 ~]# chef-client --local-mode exemplo.rb
[2018-04-15T23:18:10+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-15T23:18:10+00:00] WARN: No cookbooks directory found at or above current directory. Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-15T23:18:20+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 5 resources
Recipe: @recipe_files::/root/exemplo.rb
* yum_package[apache] action install (up to date)
* service[httpd] action enable (up to date)
* service[httpd] action start (up to date)
* file[/var/www/html/index.html] action create (up to date)
* file[/etc/motd] action create
- update content in file /etc/motd from 201ddf to f13843
--- /etc/motd 2018-04-15 23:16:13.705005377 +0000
+++ /etc/.chef-motd20180415-2432-zhtjnq 2018-04-15 23:18:42.138379629 +0000
@@ -1,2 +1,2 @@
-Bem Vindo! Obrigado!
+Bem Vindo!
- restore selinux security context
* execute[meu-comando] action run
- execute echo " Obrigado!" >> /etc/motd
Running handlers:
Running handlers complete
Chef Client finished, 2/6 resources updated in 31 seconds
Como em nossa recipe temos o resource file
que determina o conteúdo do arquivo /etc/motd
como sendo Bem Vindo!
, o chef percebeu que o arquivo se encontrava diferente, pois acrescentamos o Obrigado!
no passo anterior. O arquivo foi corrigido, voltando a ter seu conteúdo original, em seguida o Chef inseriu novamente Obrigado!
, pois assim está determinado em nossa recipe.
É fácil perceber isto nas linhas a seguir, retiradas do resultado de nossa última execução do chef-client:
* file[/etc/motd] action create
- update content in file /etc/motd from 201ddf to f13843
--- /etc/motd 2018-04-15 23:16:13.705005377 +0000
+++ /etc/.chef-motd20180415-2432-zhtjnq 2018-04-15 23:18:42.138379629 +0000
@@ -1,2 +1,2 @@
-Bem Vindo! Obrigado!
+Bem Vindo!
- restore selinux security context
* execute[meu-comando] action run
- execute echo " Obrigado!" >> /etc/motd
Se verificarmos nosso arquivo, veremos que ele está da mesma forma:
[root@kalib6 ~]# cat /etc/motd
Bem Vindo! Obrigado!
Para percebermos a diferença, vamos comentar as linhas do resource file /etc/motd
:
...
#file '/etc/motd' do
# content 'Bem Vindo!'
#end
...
Ao comentar estas linhas, nossa recipe não mais indicará que o conteúdo do arquivo /etc/motd
deve ser Bem Vindo!
, portanto vejamos o que acontece quando executamos novamente o chef-client.
[root@kalib6 ~]# chef-client --local-mode exemplo.rb
[2018-04-15T23:28:32+00:00] WARN: No config file found or specified on command line, using command line options.
[2018-04-15T23:28:33+00:00] WARN: No cookbooks directory found at or above current directory. Assuming /root.
Starting Chef Client, version 13.8.5
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-04-15T23:28:42+00:00] WARN: Node kalib6.test.com has an empty run list.
Converging 4 resources
Recipe: @recipe_files::/root/exemplo.rb
* yum_package[apache] action install (up to date)
* service[httpd] action enable (up to date)
* service[httpd] action start (up to date)
* file[/var/www/html/index.html] action create (up to date)
* execute[meu-comando] action run
- execute echo " Obrigado!" >> /etc/motd
Running handlers:
Running handlers complete
Chef Client finished, 1/5 resources updated in 32 seconds
Uma vez que nosso arquivo /etc/motd
já possuía o conteúdo Bem Vindo! Obrigado!
e desta vez não tentou garantir que o conteúdo do mesmo fosse apenas Bem Vindo!
, vejamos como ele se encontra:
[root@kalib6 ~]# cat /etc/motd
Bem Vindo! Obrigado!
Obrigado!
Como esperado, inserimos mais um Obrigado!
ao arquivo. Se executarmos novamente o chef-client, estaremos acrescentando novamente um Obrigado!
ao arquivo, pois o mesmo existe e não estamos mais tentando validar seu conteúdo.
E se removermos manualmente o arquivo /etc/motd
?
[root@kalib6 ~]# rm /etc/motd
rm: remove regular file ‘/etc/motd’? y
[root@kalib6 ~]# cat /etc/motd
cat: /etc/motd: No such file or directory
Vejamos o que o chef-client fará:
... (Ignorando linhas desnecessárias)
* execute[meu-comando] action run (skipped due to only_if)
...
Como já tínhamos comentado as linhas que garantem que o /etc/motd
existe e possui o conteúdo Bem Vindo!
, o resource que incluiría Obrigado!
foi ignorado, pois em nossa guard temos a condição only_if
, que indica que o comando só será executado SE o arquivo /etc/motd existir. Para ter certeza disto, vamos verificar:
[root@kalib6 ~]# cat /etc/motd
cat: /etc/motd: No such file or directory
Aproveitando a situação, vamos alterar nossa recipe e utilizar not_if
ao invés de only_if
:
package 'apache' do
package_name 'httpd'
action :install
end
service 'httpd' do
action [:enable, :start]
end
file '/var/www/html/index.html' do
content 'Hello World!'
mode '0755'
owner 'root'
group 'apache'
end
#file '/etc/motd' do
# content 'Bem Vindo!'
#end
execute 'meu-comando' do
command 'echo " Obrigado!" >> /etc/motd'
not_if 'test -r /etc/motd'
end
Já sabemos que o arquivo não existe, portanto a nossa regra agora será satisfeita, visto que queremos adicionar o conteúdo Obrigado!
APENAS caso o arquivo NÃO exista.
Vale lembrar que, por padrão, o comando echo ' Obrigado!' >> /etc/motd
irá adicionar o conteúdo ao arquivo e, caso o mesmo não exista, ele será criado com o determinado conteúdo. Isto não é um recurso do Chef, mas sim do próprio linux/echo. Executando nosso chef-client:
... (Ignorando linhas desnecessárias)
* execute[meu-comando] action run
- execute echo " Obrigado!" >> /etc/motd
Running handlers:
Running handlers complete
Repare que o arquivo foi criado apenas com o conteúdo Obrigado!
, conforme descrito em nossa recipe.
[root@kalib6 ~]# cat /etc/motd
Obrigado!
Caso o chef-client seja executado novamente, nada acontecerá, uma vez que o resource atual indica que o comando APENAS deverá ser executado caso o arquivo NÃO exista.
... (Ignorando linhas desnecessárias)
* execute[meu-comando] action run (skipped due to not_if)
...
Simples, certo?! É importante lembrar que o Chef executará os resources na devida ordem em que forem listados na recipe, portanto é importante alinhar todas as instruções e resources de acordo com o resultado desejado.
Happy hacking!