Bonjour, amis. À l'approche du lancement d'un nouveau groupe pour le cours nous partageons avec vous une nouvelle traduction. Allons-y.

L'utilisation de Pulumi et de langages de programmation généralistes pour le code d'infrastructure (Infrastructure as Code) offre de nombreux avantages : compétences et connaissances, élimination des boilerplates dans le code grâce à l'abstraction, outils familiers à votre équipe, tels que les IDE et les linters. Tous ces outils d'ingénierie logicielle ne font pas seulement de nous des professionnels plus productifs, mais améliorent également la qualité du code. Il est donc tout à fait logique que l'utilisation de langages de programmation généralistes permette d'introduire une autre pratique de développement logiciel importante — tests.
Dans cet article, nous allons explorer comment Pulumi aide à tester notre « infrastructure en tant que code ».

Pourquoi tester l'infrastructure ?
Avant de plonger dans les détails, il est utile de poser la question : « Pourquoi tester l'infrastructure ? » Il y a de nombreuses raisons à cela, en voici quelques-unes :
- Tests unitaires de fonctions ou de fragments de logique de votre programme
- Vérification de l'état souhaité de l'infrastructure par rapport à des contraintes définies.
- Détection d'erreurs courantes, telles que l'absence de chiffrement de stockage ou l'accès ouvert et non sécurisé aux machines virtuelles depuis Internet.
- Vérification de l'exécution du provisionnement de l'infrastructure.
- Exécution de tests runtime sur la logique de l'application en cours d'exécution dans votre infrastructure « programmée » pour vérifier son bon fonctionnement après le provisionnement.
- Comme nous le voyons, il existe une large gamme d'options pour tester l'infrastructure. Pulumi dispose de mécanismes pour tester à chaque point de ce spectre. Commençons et voyons comment cela fonctionne.
Tests unitaires
Les programmes Pulumi sont créés dans des langages de programmation généralistes tels que JavaScript, Python, TypeScript ou Go. Par conséquent, ils bénéficient de toute la puissance de ces langages, y compris de leurs outils et bibliothèques, y compris les frameworks de test. Pulumi est multi-cloud, ce qui signifie que vous pouvez utiliser des fournisseurs de cloud pour les tests.
(Dans cet article, malgré la multidistribution et le multi-cloud, nous utilisons JavaScript et Mocha et nous orientons vers AWS. Vous pouvez utiliser Python unittest, le framework de test Go ou tout autre framework de test que vous appréciez. Et bien sûr, Pulumi fonctionne parfaitement avec Azure, Google Cloud, Kubernetes.)
Comme nous l'avons vu, il y a plusieurs raisons pour lesquelles vous pourriez avoir besoin de tester votre code d'infrastructure. L'une d'elles est le test unitaire usuel. Étant donné que votre code peut avoir des fonctions — par exemple, pour calculer le CIDR, calculer dynamiquement des noms, des tags, etc. — vous voudrez probablement les tester. C'est la même chose que d'écrire des tests unitaires normaux pour des applications dans votre langage de programmation préféré.
Si nous compliquons un peu les choses, nous pouvons vérifier comment votre programme alloue des ressources. Pour illustrer cela, imaginons que nous devons créer un simple serveur EC2 et que nous voulons nous assurer de ce qui suit :
- Les instances ont une étiquette
Nom. - Les instances ne doivent pas utiliser de script inline
userData— nous devons utiliser AMI (image). - Il ne doit pas y avoir de SSH ouvert sur Internet.
Cet exemple est inspiré de :
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 ], // référence à l'objet du groupe ci-dessus
ami: "ami-c55673a0" // AMI pour us-east-2 (Ohio),
userData: userData // démarrer un serveur web simple
});
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;
C'est un programme Pulumi de base : il alloue simplement un groupe de sécurité EC2 et une instance. Cependant, notez que nous enfreignons ici les trois règles énoncées ci-dessus. Écrivons des tests !
Écrivons des tests
La structure générale de nos tests ressemblera à des tests Mocha normaux :
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): Il doit y avoir une étiquette Nom.
// TODO(check 2): Il ne doit pas y avoir de script inline userData.
});
let group = infra.group;
describe("#group", function() {
// TODO(check 3): Il ne doit pas y avoir de SSH ouvert sur Internet.
});
}); Maintenant, écrivons notre premier test : assurons-nous que les instances ont un tag Nom. Pour cela, nous allons simplement obtenir l'objet instance EC2 et vérifier la propriété correspondante 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();
}
});
});Cela ressemble à un test ordinaire, mais avec quelques particularités qui méritent d'être notées :
- Puisque nous demandons l'état de la ressource avant le déploiement, nos tests s'exécutent toujours en mode "plan" (ou "preview"). Ainsi, il existe de nombreuses propriétés dont les valeurs ne seront tout simplement pas récupérées ou seront non définies. Cela inclut toutes les propriétés de sortie calculées par votre fournisseur de cloud. Pour nos tests, c'est acceptable — nous vérifions uniquement les entrées. Nous reviendrons sur cette question plus tard, lorsqu'il s'agira des tests d'intégration.
- Étant donné que toutes les propriétés des ressources Pulumi sont des « sorties », et que beaucoup d'entre elles sont calculées de manière asynchrone, nous devons utiliser la méthode apply pour accéder aux valeurs. Cela ressemble beaucoup aux promesses et à une fonction
alors. - Puisque nous utilisons plusieurs propriétés pour afficher l'URN de la ressource dans le message d'erreur, nous devons utiliser la fonction
pulumi.all, pour les combiner. - Enfin, comme ces valeurs sont calculées de manière asynchrone, nous devons utiliser la fonctionnalité asynchrone intégrée de Mocha avec un rappel
doneou le retour d'une promesse.
Une fois que tout est configuré, nous aurons accès aux entrées comme à de simples valeurs JavaScript. La propriété tags est un map (tableau associatif), donc nous allons simplement nous assurer que c'est (1) non faux, et (2) qu'il existe une clé pour Nom. C'est très simple et maintenant nous pouvons vérifier tout ce que nous voulons !
Maintenant, écrivons notre deuxième vérification. C'est encore plus simple :
// 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();
}
});
});Et enfin, écrivons le troisième test. Cela sera un peu plus complexe, car nous recherchons les règles d'entrée liées au groupe de sécurité, qui peuvent être nombreuses, et les plages CIDR dans ces règles, qui peuvent également être nombreuses. Mais nous y sommes arrivés :
// 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();
}
});
});Voilà, c'est tout. Maintenant, lançons les tests !
Lancer les tests
Lancer des tests, dans la plupart des cas, peut se faire de manière habituelle en utilisant le cadre de test que vous avez choisi. Mais il y a une spécificité de Pulumi à prendre en compte.
En général, pour démarrer les programmes Pulumi, l'interface en ligne de commande Pulumi CLI est utilisée, qui configure le runtime du langage, gère les exécutions du moteur Pulumi pour permettre d'enregistrer les opérations sur les ressources et les inclure dans le plan, etc. Cependant, il existe un problème. Lorsque vous exécutez sous le contrôle de votre cadre de test, il n'y aura pas de connexion entre le CLI et le moteur Pulumi.
Pour contourner ce problème, nous devons simplement spécifier ce qui suit :
- Le nom du projet, qui est contenu dans la variable d'environnement
PULUMI_NODEJS_PROJECT(ou, de manière plus générale,PULUMI__PROJECT pour d'autres langages).
Le nom de la pile, qui est spécifié dans la variable d'environnementPULUMI_NODEJS_STACK(ou, de manière plus générale,PULUMI__STACK).
Vos variables de configuration de la pile. Elles peuvent être obtenues à l'aide de la variable d'environnementPULUMI_CONFIGet leur format est une carte JSON avec des paires clé/valeur.Le programme émettra des avertissements indiquant qu'il n'y a pas de connexion avec le CLI/le moteur lors de l'exécution. Cela est important, car en réalité, votre programme ne déploiera rien et cela peut être une surprise si ce n'est pas ce que vous vouliez faire ! Pour dire à Pulumi que c'est exactement ce dont vous avez besoin, vous pouvez définir
PULUMI_TEST_MODEdanstrue.Imaginez que nous devons spécifier le nom du projet dans
my-ws, le nom de la piledev, et la région AWSus-west-2. La ligne de commande pour exécuter les tests Mocha ressemblera à ceci :$ PULUMI_TEST_MODE=true PULUMI_NODEJS_STACK="my-ws" PULUMI_NODEJS_PROJECT="dev" PULUMI_CONFIG='{ "aws:region": "us-west-2" }' mocha tests.jsL'exécution de cela, comme prévu, nous montrera que nous avons trois tests échoués !
Infrastructure #serveur 1) doit avoir une étiquette de nom 2) ne doit pas utiliser userData (utilisez plutôt un AMI) #groupe 3) ne doit pas ouvrir le port 22 (SSH) à Internet 0 réussite (17ms) 3 échoués 1) Infrastructure #serveur doit avoir une étiquette de nom : Erreur : Étiquette de nom manquante sur le serveur urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 2) Infrastructure #serveur ne doit pas utiliser userData (utilisez plutôt un AMI) : Erreur : Utilisation illégale de userData sur le serveur urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www 3) Infrastructure #groupe ne doit pas ouvrir le port 22 (SSH) à Internet : Erreur : Port SSH 22 ouvert à Internet (CIDR 0.0.0.0/0) sur le groupeCorrigeons notre programme :
"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 ], // référence à l'objet groupe ci-dessus ami: "ami-c55673a0" // AMI pour us-east-2 (Ohio), }); exports.group = group; exports.server = server; exports.publicIp = server.publicIp; exports.publicHostName = server.publicDns;Et ensuite, nous relancerons les tests :
Infrastructure #server ✓ doit avoir une balise de nom ✓ ne doit pas utiliser userData (utilisez une AMI à la place) #group ✓ ne doit pas ouvrir le port 22 (SSH) à Internet 3 réussis (16ms)Tout a bien fonctionné… Hourra ! ✓✓✓
C'est tout pour aujourd'hui, et nous parlerons de l'examen du déploiement dans la deuxième partie de la traduction 😉
Source : habr.com
