vm
and configuration using Terraform and Ansible, integrated with secure secretmanagement via Vault and SOPS.
This generated module was created with dagger init as a starting point for VM-related
operations. It demonstrates key DevOps tasks such as decrypting secrets, applying
Terraform infrastructure changes, generating dynamic Ansible inventories, and
executing Ansible playbooks to configure VMs. The module is designed to be flexible
and extensible to support your infrastructure automation needs.
The primary function Bake orchestrates this workflow, accepting Terraform directories,
encrypted files, Vault credentials, and Ansible parameters as inputs. It optionally
decrypts SOPS-encrypted configuration files before applying Terraform operations,
then parses Terraform outputs to generate inventory files for Ansible. It supports
multiple inventory types and allows you to specify Ansible playbooks and credentials.
BakeHarvester is the same workflow for the bootstrap case, where no control
plane exists yet to provision against: it renders a Harvester VM's manifests
from the harvester-vm KCL module, applies them straight through the
Kubernetes API, waits for the guest agent to report an IP, and runs Ansible
against it — no OpenTofu and no Crossplane involved.
This module can be invoked from the Dagger CLI or programmatically via the SDK,
making it suitable for integrating into CI/CD pipelines, GitOps workflows, or
custom operator/controller logic.
Future enhancements planned include:
- Rendering manifests or configs to branches/PRs for GitOps-style deployments
- Seamless integration with SOPS for secret management and decryption
- Advanced Terraform execution and output parsing features
- Enhanced Ansible inventory generation and execution customization
- VM testing and validation steps post-provisioning
- Automated merge requests/PR handling post-deployment
This documentation serves both as a high-level overview and a detailed guide
to the module’s capabilities and intended use cases.
Installation
dagger install github.com/stuttgart-things/blueprints/vm@v3.2.0Entrypoint
Return Type
Vm Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
func (m *MyModule) Example() *dagger.Vm {
return dag.
Vm()
}@function
def example() -> dagger.Vm:
return (
dag.vm()
)@func()
example(): Vm {
return dag
.vm()
}Types
Vm 🔗
baseImage() 🔗
Return Type
String ! Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
base-imagefunc (m *MyModule) Example(ctx context.Context) string {
return dag.
Vm().
Baseimage(ctx)
}@function
async def example() -> str:
return await (
dag.vm()
.baseimage()
)@func()
async example(): Promise<string> {
return dag
.vm()
.baseImage()
}bakeFromGit() 🔗
Return Type
Directory !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| gitRepository | String ! | - | Repository to clone from GitHub |
| gitRef | String | "main" | Ref/Branch to checkout - If not specified, defaults to “main” |
| gitToken | Secret | - | Github token for authentication (private repositories) |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
bake-from-git --git-repository stringfunc (m *MyModule) Example(gitRepository string) *dagger.Directory {
return dag.
Vm().
Bakefromgit(gitRepository)
}@function
def example(gitrepository: str) -> dagger.Directory:
return (
dag.vm()
.bakefromgit(gitrepository)
)@func()
example(gitRepository: string): Directory {
return dag
.vm()
.bakeFromGit(gitRepository)
}bakeHarvester() 🔗
BakeHarvester provisions a Harvester / KubeVirt VM straight through the Kubernetes API and then configures it with Ansible — no Crossplane, no OpenTofu, no control plane of any kind on the target side.
This is the bootstrap counterpart to BakeLocal: same shape (provision, read the machine’s address back, hand it to Ansible), but the provisioning step is “render manifests and apply them” instead of “terraform apply”. It exists for the chicken-and-egg case — the first VM on a Harvester cluster, the one that will go on to run the Crossplane management cluster that provisions every VM after it.
The pipeline:
- render PVC + cloud-init Secret + VirtualMachine from the harvester-vm KCL module (dagger/kcl)
- kubectl apply them against Harvester (dagger/kubernetes)
- poll the resulting VirtualMachineInstance until it is Running AND the guest agent has reported an IP
- run Ansible against that IP (dagger/ansible, via ExecuteAnsible)
The returned directory carries the rendered manifests, the generated inventory and outputs.json ({“vm_ips”: […]}) — the same output contract BakeLocal uses, so downstream consumers do not care which of the two provisioned the machine.
Note this is a one-shot, imperative provisioner: there is no reconcile loop and no drift correction. Updating or deleting the VM afterwards is kubectl’s job (or Crossplane’s, once the management cluster this VM bootstraps is up).
Return Type
Directory !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| kubeConfig | Secret ! | - | Kubeconfig for the Harvester cluster the VM is created on. |
| vmName | String ! | - | Name of the VM. Forced into the KCL render as |
| namespace | String | "default" | Namespace for PVC, Secret and VirtualMachine. Forced into the KCL render
as |
| ociSource | String | "ghcr.io/stuttgart-things/harvester-vm:0.2.0" | OCI reference of the harvester-vm KCL module. |
| kclParametersFile | File | - | KCL parameters as a YAML file (imageId, storageClass, storage, cpuCores, memory, networkName, cloudInitSshKey, cloudInitPassword, …). Prefer this over kclParameters for anything sensitive: the file is
mounted into the render container, whereas –kcl-parameters values become
operation arguments and are echoed by |
| kclParameters | String | - | KCL parameters as comma-separated key=value pairs. Override the file. Do not put credentials here — see kclParametersFile. |
| encryptedFile | File | - | SOPS-encrypted KCL parameters file. Decrypted in-memory and used instead of kclParametersFile; the plaintext never becomes an operation argument. |
| sopsKey | Secret | - | AGE key for decrypting encryptedFile. |
| skipNamespace | Boolean | false | Skip creating the target namespace. By default BakeHarvester creates it (idempotently) first, because kubectl apply does not and a missing namespace is the most common way a bootstrap run dies on line one. |
| waitTimeout | Integer | 900 | Seconds to wait for the VM to report an IP. Generous by default: the guest has to boot, install/start qemu-guest-agent and get a DHCP lease. |
| waitInterval | Integer | 15 | Seconds between VMI polls. |
| ansiblePlaybooks | String | - | Ansible playbooks to run against the VM, comma-separated. Empty skips the Ansible stage entirely (render + apply + wait only). |
| ansibleRequirementsFile | File | - | No description provided |
| ansibleUser | Secret | - | No description provided |
| ansiblePassword | Secret | - | No description provided |
| ansibleParameters | String | - | No description provided |
| envSecrets | Secret | - | Extra environment for the Ansible container, as a secret in dotenv format (NAME=value per line), for playbooks using lookup(‘env’, …). |
| vaultRoleId | Secret | - | No description provided |
| vaultSecretId | Secret | - | No description provided |
| vaultUrl | Secret | - | No description provided |
| vmiAppearTimeout | Integer | 120 | Seconds to wait for the VMI object to exist at all before giving up. See WaitForVmIp; 0 folds the check back into –wait-timeout. |
| ansibleWaitTimeout | Integer | 30 | Seconds to wait after the IP appears before Ansible connects. The agent reports an address slightly before sshd is reliably up. |
| requirementsTemplate | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements.yaml.tmpl" | No description provided |
| requirementsData | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements-data.yaml" | No description provided |
| inventoryType | String | "simple" | Inventory type: “simple” (default [all] group) or “cluster”. |
| cacheBuster | String | "" | Any value that changes between runs — a timestamp, a CI run id. Forces a fresh fetch of the remote Ansible requirements instead of a cached render. The kubectl apply gets its own stamp regardless; see stampManifest. |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
bake-harvester --kube-config env:MYSECRET --vm-name stringfunc (m *MyModule) Example(kubeConfig *dagger.Secret, vmName string) *dagger.Directory {
return dag.
Vm().
Bakeharvester(kubeConfig, vmName)
}@function
def example(kubeconfig: dagger.Secret, vmname: str) -> dagger.Directory:
return (
dag.vm()
.bakeharvester(kubeconfig, vmname)
)@func()
example(kubeConfig: Secret, vmName: string): Directory {
return dag
.vm()
.bakeHarvester(kubeConfig, vmName)
}bakeLocal() 🔗
Return Type
Directory !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| terraformDir | Directory ! | - | No description provided |
| operation | String | "apply" | No description provided |
| variables | String | - | e.g., “cpu=4,ram=4096,storage=100” |
| encryptedFile | File | - | No description provided |
| sopsKey | Secret | - | No description provided |
| awsAccessKeyId | Secret | - | No description provided |
| awsSecretAccessKey | Secret | - | No description provided |
| vaultRoleId | Secret | - | No description provided |
| vaultSecretId | Secret | - | No description provided |
| vaultToken | Secret | - | vaultToken |
| vaultUrl | Secret | - | No description provided |
| ansiblePlaybooks | String | - | No description provided |
| ansibleRequirementsFile | File | - | No description provided |
| ansibleUser | Secret | - | No description provided |
| ansiblePassword | Secret | - | No description provided |
| envSecrets | Secret | - | Extra environment for the Ansible container, as a secret in dotenv format (NAME=value per line), for playbooks using lookup(‘env’, …). |
| ansibleParameters | String | - | No description provided |
| ansibleInventoryType | String | "default" | No description provided |
| ansibleWaitTimeout | Integer | 30 | No description provided |
| requirementsTemplate | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements.yaml.tmpl" | No description provided |
| requirementsData | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements-data.yaml" | No description provided |
| cacheBuster | String | "" | Any value that changes between runs – a timestamp, a CI run id. Threaded down to CreateAnsibleRequirementFiles, where it forces a fresh fetch of the remote requirements instead of a cached render. Leave empty to keep the previous behaviour. Worth passing from CI: the dagger-labda runner keeps its engine between runs, so without it a merged collection bump can stay invisible to the pipeline indefinitely. |
| terraformMaxRetries | Integer | 3 | No description provided |
| terraformRetryDelay | Integer | 10 | No description provided |
| inventoryType | String | "simple" | Inventory type: “simple” (default [all] group) or “cluster” (master/worker groups) |
| exportPaths | String | - | Comma-separated list of file paths to export from the Ansible container |
| agePublicKey | Secret | - | AGE public key for SOPS encryption of exported files |
| sopsFileExtension | String | "yaml" | File extension for SOPS encryption (e.g., “yaml”, “json”) |
| sopsConfig | File | - | SOPS config file (.sops.yaml) |
| exportTargetNames | String | - | Comma-separated list of target filenames for exported files (maps 1:1 to exportPaths) If not set, original filenames are used |
| exportDestinationPath | String | "encrypted-exports" | Destination path for encrypted exports within the result directory Use “./” to place files at the root level (no subdirectory) |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
bake-local --terraform-dir DIR_PATHfunc (m *MyModule) Example(terraformDir *dagger.Directory) *dagger.Directory {
return dag.
Vm().
Bakelocal(terraformDir)
}@function
def example(terraformdir: dagger.Directory) -> dagger.Directory:
return (
dag.vm()
.bakelocal(terraformdir)
)@func()
example(terraformDir: Directory): Directory {
return dag
.vm()
.bakeLocal(terraformDir)
}bakeLocalByProfile() 🔗
Return Type
Directory !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| src | Directory ! | - | No description provided |
| profile | File | - | No description provided |
| sopsKey | Secret | - | No description provided |
| awsAccessKeyId | Secret | - | No description provided |
| awsSecretAccessKey | Secret | - | No description provided |
| vaultRoleId | Secret | - | No description provided |
| vaultSecretId | Secret | - | No description provided |
| vaultToken | Secret | - | vaultToken |
| vaultUrl | Secret | - | No description provided |
| ansibleUser | Secret | - | No description provided |
| ansiblePassword | Secret | - | No description provided |
| envSecrets | Secret | - | Extra environment for the Ansible container, as a secret in dotenv format (NAME=value per line), for playbooks using lookup(‘env’, …). |
| requirementsTemplate | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements.yaml.tmpl" | No description provided |
| requirementsData | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements-data.yaml" | No description provided |
| cacheBuster | String | "" | Any value that changes between runs – a timestamp, a CI run id. Threaded down to CreateAnsibleRequirementFiles, where it forces a fresh fetch of the remote requirements instead of a cached render. Deliberately NOT a field in execution.yaml: it is a property of THIS run, not of the VM being built, and committing one would make it stale by definition. pr-vm-deploy.yaml should pass the run id. |
| inventoryType | String | "simple" | Inventory type: “simple” (default [all] group) or “cluster” (master/worker groups) |
| agePublicKey | Secret | - | AGE public key for SOPS encryption of exported files |
| sopsConfig | File | - | SOPS config file (.sops.yaml) |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
bake-local-by-profile --src DIR_PATHfunc (m *MyModule) Example(src *dagger.Directory) *dagger.Directory {
return dag.
Vm().
Bakelocalbyprofile(src)
}@function
def example(src: dagger.Directory) -> dagger.Directory:
return (
dag.vm()
.bakelocalbyprofile(src)
)@func()
example(src: Directory): Directory {
return dag
.vm()
.bakeLocalByProfile(src)
}commitToGit() 🔗
CommitToGit commits a directory of files to a GitHub repository branch. Optionally creates a new branch and opens a pull request.
Return Type
String !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| sourceDir | Directory ! | - | Directory containing files to commit |
| repository | String ! | - | Repository in “owner/repo” format |
| branchName | String | "main" | Branch name for git operations |
| commitMessage | String | "Add files via Dagger" | Commit message |
| destinationPath | String | "/" | Destination path within the repository |
| gitToken | Secret ! | - | GitHub token for authentication |
| createBranch | String | - | If non-empty, create this branch (from branchName as base) and commit there instead |
| createPr | Boolean | - | If true (and createBranch set), open a PR from the new branch back to branchName |
| prTitle | String | - | PR title (defaults to commitMessage if empty) |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
commit-to-git --source-dir DIR_PATH --repository string --git-token env:MYSECRETfunc (m *MyModule) Example(ctx context.Context, sourceDir *dagger.Directory, repository string, gitToken *dagger.Secret) string {
return dag.
Vm().
Committogit(ctx, sourceDir, repository, gitToken)
}@function
async def example(sourcedir: dagger.Directory, repository: str, gittoken: dagger.Secret) -> str:
return await (
dag.vm()
.committogit(sourcedir, repository, gittoken)
)@func()
async example(sourceDir: Directory, repository: string, gitToken: Secret): Promise<string> {
return dag
.vm()
.commitToGit(sourceDir, repository, gitToken)
}executeAnsible() 🔗
Return Type
Boolean !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| src | Directory | - | No description provided |
| playbooks | String ! | - | No description provided |
| requirements | File | - | No description provided |
| inventory | File | - | No description provided |
| hosts | String | - | Comma-separated list of hosts (e.g., “192.168.1.10,192.168.1.11”) Used to generate inventory if inventory file is not provided |
| parameters | String | - | No description provided |
| parametersFile | File | - | Path to a YAML file containing parameters (lower priority) |
| vaultAppRoleId | Secret | - | No description provided |
| vaultSecretId | Secret | - | No description provided |
| vaultUrl | Secret | - | No description provided |
| sshUser | Secret | - | No description provided |
| sshPassword | Secret | - | No description provided |
| envSecrets | Secret | - | Extra environment for the Ansible container, as a secret in dotenv format (NAME=value per line). Needed by playbooks that resolve values with lookup(‘env’, …), which is evaluated on the controller, not the target – e.g. sthings.container.kind_machinery reads SOPS_AGE_KEY that way. |
| requirementsTemplate | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements.yaml.tmpl" | No description provided |
| requirementsData | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements-data.yaml" | No description provided |
| inventoryType | String | "simple" | Inventory type: “simple” (default [all] group) or “cluster” (master/worker groups) |
| cacheBuster | String | "" | Any value that changes between runs – a timestamp, a CI run id. Threaded into CreateAnsibleRequirementFiles, where it forces a fresh fetch of the remote requirements instead of a cached render. Leave empty to keep the previous behaviour. |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
execute-ansible --playbooks stringfunc (m *MyModule) Example(ctx context.Context, playbooks string) bool {
return dag.
Vm().
Executeansible(ctxplaybooks)
}@function
async def example(playbooks: str) -> bool:
return await (
dag.vm()
.executeansible(playbooks)
)@func()
async example(playbooks: string): Promise<boolean> {
return dag
.vm()
.executeAnsible(playbooks)
}executeAnsibleEncryptAndCommit() 🔗
ExecuteAnsibleEncryptAndCommit runs Ansible playbooks, extracts files from the container, encrypts them with SOPS, and commits the encrypted files to a Git repository.
Return Type
String !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| src | Directory | - | No description provided |
| playbooks | String ! | - | No description provided |
| exportPaths | String ! | - | Comma-separated list of file paths to export from the Ansible container |
| requirements | File | - | No description provided |
| inventory | File | - | No description provided |
| hosts | String | - | Comma-separated list of hosts (e.g., “192.168.1.10,192.168.1.11”) |
| parameters | String | - | No description provided |
| parametersFile | File | - | No description provided |
| vaultAppRoleId | Secret | - | No description provided |
| vaultSecretId | Secret | - | No description provided |
| vaultUrl | Secret | - | No description provided |
| sshUser | Secret | - | No description provided |
| sshPassword | Secret | - | No description provided |
| envSecrets | Secret | - | Extra environment for the Ansible container, as a secret in dotenv format (NAME=value per line). Needed by playbooks that resolve values with lookup(‘env’, …), which is evaluated on the controller, not the target. |
| requirementsTemplate | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements.yaml.tmpl" | No description provided |
| requirementsData | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements-data.yaml" | No description provided |
| inventoryType | String | "simple" | Inventory type: “simple” (default [all] group) or “cluster” (master/worker groups) |
| cacheBuster | String | "" | Any value that changes between runs – a timestamp, a CI run id. Threaded into CreateAnsibleRequirementFiles, where it forces a fresh fetch of the remote requirements instead of a cached render. Leave empty to keep the previous behaviour. |
| agePublicKey | Secret ! | - | AGE public key for SOPS encryption |
| sopsFileExtension | String | "yaml" | File extension for SOPS encryption (e.g., “yaml”, “json”) |
| sopsConfig | File | - | SOPS config file (.sops.yaml) |
| gitRepository | String ! | - | Git repository in “owner/repo” format |
| gitBranch | String | "main" | Git branch name |
| gitCommitMessage | String | "Add encrypted files from Ansible execution" | Git commit message |
| gitDestinationPath | String | "/" | Destination path within the git repository |
| gitToken | Secret ! | - | GitHub token for authentication |
| gitCreateBranch | String | - | If non-empty, create this branch (from gitBranch as base) and commit there instead |
| gitCreatePr | Boolean | - | If true (and gitCreateBranch set), open a PR from the new branch back to gitBranch |
| gitPrTitle | String | - | PR title (defaults to gitCommitMessage if empty) |
| exportTargetNames | String | - | Comma-separated list of target filenames for exported files (maps 1:1 to exportPaths) If not set, original filenames are used |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
execute-ansible-encrypt-and-commit --playbooks string --export-paths string --age-public-key env:MYSECRET --git-repository string --git-token env:MYSECRETfunc (m *MyModule) Example(ctx context.Context, playbooks string, exportPaths string, agePublicKey *dagger.Secret, gitRepository string, gitToken *dagger.Secret) string {
return dag.
Vm().
Executeansibleencryptandcommit(ctxplaybooks, exportPaths, agePublicKey, gitRepository, gitToken)
}@function
async def example(playbooks: str, exportpaths: str, agepublickey: dagger.Secret, gitrepository: str, gittoken: dagger.Secret) -> str:
return await (
dag.vm()
.executeansibleencryptandcommit(playbooks, exportpaths, agepublickey, gitrepository, gittoken)
)@func()
async example(playbooks: string, exportPaths: string, agePublicKey: Secret, gitRepository: string, gitToken: Secret): Promise<string> {
return dag
.vm()
.executeAnsibleEncryptAndCommit(playbooks, exportPaths, agePublicKey, gitRepository, gitToken)
}executeAnsibleWithExport() 🔗
ExecuteAnsibleWithExport runs Ansible playbooks and exports specified files from the container. Same parameters as ExecuteAnsible plus exportPaths (comma-separated file paths to extract).
Return Type
Directory !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| src | Directory | - | No description provided |
| playbooks | String ! | - | No description provided |
| exportPaths | String ! | - | Comma-separated list of file paths to export from the Ansible container |
| requirements | File | - | No description provided |
| inventory | File | - | No description provided |
| hosts | String | - | Comma-separated list of hosts (e.g., “192.168.1.10,192.168.1.11”) Used to generate inventory if inventory file is not provided |
| parameters | String | - | No description provided |
| parametersFile | File | - | Path to a YAML file containing parameters (lower priority) |
| vaultAppRoleId | Secret | - | No description provided |
| vaultSecretId | Secret | - | No description provided |
| vaultUrl | Secret | - | No description provided |
| sshUser | Secret | - | No description provided |
| sshPassword | Secret | - | No description provided |
| envSecrets | Secret | - | Extra environment for the Ansible container, as a secret in dotenv format (NAME=value per line). Needed by playbooks that resolve values with lookup(‘env’, …), which is evaluated on the controller, not the target. |
| requirementsTemplate | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements.yaml.tmpl" | No description provided |
| requirementsData | String | "https://raw.githubusercontent.com/stuttgart-things/ansible/refs/heads/main/templates/requirements-data.yaml" | No description provided |
| inventoryType | String | "simple" | Inventory type: “simple” (default [all] group) or “cluster” (master/worker groups) |
| cacheBuster | String | "" | Any value that changes between runs – a timestamp, a CI run id. Threaded into CreateAnsibleRequirementFiles, where it forces a fresh fetch of the remote requirements instead of a cached render. Leave empty to keep the previous behaviour. |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
execute-ansible-with-export --playbooks string --export-paths stringfunc (m *MyModule) Example(playbooks string, exportPaths string) *dagger.Directory {
return dag.
Vm().
Executeansiblewithexport(playbooks, exportPaths)
}@function
def example(playbooks: str, exportpaths: str) -> dagger.Directory:
return (
dag.vm()
.executeansiblewithexport(playbooks, exportpaths)
)@func()
example(playbooks: string, exportPaths: string): Directory {
return dag
.vm()
.executeAnsibleWithExport(playbooks, exportPaths)
}executeTerraform() 🔗
ExecuteTerraform runs terraform with optional SOPS-encrypted file decryption,
optional Kubernetes-secret retrieval (e.g. VAULT_TOKEN injected as a tfvar),
optional kubeconfig backend support, and AWS/Vault credentials. Returns the
terraform working directory after execution. This is the canonical Terraform
execution entry point — the configuration module previously hosted a
duplicate (TerraformApply) which has been removed.
Return Type
Directory !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| terraformDir | Directory ! | - | Directory containing terraform configurations |
| operation | String | "apply" | Terraform operation to execute |
| variables | String | - | Comma-separated terraform variables (e.g. “name=patrick,food=schnitzel”) |
| awsAccessKeyId | Secret | - | AWS access key ID for S3/MinIO backend |
| awsSecretAccessKey | Secret | - | AWS secret access key for S3/MinIO backend |
| vaultRoleId | Secret | - | Vault role ID secret |
| vaultSecretId | Secret | - | Vault secret ID secret |
| vaultToken | Secret | - | Vault token secret |
| sopsAgeKey | Secret | - | AGE key for SOPS decryption of encryptedFiles / encryptedKubeConfig |
| encryptedFiles | String | - | Comma-separated list of SOPS-encrypted file paths under terraformDir to decrypt (e.g. “terraform.tfvars.sops.json,secrets.sops.yaml”) |
| kubeConfig | Secret | - | Kubeconfig secret for Kubernetes state backend access (plaintext) |
| kubeConfigPath | String | "/root/.kube/config" | Path to mount the kubeconfig inside the container (must match backend config_path in backend.tf) |
| encryptedKubeConfig | File | - | SOPS-encrypted kubeconfig file; decrypted with sopsAgeKey and used for kubectl |
| kubeSecretName | String | - | Kubernetes secret name to read (e.g. “vault-root-token”) |
| kubeSecretNamespace | String | - | Kubernetes namespace for the secret |
| kubeSecretJsonpath | String | - | JSONPath expression to extract from the Kubernetes secret (e.g. “.data.root_token”) |
| kubeSecretTfVar | String | - | Terraform variable name to set from the Kubernetes secret value (e.g. “vault_token” becomes -var vault_token=) |
| exportTfOutput | Boolean | - | Run terraform output –json after apply and write result to output.json |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
execute-terraform --terraform-dir DIR_PATHfunc (m *MyModule) Example(terraformDir *dagger.Directory) *dagger.Directory {
return dag.
Vm().
Executeterraform(terraformDir)
}@function
def example(terraformdir: dagger.Directory) -> dagger.Directory:
return (
dag.vm()
.executeterraform(terraformdir)
)@func()
example(terraformDir: Directory): Directory {
return dag
.vm()
.executeTerraform(terraformDir)
}outputTerraformRun() 🔗
OutputTerraformRun runs terraform output --json against an already-applied
terraform directory. Supports AWS S3/MinIO and Kubernetes state backends.
Return Type
String !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| terraformDir | Directory ! | - | Directory containing terraform state (output of ExecuteTerraform) |
| awsAccessKeyId | Secret | - | AWS access key ID for S3/MinIO backend |
| awsSecretAccessKey | Secret | - | AWS secret access key for S3/MinIO backend |
| kubeConfig | Secret | - | Kubeconfig secret for Kubernetes backend access |
| kubeConfigPath | String | "/root/.kube/config" | Path to mount the kubeconfig inside the container (must match backend config_path in backend.tf) |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
output-terraform-run --terraform-dir DIR_PATHfunc (m *MyModule) Example(ctx context.Context, terraformDir *dagger.Directory) string {
return dag.
Vm().
Outputterraformrun(ctx, terraformDir)
}@function
async def example(terraformdir: dagger.Directory) -> str:
return await (
dag.vm()
.outputterraformrun(terraformdir)
)@func()
async example(terraformDir: Directory): Promise<string> {
return dag
.vm()
.outputTerraformRun(terraformDir)
}outputTerraformRunWithCreds() 🔗
OutputTerraformRunWithCreds is a back-compat alias for OutputTerraformRun limited to the AWS credentials path.
Return Type
String !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| terraformDir | Directory ! | - | No description provided |
| awsAccessKeyId | Secret | - | No description provided |
| awsSecretAccessKey | Secret | - | No description provided |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
output-terraform-run-with-creds --terraform-dir DIR_PATHfunc (m *MyModule) Example(ctx context.Context, terraformDir *dagger.Directory) string {
return dag.
Vm().
Outputterraformrunwithcreds(ctx, terraformDir)
}@function
async def example(terraformdir: dagger.Directory) -> str:
return await (
dag.vm()
.outputterraformrunwithcreds(terraformdir)
)@func()
async example(terraformDir: Directory): Promise<string> {
return dag
.vm()
.outputTerraformRunWithCreds(terraformDir)
}renderHarvesterVm() 🔗
RenderHarvesterVm renders the PVC, cloud-init Secret and VirtualMachine from the harvester-vm KCL module into a single apply-ready multi-document YAML.
Exported on its own so a run can be inspected before anything touches a
cluster (... render-harvester-vm ... | tee vm.yaml), and so the render can
be reused for GitOps-style flows.
The KCL module’s top-level value is an items: list; dagger/kcl’s Run
post-processor converts exactly that shape into multi-document YAML when
formatOutput is on (its default), so the yq/awk splitting the module’s README
shows for the bare kcl run case is not needed here.
Return Type
File !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| ociSource | String | "ghcr.io/stuttgart-things/harvester-vm:0.2.0" | No description provided |
| kclParametersFile | File | - | No description provided |
| kclParameters | String | - | No description provided |
| encryptedFile | File | - | No description provided |
| sopsKey | Secret | - | No description provided |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
render-harvester-vmfunc (m *MyModule) Example() *dagger.File {
return dag.
Vm().
Renderharvestervm()
}@function
def example() -> dagger.File:
return (
dag.vm()
.renderharvestervm()
)@func()
example(): File {
return dag
.vm()
.renderHarvesterVm()
}waitForVmIp() 🔗
WaitForVmIp polls a KubeVirt VirtualMachineInstance until it is Running and the in-guest QEMU guest agent has reported an IP address, and returns that address.
Two things this deliberately does NOT do:
It does not use kubectl wait. The kubernetes module wraps every Command in
(... 2>&1) || true, so a wait that times out would come back as a success
carrying an error message — the run would sail on and point Ansible at
nothing. Polling and enforcing the deadline here keeps the failure a failure.
It does not issue the same kubectl call twice. Dagger caches an exec by the
digest of its arguments and inputs, so an unchanged kubectl get would be
served from cache and the loop would spin forever on the first (empty)
answer. The per-attempt marker in additionalCommand is what keeps every poll
a real call — it is a shell comment, so it changes the digest and nothing
else.
Return Type
String !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| kubeConfig | Secret ! | - | Kubeconfig for the cluster running the VM. |
| vmName | String ! | - | VM name (the VMI carries the same name as its VirtualMachine). |
| namespace | String | "default" | No description provided |
| waitTimeout | Integer | 900 | No description provided |
| waitInterval | Integer | 15 | No description provided |
| vmiAppearTimeout | Integer | 120 | Seconds to wait for the VMI object to exist at all before giving up. The VMI appears seconds after the VirtualMachine is applied; it is the IP that takes minutes. Bounding the two separately turns “the VM was never created” into a fast failure instead of a full –wait-timeout. 0 disables it, folding the check back into –wait-timeout. |
Example
dagger -m github.com/stuttgart-things/blueprints/vm@1b60e838b79287aea859cb9c612865ae0812b5f1 call \
wait-for-vm-ip --kube-config env:MYSECRET --vm-name stringfunc (m *MyModule) Example(ctx context.Context, kubeConfig *dagger.Secret, vmName string) string {
return dag.
Vm().
Waitforvmip(ctx, kubeConfig, vmName)
}@function
async def example(kubeconfig: dagger.Secret, vmname: str) -> str:
return await (
dag.vm()
.waitforvmip(kubeconfig, vmname)
)@func()
async example(kubeConfig: Secret, vmName: string): Promise<string> {
return dag
.vm()
.waitForVmIp(kubeConfig, vmName)
}