Aller au contenu
Infrastructure as Code medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Pulumi - inputs, outputs, config et secrets

10 min de lecture

logo pulumi

Quand un premier lab Pulumi fonctionne, le probleme suivant arrive vite : les valeurs importantes sont codees en dur dans __main__.py. Le nom de la VM, la memoire, l’utilisateur ou un mot de passe de lab deviennent alors penibles a modifier et faciles a exposer au mauvais endroit. Ce guide vous montre comment passer a une stack plus propre avec pulumi config, des outputs utiles et un secret gere par Pulumi.

Le scenario a ete rejoue le 1 avril 2026 sur KVM/libvirt avec une VM nommee pulumi-config-vm, un reseau pulumi-config-net, des outputs lisibles via pulumi stack output et un secret adminPassword masque dans la CLI puis stocke en mode secure: dans le fichier de stack.

Flux Pulumi entre la configuration CLI, la stack, le code Python, le backend local, preview, up et les outputs

  • lire des valeurs simples depuis Pulumi Config ;
  • lire une valeur secrete avec require_secret() ;
  • injecter cette valeur dans un flux cloud-init ;
  • exporter les informations utiles avec pulumi.export() ;
  • verifier que Pulumi masque bien le secret dans la CLI.

Reprenez le projet local deja prepare dans les guides precedents :

Fenêtre de terminal
cd ~/pulumi-kvm-local
source venv/bin/activate
pulumi stack select dev

Verification : la stack dev doit etre selectionnee sans erreur.

Nous allons remplacer les valeurs codees en dur par des lectures de config. Remplacez __main__.py par ce programme :

import pulumi
import pulumi_libvirt as libvirt
config = pulumi.Config()
network_name = config.get("networkName") or "pulumi-lab-net"
vm_name = config.get("vmName") or "pulumi-lab-vm"
vm_memory_mib = config.get_int("vmMemoryMiB") or 2048
vm_vcpu = config.get_int("vmVcpu") or 2
admin_user = config.get("adminUser") or "devops"
admin_password = config.require_secret("adminPassword")
provider = libvirt.Provider(
"libvirt-provider",
uri="qemu:///system",
)
lab_network = libvirt.Network(
"pulumi-lab-network",
name=network_name,
autostart=True,
domain=libvirt.NetworkDomainArgs(name="pulumi.lab"),
forward=libvirt.NetworkForwardArgs(mode="nat"),
ips=[
libvirt.NetworkIpArgs(
address="192.168.151.1",
prefix=24,
dhcp=libvirt.NetworkIpDhcpArgs(
ranges=[
libvirt.NetworkIpDhcpRangeArgs(
start="192.168.151.50",
end="192.168.151.200",
)
]
),
)
],
opts=pulumi.ResourceOptions(provider=provider),
)
vm_disk = libvirt.Volume(
"pulumi-lab-vm-disk",
name="pulumi-lab-vm.qcow2",
pool="default",
capacity=20,
capacity_unit="GiB",
backing_store=libvirt.VolumeBackingStoreArgs(
path="/var/lib/libvirt/images/ubuntu-22.04-cloudimg.qcow2",
format=libvirt.VolumeBackingStoreFormatArgs(type="qcow2"),
),
target=libvirt.VolumeTargetArgs(
format=libvirt.VolumeTargetFormatArgs(type="qcow2"),
),
opts=pulumi.ResourceOptions(provider=provider),
)
cloudinit_disk = libvirt.CloudinitDisk(
"pulumi-lab-cloudinit",
name=f"{vm_name}-cloudinit.iso",
meta_data=f"""instance-id: {vm_name}
local-hostname: {vm_name}
""",
user_data=pulumi.Output.format(
"""#cloud-config
hostname: {0}
manage_etc_hosts: true
users:
- name: {1}
groups: sudo
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
chpasswd:
list: |
{1}:{2}
expire: false
ssh_pwauth: true
package_update: true
packages:
- qemu-guest-agent
runcmd:
- systemctl enable --now qemu-guest-agent
""",
vm_name,
admin_user,
admin_password,
),
opts=pulumi.ResourceOptions(provider=provider),
)
lab_vm = libvirt.Domain(
"pulumi-lab-vm",
type="kvm",
name=vm_name,
memory=vm_memory_mib,
memory_unit="MiB",
vcpu=vm_vcpu,
running=True,
os=libvirt.DomainOsArgs(
type="hvm",
type_arch="x86_64",
),
devices=libvirt.DomainDevicesArgs(
disks=[
libvirt.DomainDevicesDiskArgs(
device="disk",
source=libvirt.DomainDevicesDiskSourceArgs(
volume=libvirt.DomainDevicesDiskSourceVolumeArgs(
pool="default",
volume=vm_disk.name,
),
),
target=libvirt.DomainDevicesDiskTargetArgs(
dev="vda",
bus="virtio",
),
),
libvirt.DomainDevicesDiskArgs(
device="cdrom",
read_only=True,
source=libvirt.DomainDevicesDiskSourceArgs(
file=libvirt.DomainDevicesDiskSourceFileArgs(
file=cloudinit_disk.path,
),
),
target=libvirt.DomainDevicesDiskTargetArgs(
dev="hda",
bus="ide",
),
),
],
interfaces=[
libvirt.DomainDevicesInterfaceArgs(
source=libvirt.DomainDevicesInterfaceSourceArgs(
network=libvirt.DomainDevicesInterfaceSourceNetworkArgs(
network=lab_network.name,
),
),
model=libvirt.DomainDevicesInterfaceModelArgs(type="virtio"),
),
],
),
opts=pulumi.ResourceOptions(provider=provider),
)
pulumi.export("network_name", lab_network.name)
pulumi.export("network_id", lab_network.id)
pulumi.export("network_uuid", lab_network.uuid)
pulumi.export("vm_name", lab_vm.name)
pulumi.export("vm_disk_name", vm_disk.name)
pulumi.export("admin_user", pulumi.Output.from_input(admin_user))
pulumi.export("vm_memory_mib", pulumi.Output.from_input(vm_memory_mib))

Le point cle est simple : le code ne porte plus toutes les valeurs utiles en dur. Il lit maintenant une partie du contexte dans la stack.

Ajoutez maintenant les valeurs de stack. Les cinq premieres sont des valeurs simples. La derniere est un secret.

Fenêtre de terminal
pulumi config set vmName pulumi-config-vm --stack dev
pulumi config set networkName pulumi-config-net --stack dev
pulumi config set vmMemoryMiB 3072 --stack dev
pulumi config set vmVcpu 2 --stack dev
pulumi config set adminUser devops --stack dev
pulumi config set --secret adminPassword 'Pulumi-Devops-2026!' --stack dev

Vous pouvez ensuite observer la configuration sans exposer le secret :

Fenêtre de terminal
pulumi config --stack dev

Verification : la sortie doit montrer adminPassword [secret] et non la valeur en clair.

Si vous voulez comprendre ou la stack stocke cette information, lisez aussi le fichier de stack :

Fenêtre de terminal
sed -n '1,120p' Pulumi.dev.yaml

Vous devez y voir une entree secure: pour adminPassword.

Lancez maintenant le preview :

Fenêtre de terminal
pulumi preview --stack dev

Dans le scenario valide, Pulumi annonce au minimum :

  • network_name : "pulumi-config-net" ;
  • vm_name : "pulumi-config-vm" ;
  • vm_memory_mib : 3072 ;
  • la creation du reseau, du disque, du cloud-init et du domaine.

Le secret n’apparait pas en clair. C’est exactement ce que vous cherchez ici.

Fenêtre de terminal
pulumi up --stack dev --yes
pulumi stack output vm_name --stack dev
pulumi stack output admin_user --stack dev
pulumi stack output vm_memory_mib --stack dev

Verification : dans le lab rejoue, les outputs retournent respectivement :

  • pulumi-config-vm ;
  • devops ;
  • 3072.

Les outputs servent a exposer les informations utiles a la fin du run sans vous obliger a inspecter directement le state brut.

Fenêtre de terminal
virsh list --all | grep pulumi-config-vm
virsh domstate pulumi-config-vm
virsh domiflist pulumi-config-vm

Vous devez voir :

  • une VM pulumi-config-vm en etat running ;
  • une interface reliee au reseau pulumi-config-net ;
  • un modele virtio.

Cette etape prouve que vos valeurs de config ont bien pilote la creation reelle de la ressource et pas seulement le texte du programme.

Fenêtre de terminal
pulumi destroy --stack dev --yes

Le workflow reste le meme : un bon guide Pulumi ne s’arrete pas a up, il inclut toujours le retour a un etat propre.

L’objet Config() est votre point d’entree vers les valeurs de stack. Il ne lit pas des variables Python globales. Il lit la configuration rattachee au projet et a la stack selectionnee.

Ces methodes servent a recuperer des valeurs non secretes. Ici, elles pilotent le nom du reseau, le nom de la VM, la memoire et le nombre de vCPU.

require_secret() indique a Pulumi qu’une valeur doit etre traitee comme sensible. C’est pour cela que adminPassword reste masque dans la CLI et stocke sous forme secure: dans Pulumi.dev.yaml.

Le bloc cloud-init doit assembler des valeurs simples et une valeur secrete. pulumi.Output.format() permet de composer cette chaine sans casser le modele de donnees de Pulumi.

Les exports servent a publier les informations que vous voulez relire apres up. Ici, ils rendent le run plus lisible pour verifier rapidement le nom de la VM, l’utilisateur configure ou la memoire choisie.

SymptomeCause probableSolution
Missing required configuration variable 'adminPassword'Le secret n’a pas ete definiRejouez pulumi config set --secret adminPassword ... --stack dev
Le secret apparait en clair dans votre codeValeur collee directement dans __main__.pyRemplacez-la par config.require_secret()
vm_memory_mib ne change pasLa valeur n’est pas relue depuis la stack couranteVerifiez pulumi stack select dev puis relancez preview
La VM porte encore l’ancien nomLa ressource existante n’a pas ete recreee dans le bon contexteRelisez le preview, appliquez puis verifiez avec virsh
  • Pulumi Config evite de coder en dur toutes les valeurs utiles dans __main__.py.
  • require_secret() permet de traiter une valeur sensible sans l’afficher en clair dans la CLI.
  • pulumi.export() rend le resultat du run plus lisible.
  • Le bon reflexe reste config -> preview -> up -> output -> verification -> destroy.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn