VisualStudioからAzure KeyVaultにアクセスするときにえらいハマった話
はじめに
今回の記事は下記の環境で検証を行っています。
記事を参照されるタイミングによっては画面構成や設定などが変わる可能性がありますのでご留意ください。
- Visual Studio Enterprise: 16.7.2
※以降Visual StudioはVSと記載しています
発生した問題
とあるアカウントを使用した場合、VSのConnected Serviceを使用してAzure Key Vaultにアクセスできない。
結論
16.7以降のVisualStudioからAzureに接続するための認証を行う場合、認証がうまく行かない場合があるようです。
Azureのリソースは認識できてるのに、肝心の接続のときはうまくいかない!
そんな場合はNuGetでAzure.Identity
のバージョンを1.2.2以上に上げて、下記のいずれかを行うことでうまくいくようです。
launchSettings.json
でAZURE_TENANT_ID
を指定するnew DefaultAzureCredential(new DefaultAzureCredentialOptions { VisualStudioTenantId = "<AzureのテナントID>" }))
を指定する
どえらいハマってVSアンインストールとかして悔しい感じなので、以降、解決までの試行錯誤やらの過程の話です。
ConnectedServiceでAzure KeyVaultにアクセスできなくなった
VS16.7からVisualStudioのConnectedServiceを使用した際に構成される内容が変わりました。
開発時は基本的にユーザーシークレットを使用すると思うのですがちょっと使ってみたろと思ったのがすべての始まりでした。
ひとまず、Azure KeyVaultへの接続設定をVSから行ってみます。
構成されるライブラリだけで見ると👆な感じの変更内容のようです。
Azure.Identityを使用するような変更が行われたという感じですね。
さて、これで実行を行うとエラーとなり実行ができませんでした。
正常に実行できる環境もあり、いわゆるおま環事象といった感じです。
AzureADアプリケーションを調べる
MsalServiceException: AADSTS70002: The client does not exist or is not enabled for consumers. If you are the application developer, configure a new application through the App Registrations in the Azure Portal at https://go.microsoft.com/fwlink/?linkid=2083908.
エラー内容は上記のとおりです。指定されたAzureADアプリが存在しないようです。
なるほどー。というわけでAzureADアプリを作成し、KVのアクセスポリシーに割り当てます。
Program.csも下記のように作成したClientIdを使用するように書き換えました。
.ConfigureAppConfiguration((context, config) => { var keyVaultEndpoint = new Uri(Environment.GetEnvironmentVariable("VaultUri")); config.AddAzureKeyVault( keyVaultEndpoint, new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = "<作成したADアプリID>" })); })
しかし結果は変わりませんでした。
別の認証が通るか試してみる
Azure SDK: What’s new in the Azure Identity August 2020 General Availability Releaseという記事でライブラリについて詳細にかかれています。
ひとまずVisualStudioの認証で失敗したけど、他の認証で成功してたらおkなのでは?と短絡に考え
Azure CLIでログイン状態にしてリトライしてみましたが結果は同じでした。
ADアプリの設定の問題かな?と思ったので正常に動作する側のAzureADアプリを見てみようと考えました。
認証フローを追う
設定を見るにも認証に使用されているAzureADのClientIDを見ないと始まりません。
そこでどのような認証が行われているのか正常に動作する環境を動かしながらFiddlerで追っかけてみました。
色々アレな情報が出てくるのでキャプチャは控えますが
ここで使用されているであろうClientIDの特定はできましたが、正常に動作する環境上でもAzureADアプリは見つかりませんでした。
で、あれば問題の根本はアプリが存在しないということはないだろうな。ということで別の線を当たることにしました。
テナントを指定する
先程記載したブログの記事でテナントの指定の仕方を記載した項目があります
authenticate-to-a-specific-tenant
僕のアカウントはAD/AD B2C含め複数のテナントに所属しており、且つオーナーとなっているテナントも複数あるのでHomeテナントを誤認したのかもしれません。
まずは👆記事に記載されている内容で解決できるか確認してみます。
Azure.Identityの1.1.1のDefaultAzureCredentialOptions
にVisualStudioTenantId
は存在しないので
Azure.Identityの1.2.2に上げる必要があります。
.ConfigureAppConfiguration((context, config) => { var keyVaultEndpoint = new Uri(Environment.GetEnvironmentVariable("VaultUri")); config.AddAzureKeyVault( keyVaultEndpoint, new DefaultAzureCredential(new DefaultAzureCredentialOptions { VisualStudioTenantId = "<Azure Tenant Id>" })); })
これでやっと動作するようになりました。
その後に試したこと
DefaultAzureCredentialOptions
を指定せずに解決できないか試しました。
VisualStudioのAzureサービス認証のアカウント絞り込み
VisualStudioの認証で使用しているアカウントに紐づくテナントを絞り込めば同じ状態になるかな?
と思い試してみましたが結果は変わりませんでした。
lauch.settings.json
でAZURE_TENANT_ID
を指定する
この方法はうまくいきました。実行ソース側をいじるのが嫌であればこちらを指定するほうが良い感じですね。
おわりに
認証まわりで沼ると解決まで時間がかかるので
ローカルでの開発時はユーザーシークレットがド安定ですね😏
msal.js v2.0.1 をAngularで使用する
はじめに
今回の記事で使用するコードは下記の構成で実装しています。
記事を参照されるタイミングによっては動作しない可能性がありますのでご留意ください。
- @angular/cli:10.0.5
- @azure/msal-browser:2.0.1
msal.js v2.0.0がリリース
先月の末頃(2020年7月21日)にmsal.jsのv2.0.0 がリリースされました。
(色々試している間にv2.0.1がリリースされちゃいましたが…
PKCEの対応が行われ、よりかんたんにセキュアに使用することができるようになります。
詳細なお話は公式のドキュメントや、今月の.NETラボを見ていただくとして…
ひとまず、僕がメインで使用しているAngularでの使用方法を確認していこうと思います。
@azure/msal-angular
msal.jsには派生ライブラリとして、Angular対応された@azure/msal-angular
というライブラリが存在します。
ServiceやInterceptorが提供され、Angularで使用するのに便利なライブラリとなっています。
👆microsoft-authentication-library-for-jsより抜粋
上図の通り、@azure/msal-angularもv2.0がPKCE対応されるはずなのですが、まだリリースされていないようです。
Roadmapにも特に予定は明記されていないようですが
何はともあれ使用したいので、プレーンな@azure/msal-browser
をAngularで使用してみましょう。
@azure/msal-browserをAngularで使用する。
基本的な使い方は@azure/msal-browserのドキュメントの通りで問題ありません。
ライブラリのインポート
npm install @azure/msal-browser
msalアプリケーションインスタンスの生成
msalのv1からmsalの処理のエントリークラスがUserAgentApplication
からPublicClientApplication
に変更されています。
インスタンスの生成にADアプリなどの設定のConfiguration
を与えるあたりの使い方は変わらないようです。
Configurationの構成自体も大きく変わっていないようで、clientId
やauthority
などしか項目を使っていない場合は設定記述部分は変更する必要はありません。
handleRedirectCallback
loginRedirect
などを行った場合に引っ掛けるhandleRedirectCallback
は大きく変更されています。
Promise
で実行されるようになっておりそれに合わせた変更が必要です。
const res = await this.clientApp.handleRedirectPromise().catch(err => { console.log({err}); });
自分は検証でエラーをトラップしたかったので雑に👆の実装にしていますが、resがvoid | AuthenticationResult
となるのでthen
を使うのもありです。
handleRedirectPromise
の内部処理実行前にloginRedirect
処理などが走るとエラーが発生するので
handleRedirectPromise
の処理終了を待機して、処理終了後にログイン処理を行うのがベターです。
login
loginを行う処理のインターフェースは変わっていません。loginRedirect
、loginPopup
でログインを実行します。
Redirectは前述するCallbackで、PopupはPromiseで結果を取得します。
ユーザー情報の取得
msal.jsのv1ではユーザー情報を取得するインタフェースがClientApp上に用意されていましたが、v2でなくなりました。
msal.jsのv2では、ログインやトークン取得時の結果としてアカウント情報が含まれた状態で返却されるので
そこからデータを取得することになります。
リダイレクトの場合は下記のようにhandleRedirectPromise
で取得できたレスポンスから抽出することになります。
const res = await this.clientApp.handleRedirectPromise().catch(err => { console.log({err}); }); this.account = (res as AuthenticationResult).account;
PopupはloginPopup
の返却値です。
アクセストークンの取得
acquireTokenSilent
で必要な引数にアカウント情報のパラメータが追加されています。
const res = await this.clientApp.acquireTokenSilent({ scopes: this.scopes, account: this.account }); return res.accessToken;
ログイン時などにアカウント情報は取得し、プライベートな変数なりに格納しておいたほうが良さそうです。
ここまでの実装はAngular特有というものではなく、普通の@azure/msal-browserの使用の仕方ですね。
本家のmsalServiceよろしく、一つのServiceクラスに👆までの処理を実装しておくと使いやすいと思います。
Interceptorの作成
Angularのサービスらしく。ということでInterceptorを作成し、WebAPIへの通信時に自動的にヘッダにトークンを付与させます。
ここは@azure/msal-angularのInterceptorをちょっとだけ参考にします。
[ { endPoint: string, scopes: string[] } ]
上記の構造の設定を使用して、アクセスするエンドポイントによって
使用するスコープを切り替える機能を実装していきます。
(👇本家と比べるとかなり雑ですが…!
@Injectable() export class MsalInterceptor implements HttpInterceptor { constructor( private auth: MsalService ) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const target = environment.endPointTargetScope.filter(f => req.url.includes(f.endPoint)); let scopes = []; if (target.length > 0) { scopes = target[0].scopes; } if (!scopes || scopes === []) { return next.handle(req); } return from( this.auth.acquireTokenSilent(scopes) .then((token: string) => { const authHeader = `Bearer ${token}`; return req.clone({ setHeaders: { Authorization: authHeader, } }); }) ) .pipe( mergeMap(nextReq => next.handle(nextReq)) ); } }
ひとまずこれで、ログイン👉アクセストークン取得👉WebAPI通信の必要最低限な動きは確認できるようになります。
まとめ
Angularでのmsal.js v2.0.xの使用の仕方を見てきました。
@azure/msal-angularのv2がリリースされる可能性はありますが
@azure/msal-browser準拠のインターフェースや使い方になりそうな気はしているので
これを使用して、変わりそうな箇所を抑えておくのも良いかもしれません。
今回検証で使用したソースコードは下記にのごった煮レポジトリ格納しています。
AngularのRouterでuseHashをtrueにしているときにmsalで`Error: Cannot match any routes. URL Segment: 'access_token'`が出るときの対処
はじめに
2020年7月時点の記事となります。
msal.jsのv2が出たのですが、書きかけだった記事の供養となります。。ご留意ください。
Angular・msal.js・@azure/msal-angularは下記のバージョンとなっています。
- Angular: 10.0.1
- msal.js: 1.3.2
- @azure/msal-angular: 1.0.0
検証を行うバージョンによっては動作が異なることがありますのでご留意ください。
何が起きているか
タイトルのとおりですが、AngularのRouterModuleを設定するときにuseHash
をtrue
にすると
Angularのルーティングはhttp://localhost:4200/#/<path>
のような#
付きのパスとなります。
AzureADでimplicit flowでトークンが返却される場合、リダイレクトURLに#access_token=~~
のようにトークン情報が付与されて返却されているようです。
- fragmentでトークンが送られる
- Angularのルーティングで#付きのパス=遷移と誤認し遷移を実行
- 実際のパスは当然存在しないのでエラー
この流れが原因のようです。
queryで返却されるようになれば良かろうかとも思うのですが
msal.jsのConfigをさらっと眺めた感じだと、responce_modeをいじる設定はないように見えるので別の方法を模索してみます。
どのように解消するか
routerのeventをsubscribeするのも良いですがちょっと手間なのでシンプルな解決方法が望ましいですね。
解決方法1:BootstrapModuleの変更
window.parent
かどうか判定し、IFrame内部であればRouterを持たないダミーのモジュールを流し込めば良いはずです。
「MSAL.js を使用してトークンを自動的に取得、更新するときにページのリロードを回避する」が参考になるかと思います。
別の問題の解消方法ですが、これに似た方法で対応が可能となります。
ただ、上記のサイトに上げた方法だと、useHash=true
にしたRouterModuleをImportすることになるので、問題となっていた現象を解決できません。
そこで、window.parent
による分岐をAppModule下ではなく
もう少し上のレイヤーで実行し、BootstrapするModuleをRouterを持たないダミーのModuleに変更する。といった対応を行います。
処理の分岐を下記のようにmain.ts
で実行します。
if (window !== window.parent && !window.opener) { // IFrame内で実行されるダミーモジュール platformBrowserDynamic().bootstrapModule(RedirectDummyModule) .catch(err => console.error(err)); } else { // 通常のモジュール platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.error(err)); }
解決方法2:リダイレクト先の変更
そもそもAngularを使用していないプレーンなダミーサイトにリダイレクト先を変更しちゃおうという手です。
「ADAL Wiki - FAQ - Q1. My app is re-loading every time adal renews a token.」が参考になります。
これはadalの記事ですがmsal、@azure/msal-angularでも同様の実装が可能となります。
リダイレクト先のHTMLとして、下記のようなHTMLを用意しておきます。
リダイレクト時点でmsalのUserAgentApplicationインスタンスが生成されていないとエラーとなるので
msal.jsの読み込みと、UserAgentApplicationのインスタンス生成を行う必要があります。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Frame Redirect</title> <script type="text/javascript" src="https://alcdn.msftauth.net/lib/1.3.2/js/msal.min.js" integrity="sha384-4cH4i4aLXsdIJJz5DF27S+GxZXRSqBORS4NN7kc7NyZBBIugxFyt6hQt5FiR/DuZ" crossorigin="anonymous"></script> </head> <body> dummy <script> var msalConfig = { clientId: "<Azure AD App Client ID>" }; var authContext = new Msal.UserAgentApplication(msalConfig); authContext.handleWindowCallback((err, res) => { console.log({ err, res }); }); if (window === window.parent) { window.location.replace(location.origin + location.hash); } </script> </body> </html>
仮に、このHTMLがredirect-page.html
とすると
アクセストークン取得時などにリダイレクトURLとしてこのURLを指定します。
// 👇msal-angularのMsalService.acquireTokenSilent() // 👇msal.jsのUserAgentApplication.acquireTokenSilent() const res = await this.msalService.acquireTokenSilent({ scopes: [ 'スコープ'], redirectUri: 'http://localhost:4200/redirect-page.html' // 👈こんな感じ });
デプロイする時も、今回作成したredirect-page.html
が必要になるのででangular.json
でデプロイ対象に指定しておきましょう。
{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", .... "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { ... "assets": [ ... "src/redirect-page.html" 👈追加 ], ...
まとめ
そもそも別問題の対応のために、解決方法1や2を行っていたので今更見つけた感じになります。
無限リダイレクト問題とかにぶつかって、解決方法1,2とかに行き当たる方は多いと思うので知らん間に対応している。といったことが多そうです。
useHash:trueで且つ、そこらの対応がされていないとかのすこしニッチな状況のものになりますね。
msalのv2でこの記事が無意味なものに変わってくれるといいなーと若干期待しています。
今回検証で使用したリポジトリは下記となります。
Microsoft Graphのpresenceについて
修正
2020/7/19 : presenceのβ公開時期を2020年6月から2019年12月に修正しました。
(@karamem0さんご指摘ありがとうございました!)
2020/8/2:presenceのSubscriptionのドキュメントが公開されたので反映しました。
はじめに
2020年7月時点のMicrosoft Graphを使用した記事となります。
また記事で扱うpresenceは2020年7月段階でβ状態なので
参照されるタイミングによっては仕様が変更されている可能性がありますのでご留意ください。
presence
2019年12月に取得WebAPIがβ公開され、2020年7月に変更通知がサポートされました。
presenceとは具体的になんなのでしょうか。
めちゃくちゃかんたんに説明すると👇コレです。
presenceの取得
Graphの変更履歴の7月からのリンクは404なのですがドキュメントは確認できます。
presenceのデータを取得するのは下記のWebAPIで取得します。
※今はβしか公開されていないのでhttps://graph.microsoft.com/beta/
エンドポイントからしかアクセスできません。
GET /me/presence GET /users/{id}/presence GET /communications/presences
GET /communications/presences
と記載されていますが素直に使用しても複数人取得できず404になりました。
ユーザーid指定が必須のようなので、/communications/presences/{id}
が正しいと思います。
複数人のpresenceの取得はgetPresencesByUserIdです。
POSTで取得したいユーザーのidをBodyに指定するので、ちょっと違和感がありますね。
POST communications/getPresencesByUserId { "ids": ["<UserId>", "<UserId>"] }
presenceで取得するプロパティについて
取得するプロパティは少なく、id
、activity
、availability
の3種だけです。
👆のドキュメントはめったくそややこしいですが、動作させて確認したところこれで正しいようです。
(user's availabilityがavailibityじゃないんだ…的な)
Presenceの状態については、Teams でのユーザーのプレゼンスで確認できます。
availabilityとactivityの対応は下図の通りの認識です。
presenceの変更通知
2020年7月に変更通知に対応されました。
CreateSubscriptionにはリソースの明記はありませんが、OverViewによると/communications/presences
のようです。
presences
の取得から考えると単一ユーザー指定の/communications/presences/{id}
の指定になるようです。
IDなしやカンマ区切りの複数指定は400になりました。
また、Subscriptionの作成では有効期限を指定する必要があります。
ドキュメントに記載の通り、1時間のようです。
サブスクリプションの作成は他のリソースのときと同様、下記のようなJSONをPOSTすることで作成できます。
{ "changeType": "updated", "notificationUrl": "<エンドポイントURL>", "resource": "communications/presences/<user id>", "expirationDateTime": "<有効期限>" }
複数指定する場合は$filter
を使用して下記のように指定するようです。
"resource": "communications/presences$filter=id in ('<userid>', '<userid>'...)",
他のリソースのSubscriptionを作成する時とそこまで大きく変わるところはありませんね。
変更通知で通知される内容
Subscriptionを作成して内容を確認した結果、下記のようなデータが取得できました。
{ "value": [ { "subscriptionId": "cc87f8f1-45aa-4b33-9dd3-660bbaf3ed57", "clientState": null, "changeType": "updated", "resource": "communications/presences('<userid>')", "subscriptionExpirationDateTime": "2020-07-18T23:03:50.4305891-07:00", "resourceData": { "@odata.type": "#Microsoft.Graph.presence", "@odata.id": "communications/presences('<userid>')", "id": null, "activity": "InACall", "availability": "Busy" }, "tenantId": "<tenantid>" } ] }
activity
もavailability
も取得できているので、TeamsやEventのように別途WebAPIでリソースデータの取得などをする必要はなさそうです。
おわりに
β公開中のpresence
の取得と変更通知の使い方を見ていきました。
第 1 回 Japan M365 Dev User Group 勉強会ではこれをネタにLTする予定です。
よろしくおねがいします。
Angular Elementsで作成したWeb Componentsの設定情報をプレーンなJSから変更する
はじめに
この記事内のアプリケーションは下記バージョンで構成しています。
- Angular CLI: 10.0.1
- Angular: 10.0.2
この記事を参照されるタイミングによっては
サンプルコードが動作しなかったりする可能性がありますのでご留意くださいませ。
そもそもなにをしたいのか
Microsoft GraphはGraph ToolkitというWeb Componentsを公開していまして
オリジナルでそれを作ってみたいなー。というのが発端です。
今回の記事はその前哨戦。
Web Componentsを作ってみてハマった箇所の備忘録。といった感じですね。
Angular Elements
Angularを使用している場合は、Angularで作成したComponentをWebComponentsでパッケージングしてくれる
Angular Elementsというものが存在します。Angularで諸々なれている自分はこれを使うのが一番手っ取り早そうです。
基本的な使い方などは、👆のドキュメントを参考にすれば良いと思うので割愛します。
最終的に下記のようにAngularを使用していないようなプレーンなHTMLで
作成したWebComponentsのセレクタを指定してAngularで作成したComponentを表示させることが出来ます
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="../../dist/custom-element/styles.css"> <script src="../../dist/custom-element/custom-element.js"></script> </head> <body> <h1>Web Component (Costum Elements)</h1> <scl-custom-button></scl-custom-button> <scl-sample-form-group></scl-sample-form-group> <scl-router-page></scl-router-page> </body> </html>
👇
情報を設定する🤔
さて、Micorosoft Graphにアクセスするということは、ADのアプリケーション情報などが必要になります。
設定情報をWeb Components作成時点でビルドインしても良いのですが、それでは使い回しがしにくいです。
具体的には下図のような構成にしたいところですね。
ビルドされたソースはどうなっている?
ビルドされたパッケージ内のServiceクラスに値を直接外部から突っ込めるでしょうか?
下記のTSがビルドされたソースを整形して見てみます。
export class LibSettingsComponent implements OnInit { ... constructor( private service: LibStateService ) { } ... set setting(setting: { state: string, userId: string, userName: string }) { console.log('emit'); this.service.setting(setting); console.log(this.service.state); this.dummyStSet = 'emit'; } }
Oy), By = ((Iy = function() { function e(t) { _classCallCheck(this, e), this.service = t, this.dummySt = "dd" } return _createClass(e, [{ key: "ngOnInit", value: function() {} }, { ... }, { key: "setting", set: function(e) { console.log("emit"), this.service.setting(e), console.log(this.service.state), this.dummyStSet = "emit" } }]),
this.service
はfunctionの引数のt
で更にそこの呼び出し元の…?
いずれにせよServiceのインスタンスに直接アクションをするのは多大な労力を伴いそうです。
設定用のComponentを用意する
Componentからのアクセスは簡易に行えます。なので設定用のComponentを作成する。というアプローチをとってみます。
中身が空の設定用のComponentを用意します。
設定用のComponentは@Input
でSetterのみを提供し、Setterの実装内で設定情報Serviceを書き換えます。
具体的なソースコードは下記のとおりです。
色々視認やデバッグしやすいようにHTMLテンプレートにものを突っ込んでますが空で問題ないと思います。
import { Component, OnInit, Input } from '@angular/core'; import { LibStateService } from '../../services/lib-state.service'; @Component({ selector: 'scl-lib-settings', template: '<div>{{dummySt}}</div>' }) export class LibSettingsComponent { dummySt = 'bf'; constructor( private service: LibStateService ) { } @Input() set setting(setting: { state: string, userId: string, userName: string }) { // ここで設定情報をServiceに反映する this.service.setting(setting); this.dummySt = 'emit'; } }
これでプレーンなHTMLのタグ上などで、setting
プロパティに値を突っ込むことで設定情報が反映されるようになりました。
設定用のコンポーネントタグを書かなくて良いようにする
さて、設定を外から与えることができるようになりましたが
何も情報を出力しない設定用のコンポーネントをHTML上に書くのはなんともダサ味があります。
なので、スクリプトでも設定情報の反映ができるようにしたいところです。
そこで、
こんな感じの設定スクリプトをAngular Elementsから提供することにします。
設定スクリプトは下記のようなものです。
class LibSettings { setSettings(data) { const settingDom = document.createElement('scl-lib-settings'); document.body.appendChild(settingDom); settingDom.dummySt = 'hoge'; settingDom.setting = data; document.body.removeChild(settingDom); } }
作成した設定スクリプトをWebComponentsをビルドする際に一緒に吐き出したいので、angular.jsonをいじります。
"custom-element": { // Angular Elementsのプロジェクト名 ... "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { ... "scripts": [ // 👇を追加 "projects/custom-element/src/static-scripts/lib-settings.js" ]
これで、WebComponentsをパッケージングしたときに、同一ディレクトリに設定スクリプトも吐かれるようになりました。
使ってみる
設定スクリプトで設定が反映されたサービスのプロパティをアラート表示するボタンComponentを作成しました。
このボタンを押下することで設定情報の反映の確認を行ってみます。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="../../dist/custom-element/styles.css"> <script src="../../dist/custom-element/custom-element.js"></script> </head> <body> <h1>Web Component (Costum Elements)</h1> <scl-custom-button></scl-custom-button> <scl-sample-form-group></scl-sample-form-group> <scl-router-page></scl-router-page> <script src="../../dist/custom-element/scripts.js"></script> <script type="text/javascript"> // 設定スクリプトを使用して設定情報の反映 const cl = new LibSettings(); const setting = { state: 'HTMLから設定したよ!', userId: 'user', userName: 'hogehoge' } cl.setSettings(setting); // </script> </body> </html>
上記の状態で、アラートに「HTMLから設定したよ!」と表示されればOKです。
設定できました。
これで設定情報を外部から与えることができそうです。
おわりに
長い前哨戦が終わったので、次回はようやく目的だったオリジナルのGraph Toolkitの作成に入ってみます。
手を付けれていないのでいつになるかわかりませんが
今回検証を行ったソースコードは下記となります。
色々検証で試行錯誤したので少々とっちらかっているのはご容赦くださいませ…
Microsoft MVPを初受賞しました
タイトルの通りMicrosoft MVPを初受賞しました。
カテゴリはOffice Developmentとなります。
登壇の機会を多くくださった.NETラボの皆様
セッションを聞いてくださった皆様
フィードバックをくださった皆様
個人の活動であるにも関わらず会社でぼくの活動をサポートしてくださった皆様
僕の活動にあらゆる形で関わってくださった皆様にお礼申し上げます。
本当にありがとうございました。
これからも楽しんで技術を追いかけ続けようと思います。
ただ看板を頂いた以上はそれに恥じない動きを心がけたいと思います。
感想
初受賞ということもあり、Twitterで感想おさまらんなー。というわけで感想をポストしています。
Officeの名を冠するカテゴリで受賞させていただいたのですが
僕のブログは「Office」で検索しても1件も引っ掛からないレベルでOfficeに触れていません。
(1件くらいあるやろと思ってたので「マジか」と自分でも思いました。。。)
- よく言えばMicrosoft Graph特化で
- OfficeというよりOfficeのデータに熱中していて
- Webや.NETといったフロント方面から執拗にOfficeのデータにアプローチを行っている
そんな様が審査時に「変な人」的に印象にのこったかもしれないなーとか思ったりしています。
今後の話
受賞したからには継続して受賞していきたいと思いますし
Azureが好きで、Angularが好きで、.NET Coreが好きで、Microsoft Graphが好き。といった状況もまだまだ続くと思います。
そんな自分なので、Microsoft Graphはもちろんですが、Microsoft Graph以外も含め、多くの情報発信ができれば良いな。と考えていますので
今後ともよろしくおねがいいたします🙇
蛇足
受賞メールが届いた日は興奮したのか一睡もできず、かなり久々に完徹して出勤する事になりました。
結果、お昼すぎに睡魔に勝てず途中リタイア。
自分の老いにも確かな手応えを感じてしまったので、体調にも気をつけつつ頑張ろうと思います。。
AzureDevOpsのCIで.NET CoreとAngularのアプリケーションをバージョンアップする
はじめに
この記事内で使用する各フレームは下記バージョンで構成しています。
- Angular: 9.1.8
- .NET Core: 3.1
また、AzureDevOpsは記事作成時点のキャプチャやコマンドが掲載されています。
この記事を参照されるタイミングによっては
動作しなかったり画面が違うといった可能性がありますのでご留意くださいませ。
モチベーション
アプリケーションのバージョンアップ作業って地味で忘れがちです。
そういった作業は自動で行ってしまいたい!!
と、いうわけでAzure DevOpsのCIフローの中でバージョンアップ作業ができないか試してみました。
僕が主に使用しているのは.NET CoreとAngularのアプリケーションですので
その二つのアプリケーションでバージョンアップを実施するCIを組んでみようと思います。
.NETアプリケーション
.NETのアプリケーションのバージョンアップをCIで実現しようと思います。
.NETアプリケーションのバージョン番号はメジャー.マイナー.ビルド番号.リビジョン
で表現されますが
ビルド番号、あるいはリビジョンに*指定で、ビルド時に自動的にバージョンアップをしてくれます。
なので普通に運用する場合は何も考えなくてもバージョンアップはしてくれるのですが
バージョン付番を独自のルールで運用したり、付番したバージョンをデータストアに格納したりとかしたい場合があります。
なので、今回は独自ルールでバージョンを付番してアプリケーションを発行するCIを構築してみようと思います。
バージョンアップルール
今回は下記のルールでバージョンアップしようと思います
- 2020年6月2日からの経過日数をバージョン番号とする
- ビルドを行った日の00:00からの経過分数をリビジョンとする
.NETアプリケーションのビルドコマンドでバージョン番号を指定できるよう準備
MsBuld.exeを使用してビルドする際のコマンドでカスタムプロパティを指定できます。
そこで、ビルドコマンドのArgumentsでバージョンを変更できるようにします。
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>netcoreapp3.1</TargetFramework> <UseWPF>true</UseWPF> <PublishSingleFile>true</PublishSingleFile> <RuntimeIdentifier>win-x86</RuntimeIdentifier> <!-- バージョン番号のベースの経過日数を受け取る --> <BuildNumber Condition=" '$(BuildNumber)' == '' ">0</BuildNumber> <!-- リビジョンのベースの経過分数を受け取る --> <Revision Condition=" '$(Revision)' == '' ">0</Revision> <!-- バージョンの指定--> <AssemblyVersion>0.0.$(BuildNumber).$(Revision)</AssemblyVersion> <VersionPrefix>0.0.$(BuildNumber).$(Revision)</VersionPrefix> </PropertyGroup> </Project>
これで、MsBuild.exe <プロジェクト> /p:BuildNumber=<適当な数字> /p:Revision=<適当な数字>
でバージョンをコマンド上で指定できるようになりました。
MsBuild.exe <プロジェクト> /p:BuildNumber=1 /p:Revision=2
した結果は下図のとおりです
CIの設定
CIで使用する変数の設定
CIの複数のタスクでバージョン情報を使い回すので、その情報を格納する変数を指定します。
経過日数を格納する$days
と経過分数を格納する$dayinterval
を用意しています。
variables: days: 0 dayinterval: 0
Power Shell
まずはPowerShellでコマンドで指定するバージョン番号とリビジョンを生成しています。
現在日付をゴニョゴニョして、$days
と $dayinterval
変数に格納します。
- 2020年6月2日からの経過日数をバージョン番号とする
- ビルドを行った日の00:00からの経過分数をリビジョンとする
ってことをやっています。
- task: PowerShell@2 displayName: 'Calc Build Version' inputs: targetType: inline script: | $baseDate = [datetime]"06/02/2020" $currentDate = $(Get-Date) $interval = NEW-TIMESPAN -Start $baseDate -End $currentDate $days = $interval.Days Write-Host "##vso[task.setvariable variable=days]$days" echo $days $today = Get-Date -f d $startdate = Get-Date $today $todayinterval = NEW-TIMESPAN -Start $startdate -End $currentDate $dayinterval = [Math]::Truncate($todayinterval.TotalMinutes) Write-Host "##vso[task.setvariable variable=dayinterval]$dayinterval" echo $dayinterval
Write-Host "##vso[task.setvariable variable=<変数名>]<PowerShell内の変数(突っ込みたいValue)>"
で👆で作成した変数に突っ込めます。
このPowershellでは、👆で指定したdays
とdayinterval
に値を突っ込んでいますね。
バージョンの情報を管理しているサービスなんかがある場合は
WebAPIを作成して、Invoke-WebRequest
コマンドを使用して引っ張ってきたり登録したりすればいいかなと思います。
MsBuild
MsBuildで👆で指定したバージョンを指定してビルドを行うコマンドを作成していきます。
- task: MSBuild@1 inputs: solution: '**/<プロジェクト名>/**/*.csproj' configuration: Release msbuildArguments: /t:Publish /p:BuildNumber=$(days) /p:Revision=$(dayinterval)
これはそこまで複雑なことはしていないですね。
PowerShellで設定した変数をMsBuildで使用するカスタムプロパティに指定しています。
実行結果
PowerShellタスクで指定するバージョン情報を出力しています。
Publishされたファイルのバージョンが、Powershellで生成した情報で構成されていることが確認できました。
Angularアプリケーション
普通にWebアプリケーションを作成するだけであれば特に意識する必要はないと思うのですが
ライブラリを作成してPublicなりPrivateなnpmレジストリにPublishしたい場合、バージョンアップは必要な作業です。
基本はnpm version
コマンドでバージョンアップしていけば良いかなと思っています。
バージョンアップルール
今回は下記のルールでバージョンアップしようと思います
master
ブランチのCIはnpm version patch
でパッチバージョンを上げるdevelop
ブランチのCIのときはプレリリースでdev
バージョンとしてリリースするnpm version prepatch --preid=dev
でバージョンを設定する- 参考: npm-version
CIの設定
ちょっとCIの順番と連動していないのですが、行う作業の関連など考慮して順不同で説明しています。
Power Shell
npmコマンドをPowerShellで実行します。
このPowershellコマンドでは下記のことを実行します
npm version
の実行- ライブラリの
package.json
のバージョンを上げたいのでライブラリのディレクトリで行う
- ライブラリの
- バージョン変更をリモートリポジトリにPushする
- そうしないと次回以降も同一のバージョンが生成されてしまうためですね
参考: MS Doc - Run Git commands in a script
参考: MS Doc - Build Azure Repos Git or TFS Git repositories
参考: Stack Overflow - How to increase a version of an npm package using Azure Devops pipeline
先にyamlを掲載すると👇な感じになります。
- task: PowerShell@2 displayName: 'Version Up & Commit' inputs: targetType: inline script: | git config user.name "learn-angular-library-ci Build Service (devtakas-public)" git config user.email "dummy@example.com" $BranchName = "$(Build.SourceBranch)" -replace "refs/heads/" git checkout $BranchName --quiet cd projects\sample-lib npm version $env:VERSIONCOMMAND --preid=dev -m "$env:VERSIONCOMMENT" --force --silent git add . git commit -m "$env:VERSIONCOMMENT" git push --quiet
Script内1~2行目
git config user.name "learn-angular-library-ci Build Service (devtakas-public)" git config user.email "dummy@example.com"
gitにコミットするためにユーザーの情報などを設定しています。
ここは好きな値で問題ないです。
Script内3~4行目
$BranchName = "$(Build.SourceBranch)" -replace "refs/heads/" git checkout $BranchName --quiet
Azure DevOpsのCI実行ログを見ればわかるのですが
ソースコードの操作が行われているブランチは厳密にはCIコマンドで指定したブランチと異なります。
バージョン変更を行ったコミットをPushしたいブランチはCIで指定するブランチなので
環境変数に格納されている作業ブランチをベースに作業ブランチのチェックアウトを行う必要があります。
Script内最後の行まで
cd projects\sample-lib npm version $env:VERSIONCOMMAND --preid=dev -m "$env:VERSIONCOMMENT" --force --silent git add . git commit -m "$env:VERSIONCOMMENT" git push --quiet
作業ディレクトリをライブラリのpackage.jsonがあるところまで移動して諸々作業します。
$env:VERSIONCOMMAND
はAzure DevOpsのCI上で設定する変数です。
masterのCIではpatch
、developのCIではprerelease
を指定しています。
AzureDevOpsのCIでは、rootディレクトリ以外ではnpm version
してもCommitが発生しなかったので
別途 git add .
とgit commit
しています。
警告などでるとAzure DevOpsのCIでエラーを吐いてしまうので
適宜--quiet
や--silent
でエラーにならないように回避しています。
参考: Stack Overflow - How to generate NPM release candidate version
参考: はらへり日記 - npm scriptsでエラーログを表示させたくない話
認証の設定
参考: MS Doc - Run Git commands in a script
AzureDevOpsで管理されているリポジトリに対してPushを行いたいので
ソースを取得する段階で認証情報を設定しちゃいます。
下記で設定可能です。
steps: - checkout: self persistCredentials: true
Trigger
例えばdevelop
ブランチの変更があった場合、develop
ブランチにCommit/Pushが行われます。
つまり、なにも考慮しないと無限ループになります(なった)。
自分は、単純に下記の通り自動変更される対象のファイルをexclude設定にしました。
trigger: branches: include: - develop paths: exclude: - projects/sample-lib/package.json
ここは人により設定が変わりそうなところですね。
ユーザーの設定
CIには実行するユーザーが割り振られています。
なのでそのユーザーがソースコードに対してCommitしたりPushできたりするように設定する必要があります。
参考: MS Doc - Run Git commands in a script
設定場所は下図から確認できます。
Usersの<プロジェクト名> Build Service
という名前に基本なっていると思います。
設定は下記のとおりですね。
Contribute
/Read
- 必須
Create Branch
/Create Tag
- 必要があれば
- リリース時にタグ付けしたいとか、自動コミットじゃなくてブランチとPR作りたいとかの場合かな?と思います
Bypass policies when pushing
- ブランチポリシーで保護しているブランチに対してPushしたい時に必要です。
実行結果
今回はDevOps上のArtifactsにライブラリをPublishしました。
- developブランチのCIの場合
preleaseで出力されていることが確認できます。
- masterブランチのCIの場合
patchで出力されていることが確認できます。
おわりに
今回使用したリポジトリは下記の通りです。
Angularの方はブランチポリシー下の動作検証のためAzure DevOpsでソース管理もしています。
.NET Core
Angular
供養
長く苦しい戦いの記録