Hola, amigos. Con la llegada de un nuevo ciclo del curso, compartimos con ustedes una nueva traducción. ¡Vamos!

El uso de Pulumi y lenguajes de programación de propósito general para el código de infraestructura (Infrastructure as Code) ofrece muchas ventajas: la posesión de habilidades y conocimientos, la eliminación de código boilerplate a través de la abstracción, herramientas familiares para su equipo como IDEs y linters. Todas estas herramientas de ingeniería de software no solo nos hacen más productivos, sino que también mejoran la calidad del código. Por lo tanto, es completamente natural que el uso de lenguajes de programación de propósito general permita implementar otra práctica crucial en el desarrollo de software — pruebas.
En este artículo, veremos cómo Pulumi ayuda a probar nuestra "infraestructura como código".

¿Por qué probar la infraestructura?
Antes de entrar en detalles, vale la pena plantear la pregunta: "¿Por qué deberíamos probar la infraestructura?" Hay muchas razones para ello y aquí algunas de ellas:
- Pruebas unitarias de funciones individuales o fragmentos de la lógica de su programa.
- Verificación del estado deseado de la infraestructura según ciertos parámetros.
- Detección de errores comunes, como la falta de cifrado en el bucket de almacenamiento o acceso no seguro y abierto a máquinas virtuales desde Internet.
- Verificación del cumplimiento del aprovisionamiento de la infraestructura.
- Ejecución de pruebas en tiempo de ejecución de la lógica de la aplicación que se ejecuta dentro de su infraestructura "programada" para verificar su operatividad después del aprovisionamiento.
- Como vemos, existe una amplia gama de opciones para probar la infraestructura. Pulumi tiene mecanismos para probar en cada punto de este espectro. Comencemos y veamos cómo funciona.
Pruebas unitarias
Los programas de Pulumi se crean en lenguajes de programación de propósito general, como JavaScript, Python, TypeScript o Go. Por lo tanto, tienen acceso a todo el poder de estos lenguajes, incluidas sus herramientas y bibliotecas, incluyendo marcos de prueba. Pulumi es multiplataforma, lo que significa que se pueden usar cualquier proveedor de nube para las pruebas.
(En este artículo, a pesar de la multilingüidad y la multiplataforma, utilizamos JavaScript y Mocha y nos enfocamos en AWS. Se puede usar Python unittest, marco de prueba Go o cualquier otro marco de prueba que prefiera. Y, por supuesto, Pulumi funciona muy bien con Azure, Google Cloud, Kubernetes.)
Como hemos visto, hay varias razones por las cuales podría ser necesario probar su código de infraestructura. Una de ellas es la típica prueba unitaria. Dado que su código puede tener funciones, como para calcular CIDR, calcular dinámicamente nombres, etiquetas, etc., probablemente querrá probarlas. Esto es lo mismo que escribir pruebas unitarias típicas para aplicaciones en su lenguaje de programación favorito.
Si complicamos un poco las cosas, podemos verificar cómo su programa distribuye los recursos. Para ilustrar, imaginemos que necesitamos crear un servidor EC2 y queremos asegurarnos de lo siguiente:
- Las instancias tienen una etiqueta
Nombre. - Las instancias no deben utilizar un script inline
userData— debemos utilizar AMI (imagen). - No debe haber SSH abierto al Internet.
Este ejemplo se inspira en :
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 "¡Hola, Mundo!" > index.html
nohup python -m SimpleHTTPServer 80 &`;
let server = new aws.ec2.Instance("web-server-www", {
instanceType: "t2.micro",
securityGroups: [ group.name ], // referencia al objeto del grupo anterior
ami: "ami-c55673a0" // AMI para us-east-2 (Ohio),
userData: userData // iniciar un servidor web simple
});
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;
Este es un programa básico de Pulumi: simplemente asigna un grupo de seguridad EC2 y una instancia. Sin embargo, es importante notar que aquí estamos rompiendo las tres reglas mencionadas anteriormente. ¡Comencemos a escribir pruebas!
Escribimos pruebas
La estructura general de nuestras pruebas se verá como pruebas regulares de Mocha:
ec2tests.js
test.js:
let assert = require("assert");
let mocha = require("mocha");
let pulumi = require("@pulumi/pulumi");
let infra = require("./index");
describe("Infraestructura", function() {
let server = infra.server;
describe("#server", function() {
// TODO(check 1): Debe haber una etiqueta Name.
// TODO(check 2): No debe haber un script inline userData.
});
let group = infra.group;
describe("#group", function() {
// TODO(check 3): No debe haber SSH abierto al Internet.
});
}); Ahora escribamos nuestra primera prueba: asegurémonos de que las instancias tengan una etiqueta Nombre. Para verificar esto, simplemente obtenemos el objeto de la instancia EC2 y comprobamos la propiedad correspondiente 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 una prueba normal, pero con varias características que merecen atención:
- Dado que solicitamos el estado del recurso antes del despliegue, nuestras pruebas siempre se ejecutan en modo 'plan' (o 'vista previa'). Por lo tanto, hay muchas propiedades cuyos valores simplemente no se obtendrán o estarán indefinidos. Esto incluye todas las propiedades de salida calculadas por su proveedor de nube. Para nuestras pruebas, esto está bien; solo verificamos las entradas. Volveremos a este tema más adelante cuando tratemos sobre las pruebas de integración.
- Como todas las propiedades de los recursos de Pulumi son 'salidas' y muchas de ellas se calculan de forma asíncrona, necesitamos usar el método apply para acceder a los valores. Esto es muy similar a las promesas y a la función af.
then. - Dado que usamos varias propiedades para mostrar la URN del recurso en el mensaje de error, necesitamos utilizar la función
pulumi.all, para combinarlas. - Finalmente, dado que estos valores se calculan de forma asíncrona, necesitamos utilizar la funcionalidad asíncrona incorporada de Mocha con la devolución de llamada
doneo devolviendo una promesa.
Una vez que tengamos todo configurado, tendremos acceso a los datos de entrada como valores simples de JavaScript. La propiedad tags es un mapa (array asociativo), así que simplemente nos aseguramos de que (1) no sea false, y (2) haya una clave para Nombre. ¡Es muy sencillo y ahora podemos comprobar cualquier cosa!
Ahora escribiré nuestra segunda verificación. Es aún más fácil:
// 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();
}
});
});Y, finalmente, escribamos una tercera prueba. Esto será un poco más complicado, porque buscamos las reglas de entrada relacionadas con un grupo de seguridad, que pueden ser numerosas, y los rangos CIDR en estas reglas, que también pueden ser numerosos. Pero lo logramos:
// 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();
}
});
});Eso es todo. ¡Ahora ejecutemos las pruebas!
Ejecutar pruebas
Generalmente, se pueden ejecutar pruebas de manera convencional, utilizando el marco de pruebas que elija. Pero hay una particularidad de Pulumi que vale la pena señalar.
Normalmente, para ejecutar programas de Pulumi se utiliza el pulumi CLI (interfaz de línea de comandos), que configura el runtime del lenguaje, controla las ejecuciones del motor de Pulumi para que se puedan registrar las operaciones con los recursos e incluirlas en el plan, etc. Sin embargo, hay un problema. Al ejecutarlo bajo el control de tu framework de pruebas, no habrá conexión entre el CLI y el motor de Pulumi.
Para sortear este problema, solo necesitamos especificar lo siguiente:
- El nombre del proyecto, que se encuentra en la variable de entorno
PULUMI_NODEJS_PROJECT(o, en un caso más general,PULUMI__PROJECT para otros lenguajes).
El nombre de la pila, que se indica en la variable de entornoPULUMI_NODEJS_STACK(o, en un caso más general,PULUMI__STACK).
Tus variables de configuración de la pila. Pueden ser obtenidas a través de la variable de entornoPULUMI_CONFIGy su formato es un mapa JSON con pares clave/valor.El programa emitirá advertencias, indicando que durante la ejecución no hay conexión disponible con el CLI/motor. Esto es importante, porque en realidad, tu programa no desplegará nada y puede ser una sorpresa si eso no es lo que querías hacer. Para decirle a Pulumi que esto es precisamente lo que necesitas, puedes establecer
PULUMI_TEST_MODEentrue.Imagina que necesitamos especificar el nombre del proyecto en
my-ws, el nombre de la piladev, y la región de AWSus-west-2. La línea de comandos para ejecutar las pruebas de Mocha se verá de la siguiente manera:$ PULUMI_TEST_MODE=true PULUMI_NODEJS_STACK="my-ws" PULUMI_NODEJS_PROJECT="dev" PULUMI_CONFIG='{ "aws:region": "us-west-2" }' mocha tests.jsEjecutar esto, como se esperaba, nos mostrará que tenemos tres pruebas fallidas.
Infrastructure #server 1) debe tener una etiqueta de nombre 2) no debe usar userData (usa un AMI en su lugar) #group 3) no debe abrir el puerto 22 (SSH) al Internet 0 pasadas (17ms) 3 fallidas 1) Infrastructure #server debe tener una etiqueta de nombre: Error: Falta una etiqueta de nombre en el servidor urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 2) Infrastructure #server no debe usar userData (usa un AMI en su lugar): Error: Uso ilegal de userData en el servidor urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 3) Infrastructure #group no debe abrir el puerto 22 (SSH) al Internet: Error: El puerto SSH 22 está abierto de forma ilegal al Internet (CIDR 0.0.0.0/0) en el grupoCorrijamos nuestro 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 ], // referencia al objeto del grupo anterior ami: "ami-c55673a0" // AMI para us-east-2 (Ohio), }); exports.group = group; exports.server = server; exports.publicIp = server.publicIp; exports.publicHostName = server.publicDns;Y luego volveremos a ejecutar las pruebas:
Infraestructura #servidor ✓ debe tener una etiqueta de nombre ✓ no debe usar userData (usa una AMI en su lugar) #grupo ✓ no debe abrir el puerto 22 (SSH) a Internet 3 pasando (16ms)Todo salió bien… ¡Hurra! ✓✓✓
Eso es todo por hoy, y hablaremos sobre la prueba de implementación en la segunda parte de la traducción 😉
Fuente: habr.com
