かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Azure DevOps の Pipelines の変数を使おう

ハードコーディングされた値は死すべし!! ということで Azure DevOps の Pipelines で変数使っていこうと思います。

docs.microsoft.com

ハローワールド

何事もハローワルドから。variables で変数を定義できます。定義した変数は $(変数名) で参照できます。ということでさくっと以下のようはパイプラインの yaml を作ってみました。後々の確認のために特に必要はないのですが stage から定義しています。

trigger:
- master

variables:
  var1: Hello
  var2: World

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: build
    displayName: build
    jobs:
    - job: test1
      steps:
      - pwsh: |
          Write-Host '$(var1)'
          Write-Host '$(var2)'

pwsh タスクで定義した変数を参照しています。このパイプラインが実行されると以下のように表示されます。

Hello
World

いい感じ。

その他にも ${{ xxx }} のように書いて実行時じゃなくてコンパイル時に変数を評価するような書き方や $[xxx] のように書く方法もあります。$[xxx] という書き方も実行時に評価されるらしいですが、これは pwsh とかの中には書けませんでした。

trigger:
- master

variables:
  var1: Hello
  var2: World

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: build
    displayName: build
    jobs:
    - job: test1
      variables:
        jobVar1: $[variables.var1]
        jobVar2: $[variables.var2]
      steps:
      - pwsh: |
          Write-Host '$(var1)'
          Write-Host '$(var2)'
      - pwsh: |
          Write-Host '${{variables.var1}}'
          Write-Host '${{variables.var2}}'
      - pwsh: |
          Write-Host '$(jobVar1)'
          Write-Host '$(jobVar2)'

しれっと使ってますが job レベルとかでも変数を定義できます。上の yaml でいう jobVar1jobVar2 を定義しているところですね。

スクリプトの中で変数を定義したい

pwsh タスクなどの中で特別なフォーマットで標準出力に書き込むことでスクリプト内で変数を定義できます。出力する内容は "##vso[task.setvariable variable=変数名]値" です。これで変数を定義できます。やってみましょう。

trigger:
- master

variables:
  var1: Hello
  var2: World

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: build
    displayName: build
    jobs:
    - job: test1
      steps:
      - pwsh: |
          Write-Host '##vso[task.setvariable variable=fromScript]Hello from Script'
      - pwsh: |
          Write-Host '$(fromScript)'

これで2つ目の pwsh のほうで Hello from Script が表示されます。

同じ job 内の task からなら $(変数名) でいけるのですが、これが job を跨ぐと参照できなくなります。試しにこうしてみましょう。

trigger:
- master

variables:
  var1: Hello
  var2: World

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: build
    displayName: build
    jobs:
    - job: test1
      steps:
      - pwsh: |
          Write-Host '##vso[task.setvariable variable=fromScript]Hello from Script'
    - job: test2
      dependsOn: test1
      steps:
      - pwsh: |
          Write-Host '$(fromScript)'

これを実行すると悲しいことに $(fromScript) が表示されます。これは定義されていない変数を参照したとき表示されるやつですね。job を跨いだ変数の参照には一工夫がいります。

まず、script 内で変数を定義するときに isOutput=true を定義して出力に含めるようにします。こうすることで job の出力として別の job から参照できます。参照する側では $[dependencies.依存先job名.outputs['タスク名.変数名']] のようになります。あと、dependencies として参照するので dependsOn にも依存先として明示しておきます。以下のような感じです。

trigger:
- master

variables:
  var1: Hello
  var2: World

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: build
    displayName: build
    jobs:
    - job: test1
      steps:
      - pwsh: |
          Write-Host '##vso[task.setvariable variable=fromScript;isOutput=true]Hello from Script'
        name: scriptTask
    - job: test2
      dependsOn: test1
      variables:
        fromScriptInTest1: $[dependencies.test1.outputs['scriptTask.fromScript']]
      steps:
      - pwsh: |
          Write-Host '$(fromScriptInTest1)'

これで、fromScriptInTest1 変数に test1 ジョブで定義した fromScript 変数が入ります。

このスクリプト内での変数の宣言を応用すると、ARM Template の outputs を変数として定義することができるようになります。以下のような感じでいけます。

stages:
  - stage: build_and_deploy
    displayName: Build and deploy
    jobs:
      - job: infrastructure
        displayName: Create Azure resources
        steps:
          - task: AzureResourceManagerTemplateDeployment@3
            displayName: Deploy ARM template to Azure
            inputs:
              deploymentScope: 'Resource Group'
              azureResourceManagerConnection: 'KazukiOta1'
              subscriptionId: 'xxxxxx-xxxxx-xxxxx-xxxxxx'
              action: 'Create Or Update Resource Group'
              resourceGroupName: $(resourceGroupName)
              location: 'Japan East'
              templateLocation: 'Linked artifact'
              csmFile: 'azuredeploy.json'
              csmParametersFile: 'azuredeploy.parameters.json'
              deploymentMode: 'Incremental'
              deploymentOutputs: armOutputs
          - pwsh: |
              $outputs = ConvertFrom-Json '$(armOutputs)'
              foreach ($x in $outputs.PSObject.Properties) {
                Write-Host "##vso[task.setvariable variable=armOutputs_$($x.Name);isSecure=true;isOutput=true]$($x.Value.value)"
              }

deploymentOutputs で ARM Template の出力の JSON を受け取って PowerShell 内でパースして ##vso... を使って変数にしています。

ステージ間の変数の参照

これは正攻法ではできないみたいなので頑張るしかないみたいです。

以下の medium に、アーティファクトに変数の内容を出力しておいて、別のステージではアーティファクトをダウンロードして読み込んで使うという方法が書いてありました。

medium.com

ちょっと大変ですね。

variable group

Pipelines の Library の中に Variable Group というものが作れます。ここで以下のように変数のグループを定義できます。

f:id:okazuki:20200301235410p:plain

もう 1 つグループ作りました。こっちは日本語

f:id:okazuki:20200301235904p:plain

このようにしておくと、以下のように stage 単位や job 単位でさくっと変数を切り替えれます。

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: development
    variables:
    - group: vargroup1
    - name: var2
      value: localVar
    jobs:
    - job: test1
      steps:
      - pwsh: |
          Write-Host '$(message)'
          Write-Host '$(var2)'
  - stage: production
    variables:
    - group: vargroup2
    - name: var2
      value: 'ローカル変数'
    jobs:
    - job: test1
      steps:
        - pwsh: |
            Write-Host '$(message)'
            Write-Host '$(var2)'

これを実行すると英語と日本語でそれぞれでますね。ばっちり。

f:id:okazuki:20200302000521j:plain

f:id:okazuki:20200302000534j:plain

template

template を使って variables を yaml として定義しておいて取り込むことで同じようなこともできます。 例えば vartemplate1.yml を以下の内容で作って

variables:
  message: Hello world

vartemplate2.yml を以下の内容で作って

variables:
  message: 'こんにちは世界'

そして、azurepipeline.yml を group から template に置き換えます。

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: development
    variables:
    - template: vartemplate1.yml
    - name: var2
      value: localVar
    jobs:
    - job: test1
      steps:
      - pwsh: |
          Write-Host '$(message)'
          Write-Host '$(var2)'
  - stage: production
    variables:
    - template: vartemplate2.yml
    - name: var2
      value: 'ローカル変数'
    jobs:
    - job: test1
      steps:
        - pwsh: |
            Write-Host '$(message)'
            Write-Host '$(var2)'

これでも Variable group と同じ結果が得られます。違いとしては、template の方はリポジトリーに入るけど Variable group はリポジトリには入らないという点です。リポジトリに入ったらまずい情報とかをまとめて置いておきたいとか時に気軽に使えます。

秘密な情報

その他にパイプラインの画面から変数を定義できます。こちらは、もうちょっと秘密なものを置けそう。

f:id:okazuki:20200302001546j:plain

f:id:okazuki:20200302001600p:plain

さらに、もっと秘密な情報を保存しておきたい時は Variable Group に Azure KeyVault との連携もあります。(これは自分ではまだ動作確認してない)

docs.microsoft.com

ということで秘密なものをきちんと全体で管理したいなら Azure KeyVault で、そうじゃないならパイプラインの変数でという感じでしょうか。もうちょっとゆるいやつは Variable group に限定的な人しか読めないようにしておいておくとか?

まとめ

ということで、Azure DevOps の Pipelines ネタのブログなら iPad で書けるのではないかと思って書いてみました。意外と画像編集もできるし workingcopy という git クライアント買えば Azure DevOps から clone してローカルで Textastic で編集して push とかも出来ていい感じでした。

でも、パソコンの方がやっぱりこういう作業は今のところ快適でした。