Bonjour les amis. En prévision du démarrage d'un nouveau flux au rythme
L'utilisation de Pulumi et des langages de programmation à usage général pour le code d'infrastructure (Infrastructure as Code) offre de nombreux avantages : la disponibilité de compétences et de connaissances, l'élimination du passe-partout dans le code grâce à l'abstraction, des outils familiers à votre équipe, tels que les IDE et les linters. Tous ces outils de génie logiciel nous rendent non seulement plus productifs, mais améliorent également la qualité de notre code. Par conséquent, il est tout à fait naturel que l'utilisation de langages de programmation à usage général nous permette d'introduire une autre pratique importante en matière de développement logiciel : test.
Dans cet article, nous verrons comment Pulumi nous aide à tester notre infrastructure en tant que code.
Pourquoi tester l’infrastructure ?
Avant d’entrer dans les détails, il convient de se poser la question : « Pourquoi tester l’infrastructure ? » Il y a plusieurs raisons à cela et en voici quelques-unes :
- Tests unitaires de fonctions individuelles ou de fragments de la logique de votre programme
- Vérifie l’état souhaité de l’infrastructure par rapport à certaines contraintes.
- Détection d'erreurs courantes, telles que l'absence de chiffrement d'un compartiment de stockage ou un accès ouvert et non protégé depuis Internet aux machines virtuelles.
- Vérifier la mise en œuvre de l'approvisionnement de l'infrastructure.
- Effectuer des tests d'exécution de la logique d'application exécutée dans votre infrastructure « programmée » pour vérifier la fonctionnalité après le provisionnement.
- Comme nous pouvons le constater, il existe un large éventail d’options de test d’infrastructure. Polumi dispose de mécanismes pour tester à chaque point de ce spectre. Commençons et voyons comment cela fonctionne.
Tests unitaires
Les programmes Pulumi sont écrits dans des langages de programmation généraux tels que JavaScript, Python, TypeScript ou Go. Par conséquent, toute la puissance de ces langages, y compris leurs outils et bibliothèques, y compris les frameworks de test, est à leur disposition. Pulumi est multi-cloud, ce qui signifie qu'il peut être utilisé pour des tests auprès de n'importe quel fournisseur de cloud.
(Dans cet article, bien que multilingues et multicloud, nous utilisons JavaScript et Mocha et nous concentrons sur AWS. Vous pouvez utiliser Python unittest
, Allez au framework de test, ou à tout autre framework de test que vous aimez. Et bien sûr, Pulumi fonctionne très bien avec Azure, Google Cloud, Kubernetes.)
Comme nous l'avons vu, il existe plusieurs raisons pour lesquelles vous souhaiterez peut-être tester votre code d'infrastructure. L’un d’eux est le test unitaire conventionnel. Parce que votre code peut avoir des fonctions - par exemple, calculer le CIDR, calculer dynamiquement des noms, des balises, etc. - vous voudrez probablement les tester. Cela revient à écrire des tests unitaires réguliers pour des applications dans votre langage de programmation préféré.
Pour être un peu plus compliqué, vous pouvez vérifier comment votre programme alloue les ressources. Pour illustrer, imaginons que nous devions créer un simple serveur EC2 et que nous voulions être sûrs des éléments suivants :
- Les instances ont une balise
Name
. - Les instances ne doivent pas utiliser de script en ligne
userData
- il faut utiliser une AMI (image). - Aucun SSH ne devrait être exposé à Internet.
Cet exemple est basé sur
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 ], // reference the group object above
ami: "ami-c55673a0" // AMI for us-east-2 (Ohio),
userData: userData // start a simple web server
});
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;
Il s'agit du programme de base de Pulumi : il alloue simplement un groupe de sécurité EC2 et une instance. Cependant, il convient de noter que nous enfreignons ici les trois règles énoncées ci-dessus. Écrivons des tests !
Écrire des tests
La structure générale de nos tests ressemblera à celle des tests Mocha classiques :
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): Должен быть тэг Name.
// TODO(check 2): Не должно быть inline-скрипта userData.
});
let group = infra.group;
describe("#group", function() {
// TODO(check 3): Не должно быть SSH, открытого в Интернет.
});
});
Écrivons maintenant notre premier test : assurez-vous que les instances ont la balise Name
. Pour vérifier cela, nous obtenons simplement l'objet d'instance EC2 et vérifions 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 classique, mais avec quelques fonctionnalités à noter :
- Parce que nous interrogeons l’état d’une ressource avant le déploiement, nos tests sont toujours exécutés en mode « plan » (ou « aperçu »). Ainsi, il existe de nombreuses propriétés dont les valeurs ne seront tout simplement pas récupérées ou ne seront pas définies. Cela inclut toutes les propriétés de sortie calculées par votre fournisseur de cloud. C'est normal pour nos tests - nous vérifions uniquement les données d'entrée. Nous reviendrons sur cette problématique 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 évaluées de manière asynchrone, nous devons utiliser la méthode apply pour accéder aux valeurs. Ceci est très similaire aux promesses et à la fonction
then
. - 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, puisque ces valeurs sont calculées de manière asynchrone, nous devons utiliser la fonction de rappel asynchrone intégrée de Mocha.
done
ou retourner une promesse.
Une fois que nous aurons tout configuré, nous aurons accès aux entrées sous forme de simples valeurs JavaScript. Propriété tags
est une carte (tableau associatif), nous allons donc simplement nous assurer que ce n'est pas (1) faux, et (2) il y a une clé pour Name
. C'est très simple et maintenant on peut tout tester !
Maintenant, faisons notre deuxième chèque. 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. Ce sera un peu plus compliqué car nous recherchons les règles de connexion associé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 avons réussi :
// 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();
}
});
});
C'est tout. Lançons maintenant les tests !
Exécution de tests
Dans la plupart des cas, vous pouvez exécuter des tests de la manière habituelle, en utilisant le framework de test de votre choix. Mais il y a une caractéristique de Pulumi à laquelle il convient de prêter attention.
Généralement, pour exécuter les programmes Pulumi, on utilise la CLI pulimi (interface de ligne de commande), qui configure le temps d'exécution du langage, contrôle le lancement du moteur Pulumi afin que les opérations avec les ressources puissent être enregistrées et incluses dans le plan, etc. Cependant, il y a un problème. Lors de l'exécution sous le contrôle de votre framework de test, il n'y aura aucune communication entre la CLI et le moteur Pulumi.
Pour contourner ce problème, il suffit de préciser les éléments suivants :
- Nom du projet, contenu dans la variable d'environnement
PULUMI_NODEJS_PROJECT
(ou plus généralementPULUMI__PROJECT для других языков).
Le nom de la pile spécifié dans la variable d'environnementPULUMI_NODEJS_STACK
(ou plus généralementPULUMI__ STACK).
Variables de configuration de votre pile. Ils peuvent être obtenus à l'aide d'une variable d'environnementPULUMI_CONFIG
et leur format est une carte JSON avec des paires clé/valeur.Le programme émettra des avertissements indiquant que la connexion à la CLI/au moteur n'est pas disponible pendant l'exécution. Ceci est important car votre programme ne déploiera rien et cela peut surprendre si ce n'est pas ce que vous aviez l'intention de faire ! Pour dire à Pulumi que c'est exactement ce dont vous avez besoin, vous pouvez installer
PULUMI_TEST_MODE
вtrue
.Imaginez que nous devions spécifier le nom du projet dans
my-ws
, nom de la piledev
et 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.js
Faire cela, comme prévu, nous montrera que nous avons trois tests échoués !
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 group
Corrigeons 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 ], // 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;
Et puis relancez les tests :
Infrastructure #server ✓ must have a name tag ✓ must not use userData (use an AMI instead) #group ✓ must not open port 22 (SSH) to the Internet 3 passing (16ms)
Tout s'est bien passé... Hourra ! ✓ ✓ ✓
C'est tout pour aujourd'hui, mais nous parlerons des tests de déploiement dans la deuxième partie de la traduction 😉
Source: habr.com