tech.guitarrapc.cóm

Technical updates

Azure DevOpsの Microsoft-hosted Agent で HoloLens ビルドを行う(MRTK/MRTKv2)

前回、Azure DevOps で HoloLens のビルド環境を行うこと、Microsoft-hosted Agent が今ならいいということを書きました。

tech.guitarrapc.com

今回は、MRTKとMRTKv2 を Microsoft-hosted Agent で現実的にビルドすることを考えてみましょう。 内容としては、次の記事が近いです。

withmr.nextscape.net

この記事では、MRTKv1とMRTKv2 それぞれについてリポジトリとAzure Devops Pipeline の YAMLを示しつつ、現実にビルドしていくことに必要なことをカバーします。

目次

Unity のビルドエラーがわからない

Windows上でUnityのバッチビルドをしたときに困るのが、Unity ビルドの経過、ログが標準出力に出ないことです。*1

結果Unity 内部でどのようなエラーが起こってもエラーの詳細がわからないので、記事にあるUnityBuildTask@2 を使う方法は現実的ではありません。*2

コンパイルエラーでどこでコンパイルエラー出たかわからないとか絶望です。

そのため、Unity Tools for Azure DevOps でUnity ビルドを行うのは厳しいものがあります。

- task: UnityBuildTask@2
  displayName: 'Unity Build WindowsStoreApps'
  name: 'unityBuild'
  inputs:
    buildTarget: 'WindowsStoreApps'
    unityEditorsPathMode: 'unityHub'
    unityProjectPath: '$(unityProjectPath)'
    commandLineArgumentsMode: 'default'

marketplace.visualstudio.com

代わりに「Unityビルド呼び出しつつlogファイルに吐いたログを読み込んで標準出力に吐く」ラッパーアプリケーションを用意する方法をよく採用されています。

https://chiepomme.gitbook.io/chienote/ci-cd/unity-build-log-to-stdoutchiepomme.gitbook.io

お手伝い先で必要になり一年前に作り使っていただいているのが UnityBuildRunner です。*3

github.com

dotnet global tool として公開してあるので、dotnet tool install -g UnityBuildRunner でWindows/Mac問わずサクッとインストールできます。

使い方は単純です。Unity のバッチビルドは次のフォーマットです。

Unity.exe [args]

この引数をUnityBuildRunnerにそのまま渡せばokです。 加えてタイムアウトにも対応してあります。

UnityBuildRunner [-UnityPath|-unityPath|-u] [-timeout|-t 00:60:00] [-version] [-help] [args]

たとえば、MRTKv2 でビルドするならこのように書けます。

UnityBuildRunner -UnityPath "C:\Program Files\Unity\Hub\Editor\2018.3.13f1\Editor\Unity.exe" -quit -batchmode -projectPath パス -buildTarget WSAPlayer -logfile Editor.log -executeMethod Microsoft.MixedReality.Toolkit.Build.Editor.UnityPlayerBuildTools.StartCommandLineBuild

Unityパスは環境変数から読むこともできるので次のようにも書けます。

set unityPath=C:\Program Files\Unity\Hub\Editor\2018.3.13f1\Editor\Unity.exe
UnityBuildRunner -quit -batchmode -projectPath パス -buildTarget WSAPlayer -logfile Editor.log -executeMethod Microsoft.MixedReality.Toolkit.Build.Editor.UnityPlayerBuildTools.StartCommandLineBuild

f:id:guitarrapc_tech:20190626163832p:plain
https://github.com/guitarrapc/UnityBuildRunner を使ったWindowsでのUnityビルド

MRTKv1 のビルドを行う

MRTK のビルドは、HoloToolkit.Unity.BuildDeployTools.BuildSLN メソッドを使います。*4

MRTKv2を使ったビルドとYAML定義を含んだリポジトリです。

github.com

Azure DevOps でのビルド結果も示します。

dev.azure.com

ビルドのYAML定義を見てみましょう。

gist.github.com

途中のコメントを外すとsln一式がartifacts から取得できます。

# if you want to build locally
# - task: PublishPipelineArtifact@0
#   displayName: 'Publish Pipeline Artifact'
#   inputs:
#     artifactName: 'UWP_Sln'
#     targetPath: 'UWP'

ちなみにMRTKv1 のシンプルなシーンをビルドしてみると、ビルドに30min、長い。

f:id:guitarrapc_tech:20190626170711p:plain
MRTKv1 の Azure DevOps Microsoft-host Agent でのビルド

プロジェクト固有の情報を知らずビルドするコツとして、VSビルド時にslnファイルを指定するのではなくslnのあるフォルダを指定 + / と書くことでslnビルドできます。/ がないとsln が見つからないので注意です。

MRTKv2 のビルドを行う

MRTKv2 は、Windows SDK 18362 が必要ですが、これがHosted Agent に来てビルドできるようになったのは6/25です。 Sprint 153 で追加対応が来てから、自分たちのAgent にくるまで約2週間かかりました。

MRTKv2のビルドは、Microsoft.MixedReality.Toolkit.Build.Editor.UnityPlayerBuildTools.StartCommandLineBuild メソッドを使います。 相変わらずDevelopment ビルドの指定ができないのがアレなのでラッパーを書くことになるでしょう。

MRTKv1を使ったビルドとYAML定義を含んだリポジトリです。

github.com

Azure DevOps でのビルド結果も示します。

dev.azure.com

ビルド定義のYAMLは次のようになります。

gist.github.com

途中のコメントを外すとsln一式がartifacts から取得できます。

# if you want to build locally
#- task: PublishPipelineArtifact@0
#  displayName: 'Publish Pipeline Artifact'
#  inputs:
#    artifactName: 'sln'
#    targetPath: 'Builds/WSAPlayer'

MRTKv2 のサンプルシーンをビルドしてみると、ビルドに30min、長い。

f:id:guitarrapc_tech:20190626170812p:plain
MRTKv2 の Azure DevOps Microsoft-host Agent でのビルド

プロジェクト固有の情報を知らずビルドするコツとして、VSビルド時にslnファイルを指定するのではなくslnのあるフォルダを指定 と書くことでslnビルドできます。先ほどと違い、/ で終わるとsln が見つからないので注意です。

学び

Unityのインストールにかかる時間

Microsft-hosted Agent にはUnity がインストールされていません。 そのためこのYAML ではUnityを都度インストールしています。

このUnityの準備にかかる時間をリードタイムとしてみると、10min 程度かかります。 毎ビルドで10min かかるのはアレでもあり、その程度で済むならいいかもしれません。((よくない)))

Output変数が定義されたタスクの変数はnameを指定することでとれるようになる

UnityGetProjectVersionTask@1 で取得したUnityバージョンを変数に出力します。 これを使うことでUnityプロジェクトのバージョンを知ることなく、同じYAMLをどのUnityバージョンでも変わらず使えます。

さて、UI と違って YAML では reference variable の設定はサポートされていません。 しかしTASKで出力変数が設定されている場合は、name をつけることで取得できます。

- task: DinomiteStudios.64e90d50-a9c0-11e8-a356-d3eab7857116.custom-unity-get-project-version-task.UnityGetProjectVersionTask@1
  displayName: 'Unity Get Project Version'
  name: unitygetprojectversion

これで、後段のstep において $(unitygetprojectversion.projectVersion) で参照できます。 教えてもらって気づきました、これはむりげー。

*1:macOS/Linux では、-logFile を無引数で与えることでログが標準出力に出ます

*2:MRTKv1 で私の環境ではビルドが60min 立っても終わらない状況も発生しました

*3:↑の記事より前に作ったのですが、結果似たことしててあぁって感じです。悲しい

*4:Unity BatchビルドはDevelopment 指定が引数でできないのがアレなので、たいがいラッパーを書いている気がします。

Azure DevOpsでのHoloLens ビルドを行うためのCI/CD選択

お手伝い先でHoloLens でのCI/CD を構築、運用して1年を超えましたが、日々安定してビルドができています。

先日素敵な記事も出てて良い感じです。*1

Azure DevOpsでHoloLensアプリをビルド(MS-hosted編) | NEXTSCAPE with MR

HoloLens のCI環境について、必要な要件、CI環境の選択、Azure DevOps のMicrosoft-hosted と Self-hosted の選択を見てみます。

目次

ビルド環境の選択(OS)

HoloLens に限定してビルドについて検討すると、UWP ビルド = Windows SDK が鍵になります。 現状のWindows SDKは、Windows でのみインストールが可能であり、結果としてビルド環境もWindows に限定されます。

Unity は Unity 2019 においても動作環境として、Windows Server を想定していません。(以前サポートにも問い合わせた)

OS:Windows 7 SP1 以降、8、10(64 ビットバージョンのみ)、macOS 10.12 以降

サーバー版の Windows、OS X についてはテストされていません。

f:id:guitarrapc_tech:20190624194637p:plain
https://unity3d.com/jp/unity/system-requirements

Windows Server 2012 R2 でUnity 起動すると即クラッシュします、対策にはDesktop Heap を広げる必要がありました。 幸いにもWindows Server 2016/Windows Server 2019 では問題なく起動し、ビルドも可能です。*2

今HoloLens ビルドを行うなら、Windows Server 2019 でビルドが可能です。(やってる)

ビルドの選択(コンテナ)

現状の Windows SDK + Unity をコンテナでビルドするのは難しい感触です。

Windows Container でコンテナイメージが公開されているようにも見えますがuwp という名はトラップです。中身は、Portal Library (Target UWP 10.0) であり UWPビルドはできません。

coderobin/windows-sdk-10.1 - Docker Hub

UWP のセルフビルドでわんちゃんいけそうですが、ここにUnity が入ると更にサイズが.... 。

windows-container/windowsservercore/windows-sdk-10.1 at master · coderobin/windows-container · GitHub

コンテナビルドができても、Windows Container だと旨味が少ないので悩ましいですが..... コンテナだと楽なのも事実なのでなんともです。

合わせて必要に応じてこのあたりも対応ですね。

Visual Studio Build Tools をコンテナーにインストールする | Microsoft Docs

なお、HoloLens はすでにIL2CPP ビルドがデファクトまったなしなので、C# と VC++ のビルドが必要です。また、vcxproj/csproj は SDK 形式ではないため dotnet ではなく msbuildが必要です。*3

Windows のビルドサービス

Windows でのビルドを行うとき選択肢はいくつか考えられます。

  • Azure DevOps
  • AppVeyor
  • Jenkins v2
  • Drone
  • Team City

Managed な環境でWindowsビルド を提供しているのは Azure DevOps と AppVeyor です。 Self-hosted Agent (自分でWindowsホストにエージェントをインストールして利用する)を提供しているのは、Azure DevOps、AppVeyor(Enterprise)、Jenkins v2、Drone、Team City です。

Team Cityを除きどれもYAMLでのビルドを定義できるのでそこは別にいいでしょう。

Open Source ではビルドをコスト、並列数、時間制約で考えると、Managed な環境であるAzure DevOps が最も強い選択肢になるのではないでしょうか?*4 AppVeyorは癖があることと、Spin upの遅さから選択しにくいものがあります。

個人的にはCircleCI が好きなので見てみると、Windowビルド対応予定があるように書いてあります。

f:id:guitarrapc_tech:20190624195935p:plain
https://circleci.jp/pricing/usage/

リポジトリもあり、内容を見てみるとPerformance plan でトライアルできるようです。

GitHub - CircleCI-Public/windows-preview-docs: Temporary docs on how to use the pre-release preview of Windows builds on CircleCI

Windows 向けOrb もあり、使えそうな感触があります。

orbs:
  win: sandbox/windows-tools@dev:preview

基本的なビルド機能である、caching, workspaces, SSH into the build がサポートされている一方で、Previewの現在、デフォルトでインストールされているのはごく一部のソフトウェアなので注意です。

We install Git, Chocolatey, and 7zip. We don’t install any other dependencies at the moment.

ビルド環境としての AzureDevOps Pipeline

Azure DevOps のビルド環境には、「おまかせできる Microsoft-hosted」 と 「自分で環境を自由に設定できるSelf-hosted」 の2種類あります。

  • Microsft-hosted Agent: Microsoft がhost して各種ソフトウェアもビルトインで入っている
  • Self-hosted agent: ユーザーがWindows/macOS/Linux のいずれかにインストールしてミドルウェアや環境を好きに構築したうえで動かす

docs.microsoft.com

両環境で一番大きく異なるのは、価格、パフォーマンス、実行環境のクリーンさ、ミドルウェアの管理です。

私自身パフォーマンスからSelf-hosted Agent を使う決断を下すことが多かったのですが、現在はHosted agent がいいように思います。(理由は後述)

この選択に関しては、他のAzure DevOps ユーザーも記事をあげているので参考にするといいでしょう。

Too bad! You still need a private build agent when using VSTS – Henry Been

では、Self-hosted Agent と Microsoft-hosted Agent ののどちらを、どのような観点で選択するといいのか考えてみます。

価格面の違い

価格表があるので見てみましょう。

  • Microsoft-hosted Agent : 追加の並列ジョブごとに ¥4,480 (分数制限なし)
  • Self-hosted Agent : 追加の並列ジョブごとに ¥1,680 (分数制限なし)

f:id:guitarrapc_tech:20190616195512p:plain
https://azure.microsoft.com/ja-jp/pricing/details/devops/azure-devops-services/

Hosted Agentが1並列ビルドに4480円というのはかなり高く感じるのではないでしょうか。*5

一方のSelf-hosted は1680円と安く見えるのに加え、MSDN の Visual Studio Enterprise サブスクライバーはアカウント1つあたり1つの課金済みSelf-hosted Agent ライセンスが付きます。*6 開発者一人ずつにMSDN サブスクリプションを会社で付与している場合、実質無料で開発者の数だけ Private Agent を割り当てて並列ビルドをかけられるので、一見すると大きなメリットに見えます。

The free tier is one parallel job. In addition, you get one free parallel job for each Visual Studio Enterprise subscriber that is a member of your organization. You can get more using paid self-hosted parallel jobs.

Buy continuous integration, continuous deployment, Azure Pipelines - Azure DevOps Services | Microsoft Docs

実際に Self-hosted Agent のコストを考えるときは動作するホストやストレージ料金を含めることになります。*7 よくあるケースとして、Self-hosted Agent を AzureVMを実行ホストとして実行することを基準に簡単に価格を考えましょう。*8

ビルドVMにCPUとメモリがほしいのでコスパがいいBシリーズの4 vCPU/16G RAMを採用します。

インスタンス VCPU メモリ 一時ストレージ 従量課金制
B4MS 4 16 GiB 32 GiB ~¥19,131.84/月

f:id:guitarrapc_tech:20190625195225p:plain
https://azure.microsoft.com/ja-jp/pricing/details/virtual-machines/windows/

git で大量のファイルを読み書きし、ビルドで大量のファイルリードがかかるのが主なストレージ負荷になるため、CIには高IOに耐えられる プレミアムストレージが欲しくなるでしょう。

f:id:guitarrapc_tech:20190625195717p:plain
https://azure.microsoft.com/ja-jp/pricing/details/managed-disks/

以上を踏まえて、MSDNでSelf-hosted Agent がついてくる環境で、VM とプレミアムストレージを256GB/512GB/1TB の組み合わせと、Hosted agent で (¥4480) で換算して考えてみます。

VM Storage Self-hosted 合計 Hostedの台数換算
* ¥19,131.84(VM) ¥4,896.72(Storage 256GB) ¥24028.56 5.3台
* ¥19,131.84(VM) ¥9,430.40(Storage 512GB) ¥28562.24 6.36台
* ¥19,131.84(VM) ¥17,409.28(Storage 1TB) ¥36541.12 8.1台

ビルドの数にもよりますが、1VMに載せられるエージェントの数は2-4台程度です。 このため社内マシンを使うなどの選択をしない限り 、Self-hosted は並列度との兼ね合いはかなり悪いものがあります。*9

ビルド環境の手間

ビルド環境はただビルドだけしたいので、手間がかかるのは嫌なものです。 手間をかけるメリットがあるときはかけますが、かける必要がないならかけないのがいいでしょう。

この観点では、Microsoft-hosted Agent は一般的なビルド環境程度には楽です。

Microsoft-hosted Agent

ビルドエージェントが動作するホスト環境の管理が不要なため、手間が大きく軽減されます。 都度新しい環境でビルドされるので、ビルド環境のボリューム容量も気にする必要がありません。

逆にホストの管理ができないことに割り切れないと手間がかかるでしょう。 都度新しいホストが起動するため前回のジョブ実行状態を再利用することはできません。 Hosted Agentに入っていない、インストールに手間や時間のかかるミドルウェアを事前にインストールしておいてビルドのための時間を省略することもできません。 エージェントを選ぶことはできないので、エージェントに高スペック、高IOを期待することはできません。 ジョブごとにクリーンであることが面倒なポイントです。

Self-hosted Agent

ビルドエージェントが動作するホスト環境が管理できることから、うまく使うとビルドを高速化することができます。 前回のJon結果やCheckOut 結果を再利用して、gitやdockerのキャッシュをかけておくこともできます。 Unityのようなインストールに時間のかかるミドルウェアをインストールしておくことも可能です。 ミルドウェアの特定のバージョンで問題があったときに、バージョンを固定するのも難しいでしょう。

うまく使う、の度合いが半端なくホストの管理が必要で手間が大きくかかります。 まずSelf-hosted Agent のインストールが必要です。 ビルドするホストのボリューム容量の管理が必要です。1ホストで複数エージェントを動かす場合、ホストごとにワークスペースを持つためn倍(n=ホストの数)の容量を使います。 ホスト管理の手間があるのは手間の多くを占めます。

VMとストレージが更に増え、それぞれにインストールすることを考えるとなかなか面倒なことがわかります。

Microsoft-hosted Agent と Self-hosted Agent の選択

ビルドの頻度にもよりますが、HoloLens 開発でSelf-hosted Agent か Microsoft-hosted Agent を選ぶ場合、今ならMicrosoft-hosted Agent がいいでしょう。

一見Microsoft-hosted Agednt はスペックが低かったり、1並列ビルドが高かったりしますが、手間がかからない、純粋に並列度で考えられるのは大きなメリットといえます。

IL2CPPが当然になった今、HoloLens の1ビルドにかかる時間は早くても15min、普通に20min~30min 程度かかります。 Unity のCacheを持った状態での起動高速化(Library) も、スクリプトの変更に弱かったりするので厳しいものがあります。

総合的にみて、割り切ってMicrosoft-hosted Agent がいい選択肢になるでしょう。

Microsoft-hosted Agent の懸念

Microsoft-hosted Agent は、UWP ビルドができる稀な Managed CI 環境ですが、Windows SDK が怖い部分でもあります。

MRTKv2 は、Windows SDK 18362 が必要ですが、これがHosted Agent のビルドで利用できるようになったのは6/25です。

Sprint 153 で追加対応が来てから自分たちのAgent にくるまで約2週間かかっています。多くの場合この程度かかることから、Azure DevOpsにおいては2週間程度は新機能の利用まで見ておく必要があります。*10

View linked GitHub activity from the Kanban board - Sprint 153 Update | Microsoft Docs

そういった意味で、Visual Studio はともかくとして*11、MRTK が 新Windows SDK に依存した実装を入れたときにCI環境の Windows SDK がなくて悲しい思いをすることはありえます。

とりあえず 今のMRTKv2 で使ってる最新Windows SDK 来たし、まぁしばらくは大丈夫でしょう。

*1:手離れされているので安心して見ていられます。

*2:以前は Windows Serverが使えない縛りから Windows 10 をAzure のDev利用などで持って来る必要がありました

*3:dotnet SDK で完結する今どきの.NET 環境ではない

*4:無料、10並列、時間無制限

*5:そもそもパイプラインで課金という価値観が、CircleCI の新料金プランで崩れていてつらさがあります

*6:企業でMSDNサブスクリプションを配布しており、このサブスクリプションをビルドに利用したい場合、MSDN サブスクライブ画面で会社のAzureアカウントと紐づけてもらうことで割り当てられます。

*7:人的コストを除外します

*8:EC2に置き換えても結構です

*9:VMやストレージ環境

*10:UIの変更を除く

*11:これは比較的すぐにくるので

Terraform 0.11.14から0.12.0 にアップグレードを行う

Terraform 0.12.0 がリリースされ、すでに 0.12.1 がリリースされました。 いくつかのTerraform 環境で随時0.11.14 から 0.12.0 にアップグレードしているのですが、その中で AzureRM Provider に関して少し困ったのでメモしておきます。

目次

TL;DR

基本は、Upgrade Guide をみること。

Upgrading to Terraform 0.12 - Terraform by HashiCorp

~破壊的変更として、タグの一部を無視することができなくなっているので、タグすべてを無視して直変更なりで一時しのぎが良さそう。~ 0.12.3 で対処が入ったので一部無視が可能になり互換性とれるようになりました。

tfenv を入れておくとめちゃめちゃ捗るので、これまで入れてなかった人はこれを気に入れておくとよいです。

操作環境

私は普段、Terraform を5つの環境で動作できるように組んでいます。*1 もっぱらGitHub PR でのCI連携をするので、Docker(Atlantis) なのですが環境によっては Azure Cloud Shell が最も利用しやすい場合はこちらを利用します。

  • Windows 10 Desktop
  • WSL Ubuntu 18.04
  • macOS 10.14 Mojave
  • Azure CloudShell
  • Docker Container (Atlantis)

さて、Azure Cloud Shell 環境はTerraform をはじめとする各種ツールが組み込みで入っており、ツールのアップグレードも勝手にされます。 つまり、おもむろにバージョンが上がります。

運用面を除外すると便利です。 一方で、0.12.0 のような破壊的変更が加わったタイミングでは、ツールのアップデート時に速やかに追随できるように整えておくのも重要になります。

今回は、0.11.14 なterraform 構成をCloudShell で当てようとしたタイミングで0.12.0にアップグレードされており*2 サクッと対応する必要があったのでした。

アップグレードの流れ

  • tfenv で0.11.14 にしておいて、terrafrom 0.12checklistで確認とエラーを潰していく。
  • Provider が0.12に対応していない場合があるので、バージョンを上げて 0.12でinit できるかで確認する。
  • tfenvで0.12.0 (あるいは0.12.1)にして、terraform 0.12upgrade をかけて微妙に拾えてなかったシンタックスの変更を潰す。 0.12upgrade で変更されないModuleが中にはいるので、手でちまちま治す。
  • terraform plan かけて変更でなかればまず大丈夫。
  • atlantis など適用環境で terraform apply かけて出力を確認して終わり。

よく引っかかる変更点

先に、0.12 の変更点だけ見ておくといいです。

Terraform 0.11→0.12で追加された新機能 | DevelopersIO

さて、次のものは度々0.12.0にアップグレード対応するたびに、あぁまたこれかという感じで引っ掛かります。

Provider not support 0.12

単純にアップグレードすればok なことが多いですが、サポートされているかの確認はしましょう。

azurerm provider なら次のようにかなり細かく出ています。

Releases · terraform-providers/terraform-provider-azurerm

Argument names must not be quoted

Terraform 0.11.14までは、map や リソース内のblock・map のハンドルが微妙にゆるふわでした。 これが、0.12 から比較的しっかり見るようになっています。

結果、例えばこういったmap 宣言が0.11.14まではokでしたが、

locals {
  common_tags {
    environment = "${var.ENV}"
  }
}

0.12.0 からは、 = が必須になっています。

locals {
  common_tags = {
    environment = var.ENV
  }
}

あるいは、AzureRM Provider の azurerm_function_app などが提供する identity ブロックもそうです。

0.11.14まではこれがokでした。

  identity = {
    type = "SystemAssigned"
  }

0.12.0 からはこうなります。

  identity {
    type = "SystemAssigned"
  }

ref: Error: Invalid argument name (Argument names must not be quoted) · Issue #19575 · hashicorp/terraform

Resource の Deprecation

たとえば、azurerm provider の data resource である azurerm_builtin_role_definition は deprecate が予定されており、azurerm_role_definition を使うように促されます。

0.11.14 まで

data "azurerm_builtin_role_definition" "contributor" {
  name = "Contributor"
}
data "azurerm_role_definition" "contributor" {
  name = "Contributor"
}

Resource の返す型から値取り出し時の注意

list/tuple<T, TN>と型を明示的に示せるようになったことで少し変更を受けます。

たとえば、azurerm_fucntion_app リソースが返す identity から principal_id を取得することを考えてみましょう。

これは次で 0.11.14 / 0.12 ともにとれます。

output identity {
  value = "${lookup(azurerm_function_app.main.identity[0], "principal_id")}"
}

しかし、モジュールの外でこの形式で取りたいがために、次のようにリストにラップしていると 0.12.0 では破壊的変更となります。

// Output of Module
output identity {
  value = ["${azurerm_function_app.main.identity}"]
}

0.11.14 まではこのようにして、identity の list からmap で拾えば取れましたが、0.12.0 では取れません。

// Get Principal from module output
output "api_functionapp_msi_identity" {
  value = "${lookup(module.functionapp.identity[0], "principal_id")}"
}

この小細工は意味ないので当初示した例に直しましょう。

このエラーのときに原因がぱっとわからなかったのできびしい。

破壊的変更

0.12.0 で、解消できていない破壊的変更が一件ありますが、これは0.12.3 で解消しました。

lifecycle / ignore_changes blocking upgrade from 0.11 to 0.12 · Issue #21433 · hashicorp/terraform

これは、構文が terraform 0.12upgrade で変わっていない場合、Attribute name required: Dot must be followed by attribute name.というエラーがでます。

ref: terraform 0.12 remote state : Attribute name required · Issue #20835 · hashicorp/terraform

どんな問題かざっくりいうと、「ignore_changes でattirbuteベースで指定するようになったが、これまで無視していたものが0.12.0 でignoreされなくなった」というものです。

私が直面したのは、Azure Web Apps の appsettings のignore ができないことでした。kubernetes のタグも同じ問題があります。

0.12.0 Error: Attribute name required · Issue #21444 · hashicorp/terraform

Issue の中にある通り、全体の無視はできます。

    ignore_changes = [
      tags
    ]
    ignore_changes = [
      appsettings
    ]

しかし、その一部だけ無視することができません。これはIssueに提示されている tags["kubernetes.io"]A static variable reference is required. でできないことでもわかります。

というか、ignore_changes は静的な指定しかできないのは0.11でもその前からもそうなのに、この回答者はトンチンカンなこと言っててもんにょりなので、もう少し真面目にどうなるのか知りたいです。

なお、TBD 0.12.1 となっていますが 0.12.1 はすでに出ているため、いったんは運用回避しかなさそうです。terraform で値変更せず、コンソールで直変更..... 事故臭しかない。

うれしい構文変化

変数への型の明示

変数の型が増えました。 特に、list や map が変わったのはうれしいでしょう。

文字列だけしかないmap でも、これまで次のような宣言でした。

type = "map"

これが次のように宣言可能になりました。

type = map(string)

うれしいですね。map(list(string)) とかも行けますね。 map(list) が0.11は言語制約上かなり厳しいものがあったので、こういう型制約は言語の表現力、拡張性に寄与するのでうれしいです。

lookup から要素アクセスへ

map の value に対して key でアクセスするときに lookup をしてきましたが、0.12 から[key]でアクセスが可能になりました。

つまり、0.11まで書いていた次のようなコードが

${lookup(map, "key")}

次のように変わります。

map["key"]

プログラムと違って、ない場合を考慮する必要がなくなければそこで落ちるという割り切りがインフラにはあるので、こういうキーアクセスはちょうどよいです。 lookup 撲滅しました。

VSCode の terraform plugin 対応

hcl2 に追随していないのでIssueが上がっていましたが、すでにPRがマージされたので、もうじきでるんじゃないでしょうか。

Update syntax highlighting to better support terraform 0.12 by MattFenner · Pull Request #181 · mauve/vscode-terraform

*1:といっても大きな違いはなく、別に大したことはしていません。WindowsのCRLFと環境変数を使う場合が一番めんどくさいポイントになりやすいでしょう。

*2:なぜ 0.12.1 じゃないのかというと時間差、Azureせんせーさすが

Windows10 のPowerShellでF7を押しても履歴が画面に表示されない

以前書いたPowerShell の履歴に関する操作ですが、Windows 10 のWindows PowerShell ではF7による画面表示がされません。

tech.guitarrapc.com

これに関して少し見てみましょう。

目次

TL;DR

PSReadLine を外すか、代替コマンドを使いましょう。 PowerShell Core では代替コマンド使えないのであきらめで。

Windows 10 と Windows PowerShell と PSReadline

Windows 10 でもコマンドプロンプトではF7 で実行履歴が見えます。

f:id:guitarrapc_tech:20190531222503p:plain
コマンドプロンプトでF7を押して実行履歴を表示する

一方でWindows 10 の Windows PowerShell でF7で実行履歴が見えないのは、 PSReadline がデフォルトで有効になったためです。 なお、もちろん PowerShell Core (6.1) でもF7で実行履歴は表示されません。

従来のようにF7で実行履歴画面を出したい

PSReadLineモジュールを外すことで表示されます。 が、今のPowerShell でPSReadlineを外すのは副作用が大きいためちょっと悩みどころです。

Remove-Module -Name PSReadLine

f:id:guitarrapc_tech:20190531222642p:plain
PSReadline モジュールを外すとF7が利用可能になる

これは Windows PowerShell / PowerShell Core 両方で有効です。(そりゃぁそうですね)

PSReadline で F7を使ってGridViewによる代替表示を行う

PSReadline は、特定のキー入力に対してハンドラーを登録できます。

例えば、F7 を押すと Windows PowerShell で使える Out-GridView 表示してみましょう。

gist.github.com

この関数を Windows PowerShell で実行してから F7を押すと、実行ヒストリが最新の入力から降順にすべて表示されます。*1

f:id:guitarrapc_tech:20190531224340p:plain
Out-GridView を使った代替表示

なお、関数の$history | Sort-Object -Descending | Out-GridView -Title History -PassThru$history | Out-GridView -Title History -PassThru にすると古い履歴から昇順になります。 好きなほうでどうぞ。

PowerShell Core は、マルチプラットフォームで表示する View に相当するコマンドレットがないのでいったんなしで。

Ref

基本的にここの通りです。

Making the Command-History Pop-Up work via F7 in Windows 10 Powershell - Stack Overflow

*1:ここ最近 Vue.js ばかりやっているのがばれる....

PowerShell でsudo っぽいものを内蔵した関数を作る

時々思い出したようにPowerShell の記事を書いてみます。

スクリプトでよくあるのが、sudo で実行時に権限があるスクリプトの許可をしたいというケースです。

Windows は組み込みsudo がないので面倒でしたが、現状なら scoop で sudo をインストールするといいと思います。

scoop.sh

scoop install sudo

これで sudo ./your_script.ps1 とできるので特権が必要なときに、必要な権限を渡すことができます。

さて今回の記事は、Windows において実行中のスクリプトや関数が特権が必要な場合に、sudo を使わずにUACダイアログを出して昇格したPowerShellで同関数を実行し直してほしいというケースです。

通常の特権昇格フロー + Windows Diffender操作のため利用には注意してください。

この2つが自動化できるのは運用上は非常に便利ですが、誤った利用は技術を良くない方向に追い込みます。 チーム内での潤滑な運用での利用に留めることを推奨します。

目次

TL;DR

悪用禁止だけど、チーム内で使うには便利です。 特に、PowerShell のExecution Policy やダブルクリック問題はだいたいこれで解決するのが定番です。(chocolatey や scoop もこの手法)

サンプル

今回は、Windows Defender によってdotnet のビルドが遅いので除外して対応しようという記事を使ってやってみます。

baba-s.hatenablog.com

サンプルスクリプトを置いておきます。

PowerShellUtil/WindowsDefender at master · guitarrapc/PowerShellUtil · GitHub

Windows PowerShell で次のコマンドを実行すると、Windows Defender のスキャン除外パスに指定したパス + Visual Studio のパスが入り、除外プロセスにVisual Studio や dotnet、msbuildが入ります。

実行前に十分に気をつけてください

iex  (new-object net.webclient).downloadstring('https://raw.githubusercontent.com/guitarrapc/PowerShellUtil/master/WindowsDefender/remote_exec.ps1')

Windows PowerShell を特権のない状態で起動して実行すると、昇格するか聞かれます。

f:id:guitarrapc_tech:20190508140824p:plain
UAC昇格が聞かれる

y を押した時だけ、UACダイアログがでて関数がそこで再実行されます。

f:id:guitarrapc_tech:20190508140916p:plain
特権に昇格されたPowerShellで関数が再実行される

f:id:guitarrapc_tech:20190508140943p:plain
実行結果

仕組み

処理の本体は、Add-DefenderExclusionForDevEnv です。

PowerShellUtil/Add-DefenderExclusionForDevEnv.ps1 at master · guitarrapc/PowerShellUtil · GitHub

キーはここです。

gist.github.com

自分が特権で実行されているかは、これで検出できます。

$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (!$currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {

自分の関数名を、自動変数 $MyInvocation から取ります。

$me = $MyInvocation.MyCommand

自分の関数の定義を、Get-Command から取ります。

$myDefinition = (Get-Command $me).Definition

これで関数文字列が生成できます。

$myfunction = "function $me { $myDefinition }"

新規プロセスでPowerShell.exe を実行するときに、実行するコマンド文字列を組み立てます。 今のパスに移動するようにしています。

$cd = (Get-Location).Path
$commands = "Set-Location $cd; $myfunction; Write-Host 'Running $me'; $me; Pause"

さて、生成したコマンド文字列は関数の改行が含まれており、このままでは PowerShell.exe の -Command 引数に渡せません。 そこで、バイナリにしてBase64文字列を -EncodedCommand にわたすことで解釈させます。

このあたりは、PowerShell を使ったワーム攻撃でもよく使われる手法です。

$bytes = [System.Text.Encoding]::Unicode.GetBytes($commands)
$encode = [Convert]::ToBase64String($bytes)
$argumentList = "-NoProfile","-EncodedCommand", $encode

あとは、PowerShell を 特権で起動するため、-Verb RunAs を指定して先程の引数を食わせます。 PowerShell.exeの実行時に、-Wait をつけることで、起動したPowerShell.exe が自動で閉じません。

$p = Start-Process -Verb RunAs powershell.exe -ArgumentList $argumentList -Wait -PassThru

おわりに

最近のほげもげみてると、こういう記事書くのは心配です。 ただ、標準で用意されている方法を用途を限定して使うことまで阻害されるのは望ましくないものです。

攻撃にすでに利用もされている方法でもあるので、UAC は自分でプロセスを起動させるほうがパブリックにはいいです。(Chocolatey や Scoop がそう)

どうか正しく使われますように。