microsoft/environment-deployment
Generate, transfer, and consume environment-specific Azure, AKS, OSMO, ACR, and Azure ML deployment bundles. Use when: discovering deployed environment details; creating OSMO image manifests or platform values; preparing a HiL host; uploading or downloading deployment files through Azure Key Vault; or deploying with generated environment configuration.
npx skills add https://github.com/microsoft/physical-ai-toolchain --skill environment-deployment
Generate non-secret deployment details from Terraform desired state and read-only Azure, Kubernetes, and OSMO discovery. Store generated artifacts under the gitignored infrastructure/setup/generated/<environment>/ directory.
Produce a validated non-secret environment bundle or a host-bound HiL handoff whose target, ownership, transfer, and next operation are explicit. Keep discovery read-only and keep generated environment values outside tracked source.
Follow these rules for every environment bundle:
terraform apply, kubectl apply, Helm upgrade/install, OSMO update/set/delete commands, or Azure create/update/delete commands while generating a bundle.Create only the artifacts needed by the deployment:
infrastructure/setup/generated/<environment>/
├── deployment.json
├── osmo-platforms.yaml
├── osmo-images.json
└── azureml-instance-types.yaml
deployment.json is required. The other files are optional when the corresponding component is not deployed.
Use lowercase letters, numbers, and hyphens for <environment>. Keep artifact filenames stable because the Key Vault transfer scripts use this allowlist.
Use every available discovery tool. Record unavailable tools and skipped checks in deployment.json.
| Tool | Purpose | Required |
|-----------|-------------------------------------------------------------------|--------------------------------------|
| Terraform | Read desired resources and node-pool configuration | Yes |
| Azure CLI | Verify Azure identity and live resource metadata | Yes |
| jq | Select explicit non-secret fields and write JSON | Yes |
| kubectl | Verify AKS nodes, labels, taints, GPU capacity, and OSMO endpoint | When AKS is reachable |
| osmo | Verify the authenticated service and available pools | When an isolated profile is supplied |
| Helm | Read the deployed OSMO image version and values | When OSMO is deployed |
For private resources, connect to the VPN before live Azure, AKS, Key Vault, or OSMO checks.
This is an agent-led discovery workflow, not a static checked-in generator. The agent executes the available read-only commands below, renders files from the current Terraform and live resource data, validates the result, and reports every skipped probe. Do not reuse artifacts from another environment.
Collect or infer:
infrastructure/terraform$HOME/.kube/physical-ai-toolchain/<aks-cluster>.yamlCreate infrastructure/setup/generated/<environment>/. Do not create a tracked placeholder in this directory.
Run terraform output -json from the Terraform directory. Select only explicit output fields; never copy raw output or state into the bundle.
Read these values when present:
| Terraform output | Bundle use |
|----------------------|-------------------------------------------------|
| resource_group | Resource group name and location |
| key_vault_name | Key Vault bundle transfer |
| aks_cluster | AKS name and resource ID |
| node_pools | GPU pool VM sizes, priority, labels, and taints |
| container_registry | ACR name and login server |
| storage_account | Storage account name |
| azureml_workspace | Azure ML workspace name |
| osmo_workload_identity | OSMO workload identity ID and Entra metadata |
Fail when the resource group, Key Vault, or requested AKS/ACR values are missing. Do not infer names from naming conventions when Terraform exposes them.
Use read-only Azure CLI calls:
az account show.az group show.az aks show and compare its normalized resource ID with Terraform.az acr show and compare its name and login server with Terraform.az keyvault show; do not enumerate or read unrelated secret values.osmo_workload_identity with az identity show --ids, and confirm its ID, client ID, and tenant ID match Terraform.Stop on any identity or resource mismatch. Do not switch subscriptions silently.
When kubectl is available and AKS is reachable:
verify_existing_aks_kubeconfig before refreshing credentials.connect_aks from scripts/lib/common.sh only when the user requested credential setup.verify_kube_target.agentpool labelnode.kubernetes.io/instance-type labelstatus.allocatable["nvidia.com/gpu"]azureml/azureml-ingress-nginx-internal-lb and form http://<RFC1918-address> from its assigned ingress IP.Scale-to-zero pools may have no live nodes. Generate their configuration from Terraform and record live capacity as unavailable instead of omitting the pool.
Do not run osmo login during discovery. If the caller supplies an isolated authenticated profile, set XDG_CONFIG_HOME to it and run read-only checks:
osmo versionosmo pool list --format-type jsonWhen Helm is available, read the deployed OSMO release with the same isolated kubeconfig and context. Prefer global.osmoImageTag from deployed values for the image version. Fall back to OSMO_IMAGE_VERSION from infrastructure/setup/defaults.conf only for a new deployment.
Record whether OSMO and Helm verification succeeded. Do not copy profile data into the bundle.
Generate osmo-platforms.yaml from node_pools.
For each GPU pool:
agentpool and node.kubernetes.io/instance-type selectors.Exists or Equal toleration.nvidia.com/gpu NoSchedule toleration when the pool uses it.{{USER_GPU}}.services.configs.pools.default.platforms.1 when the pool is scaled to zero and capacity cannot be verified.USER_CPU, USER_MEMORY, USER_STORAGE, and USER_SHM_SIZE as instructional defaults unless the user supplies workload requirements.Use unique lowercase identifiers derived from the pool key. Preserve both braces in every OSMO Jinja expression.
Generate azureml-instance-types.yaml from the same pool data:
defaultinstancetype for CPU workloads.gpuspot for the first spot pool and gpu for the first regular pool when those names are unambiguous; otherwise use gpu-<pool>.1 for scale-to-zero pools without live capacity.The deployment consumes this file through 02-deploy-azureml-extension.sh --instance-types-manifest.
Generate osmo-images.json only when OSMO images are mirrored to ACR. Use this component allowlist:
agentbackend-listenerbackend-workerclientdelayed-job-monitorinit-containerloggerrouterserviceweb-uiworkerFor each component, read the digest for osmo/<component>:<image-version> with az acr manifest show-metadata. Confirm the repository tag has writes and deletes disabled with az acr repository show. Fail if a component, digest, or immutability setting is missing.
Write this shape:
{
"schema_version": 1,
"registry": "<acr-name>",
"login_server": "<acr-login-server>",
"image_version": "<version>",
"images": {
"<component>": {
"repository": "osmo/<component>",
"digest": "sha256:<64-lowercase-hex>"
}
}
}
Write deployment.json last. Use relative artifact filenames and SHA-256 digests. Include:
{
"schema_version": 1,
"environment": "<environment>",
"generated_at": "<UTC-RFC3339>",
"subscription_id": "<subscription-id>",
"tenant_id": "<tenant-id>",
"resource_group": "<resource-group>",
"location": "<azure-region>",
"key_vault_name": "<key-vault>",
"aks_cluster": "<aks-cluster>",
"aks_resource_id": "<aks-resource-id>",
"acr_name": "<acr-name-or-empty>",
"acr_login_server": "<login-server-or-empty>",
"azureml_workspace": "<workspace-or-empty>",
"storage_account": "<storage-account-or-empty>",
"osmo_service_url": "<private-osmo-url>",
"osmo_chart_version": "<chart-version-or-empty>",
"osmo_image_version": "<image-version-or-empty>",
"osmo_workflow_data_uri": "azure://<storage-account>/<container>/workflows/data",
"osmo_workload_identity": {
"id": "<user-assigned-managed-identity-resource-id>",
"principal_id": "<managed-identity-principal-id>",
"client_id": "<managed-identity-client-id>",
"tenant_id": "<managed-identity-tenant-id>"
},
"artifacts": {
"osmo_platforms": {"file": "osmo-platforms.yaml", "sha256": "<digest>"},
"osmo_images": {"file": "osmo-images.json", "sha256": "<digest>"},
"azureml_instance_types": {"file": "azureml-instance-types.yaml", "sha256": "<digest>"}
},
"verification": {
"terraform": true,
"azure_cli": true,
"kubectl": true,
"helm": true,
"osmo": true
}
}
Set osmo_workflow_data_uri from the configured OSMO workflow_data.credential.endpoint, not a naming convention. It must match azure://<account>/<container>/workflows/data. Omit optional artifact entries when their files do not exist. Set unavailable verification tools to false; do not claim checks that did not run.
Before using or uploading the bundle:
deployment.json matches the active subscription, Terraform resource group, Key Vault, and AKS resource ID.osmo_workflow_data_uri is the configured Azure workflow-data endpoint and osmo_workload_identity matches Terraform and Azure identity metadata....osmo-images.json against verify_acr_image_manifest when ACR is used.kubectl apply --dry-run=client -f for the Azure ML InstanceTypes when kubectl is available.git check-ignore infrastructure/setup/generated/<environment>/deployment.json succeeds.Pass generated artifacts explicitly; do not copy them back into tracked values/ or manifests/ directories.
| Deployment | Generated argument |
|--------------------|-------------------------------------------------------------------------------------------------------------------------|
| Azure ML extension | 02-deploy-azureml-extension.sh --instance-types-manifest <bundle>/azureml-instance-types.yaml |
| OSMO control plane | 03-deploy-osmo.sh --platform-values <bundle>/osmo-platforms.yaml --use-acr --image-manifest <bundle>/osmo-images.json |
Read the image version, service URL, AKS resource ID, and resource names from deployment.json. Run each deployment script with --config-preview first. Deployment scripts may change Azure or Kubernetes resources; obtain user confirmation before continuing from discovery into deployment.
Upload the allowlisted bundle from the trusted deployment host:
infrastructure/setup/upload-environment-bundle.sh --environment <environment> --config-preview
infrastructure/setup/upload-environment-bundle.sh --environment <environment>
The uploader requires Key Vault secret write permission. It never changes RBAC.
Each UTF-8 artifact must be no larger than 24,000 bytes. Omit an optional file and its deployment.json artifact entry together. Existing Key Vault versions may remain, but consumers follow the current deployment.json allowlist.
Do not run concurrent uploads for the same environment. The uploader publishes artifacts first and deployment.json last so consumers either receive a coherent bundle or fail its digest checks.
For a generic, non-HiL consumer, authenticate to Azure, connect to the private network or VPN, and download the bundle:
infrastructure/setup/download-environment-bundle.sh --environment <environment> --resource-group <resource-group> --config-preview
infrastructure/setup/download-environment-bundle.sh --environment <environment> --resource-group <resource-group>
The downloader requires the Key Vault Secrets User role and private endpoint connectivity when the vault is private.
Configure local clients from the protected bundle:
infrastructure/setup/connect-environment.sh --environment <environment> --config-preview
infrastructure/setup/connect-environment.sh --environment <environment>
Use --osmo-method dev --osmo-username <user> only for an explicitly approved development deployment. Use protected files with --password-file or --token-file for service authentication.
download-environment-bundle.sh and connect-environment.sh remain generic non-HiL utilities. They do not retrieve or configure host-bound HiL credentials.
Complete this journey when an existing OSMO backend and pool are ready: the environment owner publishes the generic non-secret bundle separately from the host-bound protected inputs, then the Ubuntu consumer connects only its owned local K3s target with the catalog-bound artifacts.
The environment owner runs infrastructure/setup/04-prepare-osmo-hil-node.sh after verifying the existing OSMO backend and pool. Supply the generated bundle, approved service URL, existing backend and pool, protected OSMO profile, pull-only registry configuration, and token expiry. The script publishes the generic bundle and the exact host-bound catalog separately, writing the catalog last.
Key Vault is the only scripted protected-artifact transfer. Before publication, the environment owner manually creates the exact secret resources and grants the Ubuntu identity data-plane access to each named inbound secret only. Use Key Vault Secrets User for inbound secrets and Key Vault Secrets Officer only for the host-specific CSR secret. Verify that the Ubuntu identity has no direct or inherited vault-wide data-plane role.
Key Vault networking and RBAC are manual environment-owner actions. The publisher does not assign roles, modify Key Vault networking, or make a private vault reachable. Complete any bounded network-access window and restore private-only access before the consumer continues.
The publisher reuses the exact catalog-pinned OSMO token and token-metadata secret versions when they are valid and unexpired. --renew-token forces a new issuance. An absent catalog or valid expired token metadata issues a new token. Stop on a malformed or inaccessible catalog, or a token-metadata binding or digest mismatch. The publisher does not delete token versions.
Manual SCP is outside this repository HiL flow. An operator may use it only as an out-of-band procedure that does not invoke repository HiL publisher, VPN, or consumer scripts and does not use retired transfer arguments.
After the Ubuntu host and any required VPN are ready, run the consumer boundary:
data-pipeline/setup/hil/02-connect-osmo-backend.sh \
--environment <environment> \
--host-name <host> \
--tenant-id <tenant-id> \
--subscription <subscription-id> \
--vault-name <vault>
The consumer retrieves the catalog and declared artifacts from Key Vault. It validates catalog structure, artifact digests, token metadata, token digest, backend binding, and expiry before any Kubernetes mutation. A Key Vault access, network, target, catalog, or integrity failure stops the run.
The consumer validates the exact catalog-bound inputs and changes only the owned local K3s target. It does not administer Azure resources, AKS, Key Vault networking or RBAC, or remote OSMO desired state. Do not use download-environment-bundle.sh or connect-environment.sh as the Ubuntu HiL path.
Return the generated bundle path, validation results, unavailable checks, and the exact next preview command. For HiL preparation, identify the trusted Key Vault publisher command, the local consumer command, and any environment-owner RBAC or network checkpoint that remains.
Take microsoft/environment-deployment from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.