
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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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.
Etape 1 - Ouvrir le projet local
Section intitulée « Etape 1 - Ouvrir le projet local »Reprenez le projet local deja prepare dans les guides precedents :
cd ~/pulumi-kvm-localsource venv/bin/activatepulumi stack select devVerification : la stack dev doit etre selectionnee sans erreur.
Etape 2 - Remplacer le code de la stack
Section intitulée « Etape 2 - Remplacer le code de la stack »Nous allons remplacer les valeurs codees en dur par des lectures de config.
Remplacez __main__.py par ce programme :
import pulumiimport 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 2048vm_vcpu = config.get_int("vmVcpu") or 2admin_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-confighostname: {0}manage_etc_hosts: trueusers: - name: {1} groups: sudo shell: /bin/bash sudo: ALL=(ALL) NOPASSWD:ALLchpasswd: list: | {1}:{2} expire: falsessh_pwauth: truepackage_update: truepackages: - qemu-guest-agentruncmd: - 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.
Etape 3 - Definir la configuration et le secret
Section intitulée « Etape 3 - Definir la configuration et le secret »Ajoutez maintenant les valeurs de stack. Les cinq premieres sont des valeurs simples. La derniere est un secret.
pulumi config set vmName pulumi-config-vm --stack devpulumi config set networkName pulumi-config-net --stack devpulumi config set vmMemoryMiB 3072 --stack devpulumi config set vmVcpu 2 --stack devpulumi config set adminUser devops --stack devpulumi config set --secret adminPassword 'Pulumi-Devops-2026!' --stack devVous pouvez ensuite observer la configuration sans exposer le secret :
pulumi config --stack devVerification : 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 :
sed -n '1,120p' Pulumi.dev.yamlVous devez y voir une entree secure: pour adminPassword.
Etape 4 - Lire le preview
Section intitulée « Etape 4 - Lire le preview »Lancez maintenant le preview :
pulumi preview --stack devDans 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.
Etape 5 - Appliquer la stack et lire les outputs
Section intitulée « Etape 5 - Appliquer la stack et lire les outputs »pulumi up --stack dev --yespulumi stack output vm_name --stack devpulumi stack output admin_user --stack devpulumi stack output vm_memory_mib --stack devVerification : 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.
Etape 6 - Verifier la VM cote libvirt
Section intitulée « Etape 6 - Verifier la VM cote libvirt »virsh list --all | grep pulumi-config-vmvirsh domstate pulumi-config-vmvirsh domiflist pulumi-config-vmVous devez voir :
- une VM
pulumi-config-vmen etatrunning; - 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.
Etape 7 - Detruire proprement
Section intitulée « Etape 7 - Detruire proprement »pulumi destroy --stack dev --yesLe workflow reste le meme : un bon guide Pulumi ne s’arrete pas a up, il
inclut toujours le retour a un etat propre.
Comment lire ce code
Section intitulée « Comment lire ce code »pulumi.Config()
Section intitulée « pulumi.Config() »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.
get() et get_int()
Section intitulée « get() et get_int() »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()
Section intitulée « require_secret() »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.
pulumi.Output.format()
Section intitulée « pulumi.Output.format() »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.
pulumi.export()
Section intitulée « pulumi.export() »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.
Pieges frequents
Section intitulée « Pieges frequents »| Symptome | Cause probable | Solution |
|---|---|---|
Missing required configuration variable 'adminPassword' | Le secret n’a pas ete defini | Rejouez pulumi config set --secret adminPassword ... --stack dev |
| Le secret apparait en clair dans votre code | Valeur collee directement dans __main__.py | Remplacez-la par config.require_secret() |
vm_memory_mib ne change pas | La valeur n’est pas relue depuis la stack courante | Verifiez pulumi stack select dev puis relancez preview |
| La VM porte encore l’ancien nom | La ressource existante n’a pas ete recreee dans le bon contexte | Relisez le preview, appliquez puis verifiez avec virsh |
A retenir
Section intitulée « A retenir »- 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.