Buongiorno, amici. In preparazione all'inizio di un nuovo ciclo del corso condividiamo con voi una nuova traduzione. Partiamo.

L'uso di Pulumi e dei linguaggi di programmazione di uso generale per il codice infrastrutturale (Infrastructure as Code) offre molti vantaggi: possedere competenze e conoscenze, eliminare il boilerplate nel codice tramite astrazione, strumenti familiari per il vostro team, come IDE e linters. Tutti questi strumenti di ingegneria software non solo ci rendono più produttivi, ma migliorano anche la qualità del codice. Quindi, è del tutto naturale che l'uso di linguaggi di programmazione di uso generale permetta di implementare un'altra importante pratica di sviluppo software — il testing.
In questo articolo vedremo come Pulumi aiuta a testare la nostra «infrastruttura come codice».

Perché testare l'infrastruttura?
Prima di addentrarci nei dettagli, è opportuno chiedersi: «Perché testare l'infrastruttura?» Ci sono molte ragioni e alcune di esse sono:
- Testare in modo modulare singole funzioni o frammenti della logica del vostro programma
- Verificare che lo stato desiderato dell'infrastruttura rispetti determinati vincoli.
- Identificare errori comuni, come la mancanza di crittografia nel bucket di storage o l'accesso non protetto e aperto a Internet per le macchine virtuali.
- Verificare l'esecuzione del provisioning dell'infrastruttura.
- Eseguire test runtime della logica dell'applicazione, in esecuzione all'interno della vostra «infrastruttura programmata», per verificare il funzionamento dopo il provisioning.
- Come vediamo, c'è un ampio spettro di opzioni per testare l'infrastruttura. In Pulumi ci sono meccanismi per testare in ogni punto di questo spettro. Iniziamo e vediamo come funziona.
Test modulare
I programmi Pulumi sono scritti in linguaggi di programmazione di uso generale, come JavaScript, Python, TypeScript o Go. Pertanto, hanno accesso a tutta la potenza di questi linguaggi, compresi i loro strumenti e librerie, tra cui i framework di test. Pulumi è multi-cloud, il che significa che è possibile utilizzare qualsiasi provider cloud per i test.
(In questo articolo, nonostante la multilinguismo e la multi-cloud, utilizziamo JavaScript e Mocha e ci concentriamo su AWS. È possibile utilizzare Python unittest, framework di test Go o qualsiasi altro framework di test che preferisci. E, naturalmente, Pulumi funziona benissimo con Azure, Google Cloud, Kubernetes.)
Come abbiamo visto, ci sono diverse ragioni per cui potrebbe essere necessario testare il tuo codice infrastrutturale. Una di queste è il consueto testing unitario. Poiché il tuo codice può contenere funzioni — ad esempio, per calcolare il CIDR, calcolare dinamicamente nomi, tag, ecc. — probabilmente vorrai testarle. È lo stesso che scrivere normali test unitari per le applicazioni nel tuo linguaggio di programmazione preferito.
Se vogliamo complicarlo un po', possiamo verificare come la tua programmazione distribuisce le risorse. Per illustrare, immaginiamo di dover creare un semplice server EC2 e vogliamo assicurarci delle seguenti condizioni:
- Le istanze devono avere un tag
Name. - Le istanze non devono utilizzare uno script inline
userData— dobbiamo utilizzare AMI (immagine). - Non deve esserci SSH aperto su Internet.
Questo esempio è ispirato a :
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 ], // riferimento all'oggetto del gruppo sopra
ami: "ami-c55673a0" // AMI per us-east-2 (Ohio),
userData: userData // avvia un semplice server web
});
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;
Questo è un programma Pulumi di base: allocca semplicemente un gruppo di sicurezza EC2 e un'istanza. Tuttavia, va notato che qui stiamo violando tutte e tre le regole sopra indicate. Scriviamo i test!
Scriviamo test
La struttura generale dei nostri test assomiglierà a normali test Mocha:
ec2tests.js
test.js:
let assert = require("assert");
let mocha = require("mocha");
let pulumi = require("@pulumi/pulumi");
let infra = require("./index");
describe("Infrastruttura", function() {
let server = infra.server;
describe("#server", function() {
// TODO(check 1): Deve esserci un tag Name.
// TODO(check 2): Non deve esserci uno script inline userData.
});
let group = infra.group;
describe("#group", function() {
// TODO(check 3): Non deve esserci SSH aperto su Internet.
});
}); Ora scriviamo il nostro primo test: assicuriamoci che le istanze abbiano un tag Name. Per verificare ciò, otteniamo semplicemente l'oggetto istanza EC2 e controlliamo la proprietà corrispondente 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();
}
});
});Sembra un test normale, ma con alcune caratteristiche che meritano attenzione:
- Poiché richiediamo lo stato della risorsa prima del deployment, i nostri test vengono sempre eseguiti in modalità “plan” (o “preview”). Di conseguenza, ci sono molte proprietà i cui valori semplicemente non verranno forniti o saranno indefiniti. Queste includono tutte le proprietà in uscita calcolate dal tuo fornitore di cloud. Per i nostri test, questo va bene: verifichiamo solo i dati in ingresso. Torneremo su questo argomento più tardi, quando parleremo dei test di integrazione.
- Dato che tutte le proprietà delle risorse Pulumi sono ‘uscite’ e molte di esse vengono calcolate in modo asincrono, dobbiamo utilizzare il metodo apply per accedere ai valori. Questo è molto simile alle promesse e a una funzione async.
allora. - Poiché utilizziamo diverse proprietà per mostrare l'URN della risorsa nel messaggio di errore, dobbiamo utilizzare la funzione
pulumi.all, per unirle. - Infine, poiché questi valori vengono calcolati in modo asincrono, dobbiamo utilizzare la funzionalità asincrona integrata di Mocha con un callback
fattoo il ritorno di una promessa.
Una volta impostati, avremo accesso ai dati in ingresso come valori semplici di JavaScript. La proprietà tags è una mappa (array associativo), quindi ci assicureremo semplicemente che (1) non sia false e (2) ci sia una chiave per Name. È molto semplice e ora possiamo verificare qualsiasi cosa!
Ora scriviamo il nostro secondo controllo. È ancora più semplice:
// 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 infine, scriviamo il terzo test. Sarà un po' più complesso perché stiamo cercando le regole di ingresso legate ai gruppi di sicurezza, che possono essere molte, e le gamme CIDR in queste regole, che possono anche essere molte. Ma ce l'abbiamo fatta:
// 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();
}
});
});Ecco fatto. Ora eseguiamo i test!
Esecuzione dei test
I test possono essere eseguiti, nella maggior parte dei casi, in modo normale, utilizzando il framework di test scelto. Ma c'è una peculiarità di Pulumi che merita attenzione.
Di solito, per eseguire i programmi Pulumi si utilizza il pulimi CLI (Command Line Interface), che configura il runtime del linguaggio, gestisce le esecuzioni del motore Pulumi in modo da poter registrare le operazioni con le risorse e includerle nel piano, ecc. Tuttavia, c'è un problema. Quando si esegue sotto il controllo del proprio framework di test, non ci sarà connessione tra il CLI e il motore Pulumi.
Per aggirare questo problema, dobbiamo semplicemente fornire quanto segue:
- Il nome del progetto, che è contenuto nella variabile di ambiente
PULUMI_NODEJS_PROJECT(o, in un caso più generale,PULUMI__PROJECT per altri linguaggi).
Il nome dello stack, che è specificato nella variabile di ambientePULUMI_NODEJS_STACK(o, in un caso più generale,PULUMI__STACK).
Le tue variabili di configurazione dello stack. Possono essere ottenute tramite la variabile di ambientePULUMI_CONFIGe il loro formato è una mappa JSON con coppie chiave/valore.Il programma emetterà avvisi, indicando che durante l'esecuzione non è disponibile la connessione con il CLI/motore. Questo è importante, perché in realtà, il tuo programma non distribuirà nulla e potrebbe essere una sorpresa se non è ciò che intendevi fare! Per dire a Pulumi che questo è esattamente ciò di cui hai bisogno, puoi impostare
PULUMI_TEST_MODEintrue.Immagina di dover specificare il nome del progetto in
my-ws, il nome dello stackdev, e la regione AWSus-west-2. Il comando da eseguire per i test Mocha sarà il seguente:$ PULUMI_TEST_MODE=true PULUMI_NODEJS_STACK="my-ws" PULUMI_NODEJS_PROJECT="dev" PULUMI_CONFIG='{ "aws:region": "us-west-2" }' mocha tests.jsEseguendo questo, come previsto, ci mostrerà che abbiamo tre test falliti!
Infrastructure #server 1) deve avere un tag nome 2) non può utilizzare userData (usa un'AMI invece) #group 3) non deve aprire la porta 22 (SSH) a Internet 0 che passa (17ms) 3 falliti 1) Infrastructure #server deve avere un tag nome: Errore: Mancanza di un tag nome sul server urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 2) Infrastructure #server non deve utilizzare userData (usa un'AMI invece): Errore: Uso illegale di userData sul server urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 3) Infrastructure #group non deve aprire la porta 22 (SSH) a Internet: Errore: Porta SSH 22 aperta in modo illegale a Internet (CIDR 0.0.0.0/0) sul gruppoCorreggiamo il nostro programma:
"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 ], // riferimento all'oggetto gruppo sopra ami: "ami-c55673a0" // AMI per us-east-2 (Ohio), }); exports.group = group; exports.server = server; exports.publicIp = server.publicIp; exports.publicHostName = server.publicDns;E poi riavvieremo i test:
Infrastruttura #server ✓ deve avere un tag nome ✓ non deve utilizzare userData (utilizza invece un AMI) #group ✓ non deve aprire la porta 22 (SSH) verso Internet 3 superati (16ms)Tutto è andato a buon fine... Evviva! ✓✓✓
Per oggi è tutto, parleremo del testing del deployment nella seconda parte della traduzione 😉
Fonte: habr.com
