Amplify Gen2のリソースをオーバーライドしてみた

2024.04.05

プレビュー版のAmplify Gen2の、リソースのオーバーライドを試してみました。

Amplify Gen2のオーバーライドの概要

Amplifyが生成するcfnテンプレートを書き換えます。存在しない機能を追加したい時に使用します。

既存のAmplify(Gen1)はツールファーストであり、豊富なメニューから必要な機能を選択することで、望んでいる機能を簡単に追加することができました。 とはいえ、全ての機能に対応は難しいので、amplify overrideというコマンドを用意し、必要な場合にAmplifyが生成するリソースを書き換えるためのescape hatcheを用意していました。

Amprify Gen2は、設計方針がコードファーストになり、豊富なメニューは無いと予想されるので、Gen1よりリソースの書き換えは増えそうです。 backend.tsというファイルを起点に、作成されたAmplifyリソースの呼び出し、書き換えを行います。書き換えにはCDKを使用します。

Constructへのアクセス

Amplify Gen2はL3 Construct(最高レベルの抽象化)として定義されており、escape hatcheより、WrapされているL2、L1のConstructにアクセスできます。 具体的には、defineBackendという関数でAmplifyリソースを定義し、それぞれのサービスからパスを辿ります。 例:Data(Appsync)の場合

  • L2 Construct:backend.data.resources.graphqlApi
  • L1 Construct:backend.data.resources.cfnResources.cfnGraphqlApi

このConstructへのパスさえ覚えておけば、迷わなさそうです。 ただ、独自の設定項目があったり、一部の項目が隠されていたり、Cfnでは書き換えられるが、Amplifyでは書き換えられない項目などもあり、CDKの知識+αが必要だ、という印象です。

オーバーライドしてみた

Docsにはリソース書き換えのサンプルがあり、非常に参考になります。

リソースのオーバーライドのDocs

https://docs.amplify.aws/gen2/build-a-backend/auth/override-cognito/ https://docs.amplify.aws/gen2/build-a-backend/data/override-resources/ https://docs.amplify.aws/gen2/build-a-backend/add-aws-services/overriding-resources/

他、メニューの各所にサンプルが書かれています。 書かれていないものを含めて、自分で試してみました。

ユーザーグループの追加(2024/4/23 修正あり)

backend.tsで、custom resourceを使って定義する必要はありません。

defineAuthのパラメータとして定義が可能(groupの作成に加え、groupごとののIAM Roleも作成してくれる)なため、auth/resource.tsで設定を行って下さい。、

export const auth = defineAuth({
  loginWith: {
   ....
  },
  groups: ["worker", "admin"],  // groupを定義
});

※記事初稿時、Amplify Dataではユーザーグループを定義する方法はDocsには無さそうだ、としていましたが、defineAuthで設定ができるため、差し替えました。

パスワードポリシー、ユーザ作成禁止、ゲストアクセス無効化

一部Docs内にサンプルがあります。

amplify/backend.ts

 // Change Password policie
 const { cfnUserPool, cfnIdentityPool } = backend.auth.resources.cfnResources;
 cfnUserPool.addPropertyOverride("Policies", {
   PasswordPolicy: {
     MinimumLength: 6,
     RequireLowercase: false,
     RequireNumbers: true,
     RequireSymbols: false,
     RequireUppercase: false,
     TemporaryPasswordValidityDays: 20,
   },
 });
 
 // Prevent user create
 cfnUserPool.addPropertyOverride(
   "AdminCreateUserConfig.AllowAdminCreateUserOnly",
   true,
 );
 
 // Do not allow guest access
 cfnIdentityPool.addPropertyOverride("AllowUnauthenticatedIdentities", false);

PasswordPolicyのオーバーライドは、Cognitoには反映されるものの、AmplifyConfiguration.jsonに反映されないので、AWS側とクライアントでPasswordPolicyの齟齬が生じてしまう現象を確認しています。この問題はsandboxとデプロイした環境に影響します。報告済なので、すぐ修正されそうですが・・

暫定処置として、生成されたamplifyConfiguration.jsonを書き換えるスクリプトを作成し、amplify.ymlのbuildセクションへ組込んでいます。

amplify-configuration-override.js

 const fs = require("fs");
 const filePath = "./amplifyconfiguration.json";
 try {
   const data = fs.readFileSync(filePath, "utf8");
   const amplifyconfiguration = JSON.parse(data);
 
   amplifyconfiguration.aws_cognito_password_protection_settings = {
     passwordPolicyMinLength: 6,
     passwordPolicyCharacters: ["REQUIRES_NUMBERS"],
   };
 
   try {
     fs.writeFileSync(filePath, JSON.stringify(amplifyconfiguration), "utf8");
   } catch (err) {
     console.error(err);
   }
 } catch (err) {
   console.error(err);
 }

amplify.yml

         // 中略
         build:
             commands:
                 # force aws_cognito_password_protection_settings override
                 - 'node amplify-configuration-override.js'
                 - 'npm run build'

Dynamo Stream

最近まで、正規の手順でDyamoDBのTableNameへのアクセスができなかった為、Amplify内でIaCを完結させることが難しい状態でしたが、改善され、オーバーライドによりDynamo Streamを定義できるようになりました。

ポイントとして、DynamoDBはAmplifyのリソースではなく、AppSyncのリソースの(DataSource)として定義されているので、アクセスがやや遠回りになるところでしょうか。

amplify/backend.ts

export const backend = defineBackend({
   auth, //スキーマ定義で、Hogeというモデルが存在している、とします
   data,
   dynamoStreamFunction, //事前にFunctionを作成しておく
 });
 
 // activate dynamoDB stream
 // hoge
 backend.data.resources.cfnResources.amplifyDynamoDbTables.Hoge.streamSpecification =
   {
     streamViewType: StreamViewType.NEW_IMAGE,
   };
 
 // add stream trigger 
 backend.dynamoStreamFunction.resources.lambda.addEventSourceMapping(
   "EventSourceMappingDynamo",
   {
     batchSize: 1,
     eventSourceArn: backend.data.resources.tables.Hoge.tableStreamArn,
     startingPosition: StartingPosition.LATEST,
   },
 );
 // grant stream read
 backend.data.resources.tables.Hoge.grantStreamRead(
   backend.dynamoStreamFunction.resources.lambda,
 );

その他、DyanmoDBのbillingModeや、provisionedThroughput、ttl、Cognitoのカスタム属性など、公式にサンプルがありますので、気になる機能は一度公式をチェックされることをお勧めします。

注意点

Functionを作成した時、AppSyncのDataSourceであるDynamoDBのTableNameやArnを環境変数として渡すケースが存在すると思うのですが 当然ながら、Cfnの作成順序としては、AppSync→Functionとなります。

ここでAppSync内でLambdaによる関数リゾルバ(Function)を作成した場合、AppSyncを作成するためにdefineFunctionを実行する為、Cfnの作成順序がFunction→AppSync→Functionとなってしまい、循環参照が発生します。

https://docs.amplify.aws/gen2/build-a-backend/data/custom-business-logic/#step-2---configure-custom-business-logic-handler-code

amplify/data/resource.ts

 import {
   type ClientSchema,
   a,
   defineData,
   defineFunction // 1.Import "defineFunction" to create new functions
 } from '@aws-amplify/backend';
 
 // 2. define a function
 const echoHandler = defineFunction({
   entry: './echo-handler/handler.ts'
 })
 
 const schema = a.schema({
   EchoResponse: a.customType({
     content: a.string(),
     executionDuration: a.float()
   }),

根本的な原因は、defineFunctionを複数実行して、複数の関数を作成したとしても、出力されるCfnテンプレートは1つになってしまうので、関数ごとのStackの作成順序が同じになる為と思われます。

関数毎にCfnテンプレートを分けて出力すると、この問題も解決するかと考えているのですが、defineFunctionにそのようなオプションは見つかりませんでした。

特殊なパターンかもしれませんが、注意する必要があると感じました。

まとめ

開発中のリソースのオーバーライドですが、CDKの理解があれば、既存のAmplifyよりも使いやすい印象があります。