
Quand un premier lab Pulumi fonctionne, le problème suivant arrive vite :
les valeurs importantes sont codees en dur dans __main__.py. Le nom de la VM,
la mémoire, 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 été rejoue le 1 avril 2026 sur KVM/libvirt avec une VM nommée
pulumi-config-vm, un réseau 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 secrète avec
require_secret(); - injecter cette valeur dans un flux cloud-init ;
- exporter les informations utiles avec
pulumi.export(); - vérifier 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 déjà prepare dans les guides précédents :
cd ~/pulumi-kvm-localsource venv/bin/activatepulumi stack select devVerification : la stack dev doit être 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 clé 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 - Définir la configuration et le secret
Section intitulée « Etape 3 - Définir la configuration et le secret »Ajoutez maintenant les valeurs de stack. Les cinq premières sont des valeurs simples. La dernière 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 entrée 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 création du réseau, du disque, du cloud-init et du domaine.
Le secret n'apparaît 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 - Vérifier la VM côté libvirt
Section intitulée « Etape 6 - Vérifier la VM côté libvirt »virsh list --all | grep pulumi-config-vmvirsh domstate pulumi-config-vmvirsh domiflist pulumi-config-vmVous devez voir :
- une VM
pulumi-config-vmen étatrunning; - une interface reliee au réseau
pulumi-config-net; - un modèle
virtio.
Cette etape prouve que vos valeurs de config ont bien pilote la création réelle 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 même : un bon guide Pulumi ne s'arrête pas a up, il
inclut toujours le retour a un état 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'entrée vers les valeurs de stack. Il
ne lit pas des variables Python globales. Il lit la configuration rattachée au
projet et a la stack selectionnee.
get() et get_int()
Section intitulée « get() et get_int() »Ces méthodes servent a récupérer des valeurs non secretes. Ici, elles pilotent le nom du réseau, le nom de la VM, la mémoire et le nombre de vCPU.
require_secret()
Section intitulée « require_secret() »require_secret() indique a Pulumi qu'une valeur doit être traitée
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 secrète.
pulumi.Output.format() permet de composer cette chaîne sans casser le modèle
de données de Pulumi.
pulumi.export()
Section intitulée « pulumi.export() »Les exports servent a publier les informations que vous voulez relire après
up. Ici, ils rendent le run plus lisible pour vérifier rapidement le nom de
la VM, l'utilisateur configure ou la mémoire choisie.
Pieges fréquents
Section intitulée « Pieges fréquents »| Symptôme | Cause probable | Solution |
|---|---|---|
Missing required configuration variable 'adminPassword' | Le secret n'a pas été défini | Rejouez pulumi config set --secret adminPassword ... --stack dev |
| Le secret apparaît 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 | Vérifiez pulumi stack select dev puis relancez preview |
| La VM porte encore l'ancien nom | La ressource existante n'a pas été recreee dans le bon contexte | Relisez le preview, appliquez puis vérifiez avec virsh |
A retenir
Section intitulée « A retenir »- Pulumi Config évite 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 résultat du run plus lisible.- Le bon réflexe reste
config -> preview -> up -> output -> verification -> destroy.