Azure DevOps extension that adds a Terraform pipeline task and a Terraform tab on the build results page. The tab renders plan.json (from terraform show -json) as a change table and a high-level diagram derived from resource_changes.
- Node.js 20+ for building
- An Azure DevOps publisher (create one in the Visual Studio Marketplace manage portal if you do not have one)
- Personal access token with Marketplace (Manage) scope for publishing
infra/— minimal root module using the null provider only (no Azure/AWS/GCP credentials). Local backend by default.azure-pipelines.yml— installs the extension task subzone.ado-tf-agent.terraform-task.Terraform@0, then runs init → validate → plan (with Publish plan JSON artifact) → apply.
In Azure DevOps
- Install the extension on the same organization that runs the pipeline (otherwise YAML fails with A task is missing):
- Organization Settings (⚙ bottom left) → Extensions → Shared → Upload extension → choose
dist/subzone.ado-tf-agent-*.vsixfrom a localnpm run package, or install from the Marketplace if it is published.
- Organization Settings (⚙ bottom left) → Extensions → Shared → Upload extension → choose
- Create a pipeline from
azure-pipelines.ymland run it. - After Plan, open the completed build → Terraform tab (build results).
There is no substitute pipeline in this repo: testing the extension means installing it on your org, then running azure-pipelines.yml. Plain Terraform scripts would not exercise the task or the build Terraform tab.
Marketplace tasks use four dot-separated parts before @, not three:
publisher.extensionId.contributionId.taskJsonName@major
Example for this extension: subzone.ado-tf-agent.terraform-task.Terraform@0
- Terraform is the
namefield fromtasks/Terraform/task.json(not the friendly name). - Using only
subzone.ado-tf-agent.terraform-task@0is invalid and produces task is missing even when the extension is installed.
If the name is correct and it still fails, the extension is not on this org: install it under Organization settings → Extensions, then re-run the pipeline.
Locally (without the extension):
cd infra && terraform init -input=false && terraform validate && terraform plan -out=tfplan- Confirm
publisherinvss-extension.json(currently subzone). - Update
repository.uri, taskauthor/helpMarkDownintasks/Terraform/task.json, and branding as needed. - Replace
images/extension-icon.pngwith a 128×128 PNG for the Marketplace.
npm install
npm run packageThis produces dist/subzone.ado-tf-agent-<version>.vsix (see vss-extension.json).
Workflow: .github/workflows/extension.yml.
| Trigger | What happens |
|---|---|
Push / PR to main or master |
Builds and uploads the VSIX as a workflow artifact (no Marketplace publish). |
Push a version tag v* (e.g. v0.2.0) |
Automatically syncs version across all files, builds, then publishes that VSIX to the Marketplace. |
| Run workflow manually | Builds; set Publish VSIX to Marketplace to true to publish. |
Version management: The extension uses automated version sync from git tags. Simply create and push a version tag:
git tag v0.2.0
git push origin v0.2.0The workflow automatically updates vss-extension.json, task.json, and all package versions to match. See VERSIONING.md for details.
Repository secret (Settings → Secrets and variables → Actions):
AZURE_DEVOPS_EXT_TOKEN— Azure DevOps PAT with Marketplace (Manage) scope, tied to the same identity that owns publisher subzone.
Publishing uses tfx extension publish with your public manifest ("public": true); you can still complete or adjust listing details in the publisher portal afterward.
Install dependencies for local iteration:
cd tasks/Terraform && npm install && npm run build
cd ../../ui && npm install && npm run buildPrivate sharing is the fastest way to validate on real pipelines:
npx tfx-cli extension publish \
--manifest-globs vss-extension.json \
--token <PAT> \
--share-with <your-org-name>Or upload the .vsix under Organization settings → Extensions → Shared → Upload extension.
The manifest sets "public": true so the package is eligible for the public Marketplace. Publishing still requires:
- A valid privacy policy URL (often on your publisher profile or in the extension’s Marketplace listing).
- Support / contact information as required by the submission wizard.
- Microsoft review (not instant; timelines vary).
Publish with tfx extension publish without --share-with, then finish any remaining steps in the publisher portal. You can keep using --share-with for private testing of the same or another version if needed.
pool:
vmImage: ubuntu-latest
steps:
- task: subzone.ado-tf-agent.terraform-task.Terraform@0
displayName: Install Terraform
inputs:
command: install
terraformVersion: 1.7.5
- task: subzone.ado-tf-agent.terraform-task.Terraform@0
displayName: Terraform init (Azure backend)
inputs:
command: init
workingDirectory: infra
backendType: azurerm
azureResourceGroup: rg-terraform-state
azureStorageAccount: tfstateacct
azureContainer: tfstate
azureStateKey: myapp.tfstate
- task: subzone.ado-tf-agent.terraform-task.Terraform@0
displayName: Terraform plan
inputs:
command: plan
workingDirectory: infra
publishPlanArtifact: true
planArtifactName: terraform-planUse service connections or Azure CLI / environment variables for provider credentials as you normally would for Terraform on hosted agents.
- Task — Node 20 handler (
azure-pipelines-task-lib,azure-pipelines-tool-lib) for cross-platform agents. - UI —
ms.vss-build-web.build-results-tabloadsui/dist/planTab.html, usesazure-devops-extension-sdkandBuildRestClient.getArtifactContentZipto read the published artifact. - Diagram — Mermaid
flowchartwith subgraphs per resource type prefix; a future iteration can parseterraform graphor dependency metadata for richer edges.
Request only the scopes you need. This manifest uses vso.build and vso.build_execute. Review with your security team before broad rollout.
Before publishing, you can drop TypeScript from the packaged task folder:
cd tasks/Terraform && npm prune --omit=dev && cd ../..
npm run packageRe-run npm install in tasks/Terraform afterward if you need to compile again locally.