Boa tarde, amigos. Às vésperas do início de uma nova turma do curso compartilhamos com vocês uma nova tradução. Vamos lá.

O uso do Pulumi e de linguagens de programação de propósito geral para código de infraestrutura (Infrastructure as Code) traz muitas vantagens: a posse de habilidades e conhecimentos, a eliminação de boilerplate no código através da abstração, e ferramentas familiares para sua equipe, como IDEs e linters. Todas essas ferramentas de engenharia de software não apenas nos tornam mais produtivos, mas também melhoram a qualidade do código. Portanto, é bastante natural que o uso de linguagens de programação de propósito geral permita a implementação de mais uma prática importante no desenvolvimento de software — testes.
Neste artigo, vamos explorar como o Pulumi ajuda a testar nossa "infraestrutura como código".

Por que testar a infraestrutura?
Antes de entrar em detalhes, vale a pena perguntar: "Por que devemos testar a infraestrutura?" Há muitas razões e aqui estão algumas delas:
- Teste modular de funções individuais ou fragmentos da lógica do seu programa.
- Verificação se o estado desejado da infraestrutura atende a certas restrições.
- Detecção de erros comuns, como a ausência de criptografia em storage buckets ou acesso não seguro e aberto à Internet para máquinas virtuais.
- Verificação da execução do provisionamento da infraestrutura.
- Realização de testes em tempo de execução da lógica do aplicativo, executado dentro da sua infraestrutura "programada", para verificar a funcionalidade após o provisionamento.
- Como vemos, há uma ampla gama de opções para testar a infraestrutura. O Pulumi possui mecanismos para testes em cada ponto desse espectro. Vamos começar e ver como isso funciona.
Teste modular
Programas Pulumi são criados em linguagens de programação de propósito geral, como JavaScript, Python, TypeScript ou Go. Portanto, eles têm acesso a todo o poder dessas linguagens, incluindo suas ferramentas e bibliotecas, e também frameworks de teste. O Pulumi é multi-nuvem, o que significa que é possível usar qualquer provedor de nuvem para os testes.
(Neste artigo, apesar da multilinguagem e multi-nuvem, usamos JavaScript e Mocha e nos concentramos na AWS. Python também pode ser utilizado) unittest, framework de teste Go ou qualquer outro framework de sua preferência. E, claro, o Pulumi funciona perfeitamente com Azure, Google Cloud, Kubernetes.)
Como vimos, há várias razões pelas quais pode ser necessário testar seu código de infraestrutura. Uma delas é o teste de unidade comum. Como seu código pode ter funções — por exemplo, para calcular CIDR, calcular nomes dinâmicos, tags, etc. — você provavelmente vai querer testá-las. Isso é semelhante a escrever testes de unidade comuns para aplicativos na sua linguagem de programação preferida.
Se quisermos adicionar um pouco mais de complexidade, podemos verificar como seu programa distribui recursos. Para ilustrar, vamos imaginar que precisamos criar um servidor EC2 simples e queremos garantir que o seguinte:
- As instâncias têm uma tag
Nome. - As instâncias não devem usar script inline
userData— devemos usar AMI (imagem). - Não deve haver SSH aberto na Internet.
Este exemplo foi inspirado :
index.js:
"use strict";
let aws = require("@pulumi/aws");
let group = new aws.ec2.SecurityGroup("web-secgrp", {
ingress: [
{ protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
],
});
let userData =
`#!\/bin\/bash
echo "Hello, World!" > index.html
nohup python -m SimpleHTTPServer 80 &`;
let server = new aws.ec2.Instance("web-server-www", {
instanceType: "t2.micro",
securityGroups: [ group.name ], \/\/ referência ao objeto do grupo acima
ami: "ami-c55673a0" \/\/ AMI para us-east-2 (Ohio),
userData: userData \/\/ iniciar um servidor web simples
});
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;
Este é um programa básico do Pulumi: ele simplesmente aloca um grupo de segurança EC2 e uma instância. No entanto, vale a pena notar que aqui estamos violando todas as três regras mencionadas acima. Vamos escrever testes!
Escrevendo testes
A estrutura geral de nossos testes será semelhante aos testes Mocha comuns:
ec2tests.js
test.js:
let assert = require("assert");
let mocha = require("mocha");
let pulumi = require("@pulumi/pulumi");
let infra = require("./index");
describe("Infrastructure", function() {
let server = infra.server;
describe("#server", function() {
\/\/ TODO(check 1): Deve haver uma tag Name.
\/\/ TODO(check 2): Não deve haver script inline userData.
});
let group = infra.group;
describe("#group", function() {
\/\/ TODO(check 3): Não deve haver SSH aberto na Internet.
});
}); Agora vamos escrever nosso primeiro teste: garantir que as instâncias tenham uma tag Nome. Para verificar isso, simplesmente obtemos o objeto da instância EC2 e verificamos a propriedade correspondente tags:
// check 1: Должен быть тэг Name.
it("must have a name tag", function(done) {
pulumi.all([server.urn, server.tags]).apply(([urn, tags]) => {
if (!tags || !tags["Name"]) {
done(new Error(`Missing a name tag on server ${urn}`));
} else {
done();
}
});
});Parece um teste comum, mas com algumas características que merecem atenção:
- Como solicitamos o estado do recurso antes da implantação, nossos testes sempre são executados no modo "consulta" (ou "preview"). Assim, existem muitas propriedades cujos valores simplesmente não serão obtidos ou estarão indefinidos. Isso inclui todas as propriedades de saída calculadas pelo seu provedor de nuvem. Para nossos testes, isso está bom — verificamos apenas as entradas. Voltaremos a essa questão mais tarde, quando falarmos sobre testes de integração.
- Como todas as propriedades dos recursos Pulumi são "saídas", e muitas delas são calculadas de forma assíncrona, precisamos usar o método apply para acessar os valores. Isso é muito parecido com promessas e funções.
then. - Como usamos várias propriedades para mostrar o URN do recurso na mensagem de erro, precisamos utilizar a função
pulumi.all, para combiná-las. - Por fim, como esses valores são calculados de forma assíncrona, precisamos usar a capacidade assíncrona integrada do Mocha com callback
doneou retorno de promessa.
Depois de configurar tudo, teremos acesso aos dados de entrada como simples valores JavaScript. A propriedade tags é um mapa (array associativo), portanto, precisamos apenas garantir que (1) não seja false e (2) exista uma chave para Nome. É muito simples e agora podemos verificar qualquer coisa!
Agora vamos escrever nossa segunda verificação. É ainda mais simples:
// check 2: Не должно быть inline-скрипта userData.
it("must not use userData (use an AMI instead)", function(done) {
pulumi.all([server.urn, server.userData]).apply(([urn, userData]) => {
if (userData) {
done(new Error(`Illegal use of userData on server ${urn}`));
} else {
done();
}
});
});E, finalmente, vamos escrever o terceiro teste. Isso será um pouco mais complicado, porque estamos procurando regras de entrada relacionadas ao grupo de segurança, que podem ser muitas, e intervalos CIDR dentro dessas regras, que também podem ser muitos. Mas conseguimos:
// check 3: Не должно быть SSH, открытого в Интернет.
it("must not open port 22 (SSH) to the Internet", function(done) {
pulumi.all([ group.urn, group.ingress ]).apply(([ urn, ingress ]) => {
if (ingress.find(rule =>
rule.fromPort == 22 && rule.cidrBlocks.find(block =>
block === "0.0.0.0/0"))) {
done(new Error(`Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group ${urn}`));
} else {
done();
}
});
});Isso é tudo. Agora vamos rodar os testes!
Executando testes
Rodar testes, na maioria das vezes, pode ser feito da maneira habitual, usando o framework de teste de sua escolha. Mas há uma particularidade do Pulumi que merece destaque.
Normalmente, para executar programas Pulumi usa-se o pulumi CLI (Interface de Linha de Comando), que configura o runtime da linguagem, controla as execuções do motor Pulumi para que seja possível registrar operações com recursos e incluí-las no plano, etc. No entanto, há um problema. Ao operar sob o controle do seu framework de teste, não haverá conexão entre o CLI e o motor Pulumi.
Para contornar esse problema, precisamos especificar o seguinte:
- O nome do projeto, que está contido na variável de ambiente
PULUMI_NODEJS_PROJECT(ou, de forma mais geral,PULUMI__PROJECT para outras linguagens).
O nome da pilha, que é especificado na variável de ambientePULUMI_NODEJS_STACK(ou, de forma mais geral,PULUMI__STACK).
Suas variáveis de configuração da pilha. Elas podem ser obtidas através da variável de ambientePULUMI_CONFIGe seu formato é um mapa JSON com pares chave/valor.O programa emitirá avisos indicando que durante a execução não há conexão com o CLI/motor. Isso é importante porque, na verdade, seu programa não estará realizando nenhum deploy, e isso pode ser uma surpresa se não for isso que você pretendia! Para informar ao Pulumi que é isso que você deseja, você pode definir
PULUMI_TEST_MODEemverdadeiro.Imagine que precisamos especificar o nome do projeto como
my-ws, o nome da pilhadev, e a região da AWSus-west-2.O comando para executar os testes Mocha seria o seguinte:$ PULUMI_TEST_MODE=true PULUMI_NODEJS_STACK="my-ws" PULUMI_NODEJS_PROJECT="dev" PULUMI_CONFIG='{ "aws:region": "us-west-2" }' mocha tests.jsExecutar isso, como esperado, nos mostrará que temos três testes falhando!
Infrastructure #server 1) must have a name tag 2) must not use userData (use an AMI instead) #group 3) must not open port 22 (SSH) to the Internet 0 passing (17ms) 3 failing 1) Infrastructure #server must have a name tag: Error: Missing a name tag on server urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 2) Infrastructure #server must not use userData (use an AMI instead): Error: Illegal use of userData on server urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 3) Infrastructure #group must not open port 22 (SSH) to the Internet: Error: Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on groupVamos corrigir nosso programa:
"use strict"; let aws = require("@pulumi/aws"); let group = new aws.ec2.SecurityGroup("web-secgrp", { ingress: [ { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] }, ], }); let server = new aws.ec2.Instance("web-server-www", { tags: { "Name": "web-server-www" }, instanceType: "t2.micro", securityGroups: [ group.name ], // reference the group object above ami: "ami-c55673a0" // AMI for us-east-2 (Ohio), }); exports.group = group; exports.server = server; exports.publicIp = server.publicIp; exports.publicHostName = server.publicDns;E então, vamos reiniciar os testes:
Infraestrutura #servidor ✓ deve ter uma etiqueta de nome ✓ não deve usar userData (use uma AMI em vez disso) #grupo ✓ não deve abrir a porta 22 (SSH) para a Internet 3 passando (16ms)Tudo correu bem… Uhu! ✓✓✓
Por hoje é tudo, e falaremos sobre o teste de implantação na segunda parte da tradução 😉
Fonte: habr.com
