Fragmented Thought

Using environment specific values in Azure DevOps Helm task

By

Published

Lance Gliser

Original approach idea credit goes to Ryan Hatfield who helpfully pulled my understanding of pipeline possibilities along. My work here builds on that inspiration.

Why a template?

Foremost, I need to explain this extra layer of abstraction. It is 100% ok to have the stages in your pipeline simply repeat steps. It's actually quite easy to do while avoid duplication using pipeline variables and yaml anchors.

We started reaching for templates not knowing about yaml anchors, but I'm glad we did. We now have over 150 pipelines in the company, and the number is growing fast. Anchors would not solve that problem. Sharing task definitions between pipelines requires templates. Get on board early, and be flexible.

Template: templates/helm-upgrade.yaml

Here's our standard template:

parameters: # The K8s namespace as passed down from the {Environment.Resource} most often. - name: namespace type: string - name: releaseName type: string # The location of the chart downloaded to the stage from the artifact - name: chartPath type: string default: $(Pipeline.Workspace)/_Artifact/drop/helm # Parameters to pass along to the helm command - name: arguments type: string default: "--create-namespace" # This will allow us to pass any helm chart values through as a parameter to helm upgrade template. - name: values type: object default: {} steps: # Convert parameters.values into a local file on the stage operator - script: | echo '${{ convertToJson(parameters.values) }}' > ./localValues.json cat ./localValues.json displayName: Create values file # I ship it - task: HelmDeploy@0 displayName: "helm upgrade" inputs: namespace: ${{ parameters.namespace }} command: upgrade chartType: FilePath chartPath: ${{ parameters.chartPath }} releaseName: ${{ parameters.releaseName }} valueFile: ./localValues.json arguments: ${{ parameters.arguments }} waitForExecution: false

This can be used as a template shared between pipelines in the project, or even externalized to another repository of pipeline templates you can download on demand.

Pipeline: deployment.yaml

The primary deployment files can then simply include helm values at will:

variables: vmImage: ubuntu-latest releaseName: some-release-name # This magic string is a pointer at the service connection shared to the project containerRegistryServiceConnection: "CloudProvider - some-project-12345" containerRepository: "some-project-12345/some-image" containerTag: $(Build.BuildId) stages: # Create and public a container to your registry via whatever means you need - stage: Prepare displayName: Prepare the base artifact jobs: - job: Prepare pool: vmImage: $(vmImage) displayName: Prepare job steps: # Publish our source code. # Be sure to have a meaningful `.artifactignore` to manage this. - publish: $(System.DefaultWorkingDirectory) artifact: drop - template: templates/docker-build.yml parameters: containerRegistryServiceConnection: $(containerRegistryServiceConnection) containerRepository: $(containerRepository) containerTag: $(containerTag) # Any stages you may need for deployment - stage: Test displayName: Deploy to the Test environment variables: hostName: test.{hostName} containerTag: $(resources.pipeline._Artifact.runID) jobs: - deployment: Deploy pool: vmImage: $(vmImage) environment: Environment.Resource strategy: runOnce: deploy: steps: # While we publish the entire artifact for visibility, # we really only need the helm chart to upgrade. - download: current artifact: drop patterns: | helm/**/*.* # Use our template with any customized values we require. - template: templates/helm-upgrade.yml parameters: namespace: $(Environment.ResourceName) releaseName: $(releaseName) # arguments: --dry-run --debug values: image: tag: $(containerTag) ingress: hosts: - host: $(hostName) paths: path: / pathType: ImplementationSpecific tls: - secretName: $(hostName)-tls hosts: - $(hostName)

Reviewing the output

The "Create values file" will output your values into a local json file and also include the results in your step for review:

{ "image": { "tag": "1234" }, "ingress": { "hosts": [ { "host": "test.{hostname}", "paths": [ { "path": "/", "pathType": "ImplementationSpecific" } ] } ], "tls": [ { "secretName": "test.{hostname}-tls", "hosts": ["test.{hostname}"] } ] } }

Good luck to you, and happy helm piping!