はまったりひらめいたり…とか…

Angularや.NETやAzureやその他色々。

まんがリマインダーWebサービス(α)を公開してみました

Webサービスでまんがリマインダーを公開してみました。

manrem.devtakas.jp

f:id:TakasDev:20210815114038p:plain

どんなサービスなの?

キーワードを登録しておくと近日中に発売される/された漫画の一覧が表示されます。以上。

ブラウザのlocalstorage内にキーワード情報とか格納しているのでサイトへの登録とか必要ありません。

(ユーザー登録が必要ないサービスを自分が欲しかったので作ったというのもある)

あとはキーワードのブラウザ間の連携機能だったり

今調整中で一部の人(自分)しか使えませんがGoogleカレンダーへの連携機能だったりがあります。

私の欲しいもの且つ実験場的な要素が強いので、予告なく機能が追加されたり改善されたり変更されたりしますのでご了承ください。

構成の話

f:id:TakasDev:20210822160956p:plain

上図な感じの構成です。

  • メインはAzureStaticWebAppsを使用
  • WebAPIはAzure Functions
    • Bring Your Owns Functionsしてみたかったのでそのような構成
  • フロントはAngularで構成。バックエンド側はすべてC#で構成
  • ストレージは今の所localStorageとSQL Databaseがメイン
  • 漫画の発売情報元はRakuten WebAPI
    • WebAPIの仕様上長時間のバッチ実行になりそうだったのでDurable Functionsで対応
    • Amazonはなんか面倒くさかった(アフィとか色々めんどかったので気軽に使える方を使用)
    • 画像データも合わせて取得して半年くらいキャッシュ効くように設定した上でBLOBに格納

StaticWebApps使ってちょっとサイト組んでみたい。。っていうのと

最近自粛生活で漫画買い忘れが頻発したのでちょうどええし作ってみるか。ってのが経緯です。

割と勢い駆動開発をしてきたので、Azureの構成等々改善する余地はまだまだありそうな感じです(すでにLoadが重い)。

ソースコードの構成の話

f:id:TakasDev:20210822161237p:plain

上図のような構成です。

  • 一つのリポジトリでフロントからDBまで管理
    • モノレポ構成ってやつですね
  • リソースはすべてGitHubActionsでデプロイ

GitHub上で公開しています。気になる方は見てみていただければと。

まさかり歓迎しています。

GitHub - Takas0522/ComiCal

今後の話

やりたいことはGitHubのIssueに書いています。

ほぼ自分用のサービスなので実験場にもなりそうな気はしていますが

なにか追加してほしい機能とかあればIssueかDiscussionに書いていただければと思います。

AngularからGoogle Calendar APIでイベントを登録する

はじめに

この記事のソースコードは下記のバージョンで構成されています。

  • Angular: v12.1.3

参照されるタイミングによっては動作しない可能性がありますのでご留意ください。

やりたいこと

Front(Angular)のみで、任意のユーザーにサインインさせ、サインインしたユーザーのカレンダーに、Google Calendar APIを使用してイベント登録を行いたい。

想像以上にやり方が複雑怪奇な感じになったのでメモ書き程度に残しておきます。

GCPGoogle Calendar APIを使用するための準備

Google Calendar APIを有効にする

GCPのプロジェクト画面からAPIを有効にしていきます。

f:id:TakasDev:20210731212435p:plain

認証の設定

f:id:TakasDev:20210731212726p:plain

「認証情報を作成」から「OAuthクライアントID」を選択します

設定対象 設定内容
アプリケーションの種類 ウェブアプリケーション
名前 任意
承認済みのJavaScript生成元 http://localhost:4200
承認済みのリダイレクトURL http://localhost:4200

OAuth同意画面(スコープ)の設定

f:id:TakasDev:20210731213219p:plain

「OAuth同意画面」の設定の入力項目はほぼ任意な内容です。

「スコープを追加または削除」から必要なスコープを追加します。

必要なスコープはドキュメントを参考にします。今回はカレンダーにイベントを追加したいという目的です。

必要な処理は下記の通りなので、それぞれの処理で必要なスコープを確認しましょう。

  1. カレンダーのリストの取得
  2. 1.で取得したいずれかのカレンダーにイベントを登録

上記のドキュメントから必要なスコープは下記の通りでした

Angular側の実装

index.htmlの変更

GCPAPIをどうこうするのに使用するライブラリなんかはnpmなどで配布されていないように見受けられました。

ひとまずここは公式のドキュメント通りにスクリプトファイルをindex.htmlで読み込みます。

<!doctype html>
<html lang="jp">
<head>
  <script defer src="https://apis.google.com/js/api.js"></script> <!--追加-->
  <meta charset="utf-8">
  <title>LearnGoogleOAuth</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

各種@typesをインストール

とはいえTypeScriptな型の世界で開発を行いたいので、型定義をインストールします。

npm install --save-dev @types/gapi @types/gapi.auth2 @types/gapi.calendar

今回使用するGAPIのベース、認証、カレンダーAPIの型定義です。

型定義を使用したいファイル内で定義を読み込んでおきましょう

/// <reference types="gapi"/>
/// <reference types="gapi.auth2"/>
/// <reference types="gapi.calendar"/>

GAPIのLoad処理

GAPIのAuthやCalendarといったインスタンスgapiloadinitで生成されるようです。

それぞれのインスタンスをLoadする処理を実装します。

export class AppSerivce {
  ...
  private authClient!: gapi.auth2.GoogleAuth;

  clientLoad(): void {
    // gapi.authインスタンスを使用できるようにロードする(scopeはOAuthの同意画面で設定したScopeを指定)
    gapi.load('client:auth2', () => {
      this.authClient = gapi.auth2.init({
        client_id: environment.gapiClientId,
        fetch_basic_profile: true,
        scope: 'openid https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events ',
      });
      // gapi.client.calendarを使用できるようにロードする
      gapi.client.init({
        discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest']
      })
    });
  }
  ...
}

サインイン

上記でgapi.authインスタンスの準備ができている場合は下記のコマンドでサインインが実行できます。

  async signIn(): Promise<void> {
    const res = await this.authClient.signIn();
  }

f:id:TakasDev:20210731221133p:plain

カレンダーリストの取得

上記でgapi.client.calendarインスタンスの準備ができている場合は下記のコマンドでカレンダーリストの取得ができます。

  getCalendarList() {
    // gapi.client.calendarインスタンスが生成されていると使用できる
    const req = gapi.client.calendar.calendarList.list()
    req.execute((res) => {
      // resにカレンダー情報が返却される
    })
  }

イベントの登録

上記でgapi.client.calendarインスタンスの準備ができている場合は下記のコマンドでイベントの登録が行えます。

イベント登録時に使用できるパラメータはドキュメントを参考にしてください。

  registerEvents(calendarId: string): void {
    const event: gapi.client.calendar.EventInput = {
      summary: 'Test',
      start: {
        dateTime: '2021-07-31T00:00:00.000Z'
      },
      end: {
        dateTime: '2021-07-31T00:00:00.000Z'
      }
    };
    const req = gapi.client.calendar.events.insert({
      calendarId,
      resource: event
    });
    req.execute((res) => {
      // 結果がresに返却される
      console.log(res)
    });
  }
}

結果、下図のように登録を行えるようになりました。

f:id:TakasDev:20210731223051p:plain

gapi.cllient.calendarインスタンスを生成しないやりかた

gapi.client.calendarを介さずに、clientからリクエスト直接叩くことでもイベント登録は可能です。

  registerEvents(calendarId: string): void {
    const event: gapi.client.calendar.EventInput = {
      summary: 'Test',
      start: {
        dateTime: '2021-07-31T00:00:00.000Z'
      },
      end: {
        dateTime: '2021-07-31T00:00:00.000Z'
      }
    };
    const req = gapi.client.request({
      path: `/calendar/v3/calendars/${calendarId}/events`,
      method: 'POST',
      body: event
    });
    req.execute((res) => {
      // 結果がresに返却される
      console.log(res)
    });
  }
}

APIに対応するインスタンスの生成ができなかった場合や、インスタンス内にインターフェースがなかった場合なんかはこっちの方法になりますね。

サクッと検証したい場合もこっちのほうが楽だったりするかもしれません。

さいごに

APIに対応するインスタンスのLoadなど変わった実装が必要なので最初は面食らいました。

見たのはカレンダーだけですが他の@typesとか見るに、Googleが提供するOAuthを伴うWebAPIの実行は同じような実装でできそう?という印象です。

今回検証で実装した全体は下記のリポジトリに格納しました。

github.com

MSAL for Angular v2がGAしたので触ってみたお話

はじめに

この記事は下記の内容でお送りいたします。

  • Angular: v12.1
  • @azure/msal-angular: 2.0.1

参照時期によっては記述しているサンプルコードで動かないない可能性がありますので

その点ご留意ください。

@azure/msal-angularのv2がGAしました

@azure/msal-browserがGAしてから長らくプレビュー状態でしたが

Angularに対応したライブラリがGAされました。

MSAL for Angular v2 is now available - Microsoft 365 Developer Blog

なので、さっそく触ってみたお話になります。

@azure/msal-angular for Azure AD B2C

AADやMS Graphを使用したサンプルはDocsGitHubのリポジトリにありますので

今回はAzure AD B2CとAzure AD B2Cで保護したASP.NET Core WebAPIへのアクセスを実装してみようと思います。

ライブラリのインストール

npm i @azure/msal-browser @azure/msal-angular

msalの設定情報

まずはコアとなるPublicClientApplicationに食わせるための設定情報の作成です。

サンプルでは直接PublicClientApplicationに渡してあるパターンが多いですが、僕はenvironment.ts派です。

export const environment = {
  production: false,
  msalConfig: {
    auth: {
      clientId: '<AD B2C AppのClientId>',
      authority: 'https://<domain>.b2clogin.com/<domain>.onmicrosoft.com/<Signup/in Policy>',
      redirectUri: 'http://localhost:4200',
      knownAuthorities: [
        '<domain>.b2clogin.com'
      ]
    }
  },
  msalInterceptorConfig: [
    { resource: '/base-api/', scopes: ['<WebAPIのAzure AD B2Cスコープ>'] }
  ]
};

基本はmsal-browserの設定内容と同じです。Angularで特殊な箇所はmsalInterceptorConfig部分でしょう。

AngularのHTTP_INTERCEPTORのuseClassで使用できるMsalInterceptorが提供されています。

resourceで指定されたWebAPI宛の通信をHttpClientで行うときに、scopesで指定されたスコープのアクセストークンを取得/通信に付与してくれます。

まいどまいどアクセストークンを取得する処理なんかは書きたくないですし非常に重宝すると思います。

AppModuleの実装

ライブラリが提供するInjectionTokenに対して設定値やPublicClientApplicationインスタンスを設定してあげます。

(インジェクショントークンにこのインスタンスを渡してあげるというのはちょっと意外な構成でした)

// environment.tsの設定値を利用してPublicClientApplicationインスタンスを作成する
const msalInstanceFactory = (): IPublicClientApplication => {
  return new PublicClientApplication(environment.msalConfig);
}

// environment.tsの設定値を利用してMsalInterceptorConfiguration を作成する
const mealInterceptorConfigFactory = (): MsalInterceptorConfiguration => {
  const protectedResourceMap = new Map<string, Array<string>>();
  const conf = environment.msalInterceptorConfig;
  conf.forEach(f => {
    protectedResourceMap.set(f.resource, f.scopes);
  });
  return {
    interactionType: InteractionType.Popup,
    protectedResourceMap
  }
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    {
      provide: MSAL_INSTANCE,
      useFactory: msalInstanceFactory // インスタンスを設定
    },
    {
      provide: MSAL_INTERCEPTOR_CONFIG,
      useFactory: msalInterceptorConfigFactory // インターセプターの設定情報を渡す
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: MsalInterceptor, // インターセプターをProvideする
      multi: true
    },
    MsalService, // MsalServiceをProvideする
    MsalBroadcastService // MsalBroadcastServiceをProvideする(ログとか出したい場合)
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

AppComponentの実装

サンプルなんかではログインはボタン押下など明確なアクションをトリガーに実行されるのですが

僕がよくやる実装なんかは、ngOninit時点でログイン済みか検証→ログイン情報なかったらloginRedirect()みたいな実装にすることが多いです。

なのでそのやり方で実装してみようと思います。

export class AppComponent implements OnInit {

  constructor(
    private httpClient: HttpClient,
    private authService: MsalService
  ) {}

  ngOnInit() {
    this.authSettingInit();
  }

  private async authSettingInit() {
    await this.authService.instance.handleRedirectPromise();
    // ↑がないとエラー吐く
    this.authService.handleRedirectObservable().subscribe(x => {
      if (x) {
        console.log({handleRedirectObservable: x})
      }
    });
    const ac = this.authService.instance.getAllAccounts();
    if (ac && ac.length > 0) {
      return;
    }
    this.authService.loginRedirect();
  }

  request(): void {
    this.httpClient.get('/base-api/WeatherForecast').subscribe(x => {
      console.log(x);
    });
  }
}

すこし特殊なのが諸々の実装が走る前にawait this.authService.instance.handleRedirectPromise();をしている点ですね。

これがないとどうなるかというと下記のようなエラーがログイン後に発生します

BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. For more visit: aka.ms/msaljs/browser-errors.

handleRedirectObservableが諸々の準備が完了する前に呼ばれているからのようです。

対応としては、上のソースで記述している通りで、MsalServiceはPublicClientApplicationのインスタンスinstanceに持っているようなので

それのhandleRedirectPromiseでawaitしてあげると安定して動作します。

というかhandleRedirectPromiseを実行している時点でhandleRedirectObservableはする必要がなくなります。

なので、今回のパターンのようにロード時に即ログイン検証+ログイン処理を行う場合はhandleRedirectObservableではなくhandleRedirectPromiseを使用したほうが良さそうです。

WebAPIと通信

通信を行っている部分は上記ソースコードrequestメソッドです。

/base-api/宛の通信のときに指定スコープのアクセストークンを取得し通信する処理が

HTTP_INTERCEPTORで指定したMsalInterceptorで実行されます。

f:id:TakasDev:20210716000405p:plain

WeatherForecastWebAPI宛の通信にAuthorizationヘッダが付与され、200で返却されることが確認できます。

おわりに

@azure/msal-angularのAzure AD B2Cでの実装の基本をざっと見てみました。

今回はさわりませんでしたがGuardもあり、v1のときに提供されていたAngularライブラリと同じような使用感で使うことができる感触です。

認証周りはmsal-browserをちょこちょこラップするのもなにかと手間なのでAngularを使用している場合は積極的に活用するのが良いと思います。

今回検証で使用したコードは下記となります。サンプルごった煮の一部となっていますm(__)m

github.com

AADSTS501051 <ClientId> is not assigned to a role for the application への対処

出るたびに対応を忘れて調べているのでメモ。

Microsoft.Identity.ClientConfidentialClientApplicationBuilderでシークレットを使用したアクセストークンの取得を行いたいときに発生する場合の対応です。

はじめに

2021年7月時点の情報です。

参照時期によっては記事内の画面キャプチャや設定内容が異なっている可能性があるのでご留意ください。

何が起きているのか

エラーは下記のような内容です。

MsalUiRequiredException: AADSTS501051: Application 'Client Id'(AAD App name) is not assigned to a role for the application 'Scopes'(AAD App name).

僕がよくやる構成の話です。

Entiprise Applications上で認証に使用しているAzure ADアプリケーションの設定で

ユーザー割当必須にしている場合があります。

f:id:TakasDev:20210711111246p:plain

雑にサインインの制御を行いたい場合(ゲストユーザーのみとかその逆とか)の設定で

シークレット認証の場合ユーザーの情報を伴わない状態なのでエラーになる。というわけです。

対処

App Roleの作成

認証を行いたいAzure ADアプリケーションのApp rolesでアプリケーションのRole作成を行います。

項目 設定内容
Display name 任意な名前
Allowed member types Applications
Value 任意な値
Description Roleの説明文

APIのアクセス許可の設定

次にシークレット認証用のAzure ADアプリケーションを用意します。

このアプリケーションはユーザー割当必須の設定は行いません。

API permissionsから認証を行いたいAzureADアプリケーションへのアクセス許可の設定を行います。

先程設定を行った認証を行いたいAzure ADアプリケーションを指定します。

Appliaction permissionsで先程作成したApp Roleを指定します。

f:id:TakasDev:20210711113440p:plain

シークレットによる認証が可能となる

シークレット認証用のAzure ADアプリケーションでシークレットを生成し確認を行います。

f:id:TakasDev:20210711114527p:plain

モザイクだらけでアレですが、audが適切な形となっていること

rolesは指定したアプリケーションロールが設定されていることが確認できます。

おわりに

ちょいちょい発生しては対処方法を忘れているAADSTS501051のメモでした。

Azure Web PubSub ServiceのイベントをトリガーにAzure Funcrtionsを実行する

はじめに

2021年7月時点の内容です。

また、AzureWebPubSubServiceはプレビュー版ということもあり

記載している画面やソースコードや設定内容は参照時点によっては異なっている可能性が高いので

参照される際はご留意ください。

今回は.NETラボのセッション資料中で時間の関係上セッションに落とせなかった部分のメモ書きをこちらに落としておきます。

基本などはイベントでお話しました。

まず基本的なところから抑えたいという方は、資料などをご覧いただければと思います。

PubSubイベントをトリガーにFunctionsを実行したい

Azure Web PubSub Serviceの設定

Azure Web PubSub Serviceで起きたイベントをFunctionsで処理したいと思います。

ドキュメントはこちらから確認できます。

Azure Web PubSub ServiceのSettingsから接続先のURLを指定することで

イベントをトリガーにFunctionsを実行できるようになります。

URLはFunctionsのExtensionを使う場合(エンドポイント + /runtime/webhooks/webpubsub)となります。

設定内容は大まかに下図のとおりです。

f:id:TakasDev:20210705220630p:plain

とりあえず動きを確かめたい程度なら、SystemEventsは何も設定しなくてOKです。

Azure Functionsの実装

NuGetでMicrosoft.Azure.WebJobs.Extensions.WebPubSubを取得します。

Functionsでは下記を実装することでメッセージの送信をトリガーにした処理が可能になります。

Hub=…で指定された箇所が上図のHub nameと対応する箇所です。

[FunctionName("<Function名>")]
public static async Task<MessageResponse> Broadcast(
    [WebPubSubTrigger(WebPubSubEventType.User, "message")] BinaryData message,
    [WebPubSub(Hub = "SampleHub")] IAsyncCollector<WebPubSubOperation> operations
)
{
    // なんやかんや
}

PubSubServiceで使用されるwsswss://<resource name>/client/hubs/<hub name>?access_token=...の形式で

↑のWSSで指定されるhub nameに対する通信のイベントがトリガーとなる。といった流れです。

System Eventsをトリガーにする

設定のSystem Eventsの各項目にチェックを入れた場合は下図のコードでトリガー実行可能となります。

WebPubSubTriggerの第3引数がそれぞれ、connect/connectedのいずれかになります。

チェックを入れた方(あるいは両方)を実装します。

[FunctionName("<Function名>")]
public static ServiceResponse Connect(
    [WebPubSubTrigger("SampleHub", WebPubSubEventType.System, "connect")] ConnectionContext connectionContext,
    ILogger log
)
{
  // なんやかんや
}

[FunctionName("<Function名>")]
public static async Task Connected(
    [WebPubSubTrigger(WebPubSubEventType.System, "connected")] ConnectionContext connectionContext,
    [WebPubSub] IAsyncCollector<WebPubSubOperation> operations
)
{
  // なんやかんや
}

気をつけなければならないこと

当然といえば当然なのですが、message含めSettingsでURLを指定した場合

トリガーで実行される関数がない場合エラーとなり、Connectやメッセージの送信が失敗するようになります。

さいごに

Azure Web PubSub Serviceで発生する各イベントをトリガーにFunctionsで実行することができ

Connect前後にフローを追加したり、Messageに情報を付加して返却したりなどが簡単にできるようになっていることが確認できました。

発表や今回の記事で使用したサンプルソースは下記となります。

github.com

メモ書き-msal.jsでSSOしたらiOSやChromeでエラーが発生したので対応した話

はじめに

下記のライブラリのバージョンで発生したエラーに対する対応となります。

参照される時期によっては対応方法や対応不要になったりと状況が変わっている可能性がありますのでご留意ください。

  • @azure/msal-browser: 2.13.1

(状況変わっているのが一番うれしい

iOSChromeでSSOしたときにエラーが発生する

すでにadal.jsを使用しているWebアプリケーションやほかシステムでログインが行われている前提であった場合msal.jsの ssoSilent はよく使用する機能かもしれません。

SSO SilentはセッションCookieを利用しているのですが

iOSChromeのシークレットブラウザを使用した場合サードパーティCookieを参照できないため ssoSilentで下記のようなエラーとなってしまいます。

InteractionRequiredAuthError: login_required: AADSTS50058: A silent sign-in request was sent but no user is signed in. The cookies used to represent the user's session were not sent in the request to Azure AD. This can happen if the user is using Internet Explorer or Edge, and the web app sending the silent sign-in request is in different IE security zone than the Azure AD endpoint (login.microsoftonline.com).

シングル サインオン (MSAL.js) - Microsoft identity platform | Microsoft Docs

この問題に対する対応

対応策1:ChromeiOSの設定を変更する

てっとりばやいのはChromeの下記設定をOffにする

f:id:TakasDev:20210404153527p:plain

iOSの下記設定をOffにすることです。

f:id:TakasDev:20210404154042p:plain

しかし、あえてセキュリティ上守られているものを解除する方に寄せていくのもよろしくありません。

ここの設定を変更するのは原因の切り分けを行うときくらいにとどめておいたほうが良いと思っています。

対応策2:エラー発生時にLoginRedirect/LoginPopupを行うように対応する

エラーの内容としては「ちゃんとログインしてくださいね」ということなので、別途ログインするだけで良さそうです。

UserAgentなどで特定ブラウザだけloginRedirectすることも考えましたが

対応しなければいけないブラウザが増える可能性もありますし

ssoSilentしたときでAADSTS50058が出たときに対応したい。と状況は限定的なので

ssoSilentでエラーをキャッチしそこでloginRedirectなりloiginPopupを行うように対応してみました。

Popupを使用する場合は↓な感じです。

  async login(): Promise<AuthenticationResult> {
    const res = await this.client.ssoSilent({ loginHint: loginhint }).catch((err: AuthError) => {
      if(err.errorMessage.includes('AADSTS50058')) {
        return this.client.loginPopup();
      }
      throw err;
    });
    this.account = res.account;
    return res;
  }

AADSTS50058発生しているときだけloginPopupするようにしています。

Redirectを使用する場合は少し手間です。

RedirectされたときにキャッチするhandleRedirectPromiseをエラー発生時にだけ使用したいからです。

  async login(): Promise<AuthenticationResult | null> {
    const errFlow = sessionStorage.getItem('errflowaction');
    if (errFlow) {
      const res = await this.client.handleRedirectPromise();
      if (res) {
        this.account = res.account;
      }
      sessionStorage.removeItem('errflowaction');
      return res;
    }
    const res = await this.client.ssoSilent({ loginHint: loginhint }).catch((err: AuthError) => {
      if(err.errorMessage.includes('AADSTS50058')) {
        sessionStorage.setItem('errflowaction', 'dummy');
        this.client.loginRedirect();
        return null;
      }
      throw err;
    });
    if (res) {
      this.account = res.account;
      return res;
    }
    return null;
  }

少し不格好ですが、Redirect前にsessionStorageにFlow状態を記録しておき

それによって動作を変える方法で逃げることにしました。

さいごに

実験に使ったコードは以下に格納しています。

github.com

メモ書き - AzureDevOpsで別プロジェクトのCIをトリガーにパイプラインを実行する

はじめに

このメモは2021年1月17日時点のAzure DevOpsを使用したメモになっています。

参照されるタイミングによっては、記事内で称しているキャプチャ・設定内容が変更されている可能性がありますのでご注意ください。

やりたいこと

Docsみててこんがらがったので自分用メモ・・・

Azure DevOpsで複数のプロジェクトを構築。

各プロジェクトの構成は下な感じで。

  • 統合プロジェクト
    • サブプロジェクト1
    • サブプロジェクト2
    • ...

サブプロジェクトでライブラリ作成→統合プロジェクトでサブプロジェクトのライブラリを使用してクライアントアプリの開発。といった構成。

サブプロジェクトxのCIが成功→統合プロジェクトのCIを実行 な感じで動作させたい。

統合プロジェクトのパイプラインの構築

サブプロジェクト側のパイプラインは今まで通りの構成で問題なし。

統合プロジェクト側のyamlの頭に👇の構成。

trigger: none

resources:
  pipelines:
  - pipeline: subProjectPipeline  # パイプラインの名称。このCIからアクセスするときの識別子
    source: 'integration-pipeline-two (1)'  # トリガーするパイプライン
    project: integration-pipeline-two  # 別プロジェクトにあるCIの場合はこれを指定
    trigger:
      branches:
      - master

サブプロジェクトでPublisしたものをDownloadしたいときは👇でDLできる。

- download: subProjectPipeline  # pipelineで指定した識別子
  artifact: drop  # Artifact名

補足

Releasesを使用する場合、👇の2つをOffにしないと権限なしでサブプロジェクトでPublishされたリソースにアクセスできないとかあったけど、Pipelinesではそういうものはなさげ。

(まぁReleases Pipeline特有の設定っぽいからそらそうなんだけど、PipelineのほうはこういうのOffにする必要ないんだ。楽だなぁ。と。)

f:id:TakasDev:20210117135717p:plain

参考にしたサイト等

実験で使用したAzure DevOpsプロジェクト