構成としては認証をCognito UserPoolで行い、AppSyncからLambdaを呼び出してJSONを返す構成とする。
UserPool
UserPoolを用意する。this.node.tryGetContext
でコンテキストが渡せるのでここに環境名、例えばprod
などを与えてSuffixとするようにした。
import * as path from "path"; import * as cdk from "@aws-cdk/core"; import * as cognito from "@aws-cdk/aws-cognito"; export class MyStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const env = this.node.tryGetContext("env"); const userPool = new cognito.UserPool(this, "MyUserPool" + env, { userPoolName: "MyUserPool-" + env }); } }
cognito.UserPool
はHigh-level constructs
というものっぽくて細かい設定が隠蔽されてて触れない気がする。
正しいかわからないけど
const userPoolCfn = userPool.node.defaultChild as cognito.CfnUserPool;
とすると細かい設定ができるように見える
Lambda
Lambdaとロールを作る。ひとまずCloudWatchLogsのみアクセス可能とする。必要に応じてDynamoやRDS、S3への権限をつける。
Rustの場合はruntimeはlambda.Runtime.PROVIDED
とする。PROVIDEDの場合handlerの設定は無視されるっぽい。
lambda.Code.fromAsset
にバイナリのパスを設定しておくとcdk deploy
でバイナリをS3にあげてセットしてくれる。
import * as path from "path"; import * as cdk from "@aws-cdk/core"; import * as appsync from "@aws-cdk/aws-appsync"; import * as cognito from "@aws-cdk/aws-cognito"; import * as lambda from "@aws-cdk/aws-lambda"; import * as iam from "@aws-cdk/aws-iam"; import { definition } from "./schema"; export class MyStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { ...省略... const lambdaRole = new iam.Role(this, "MyLambdaRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), path: "/service-role/", inlinePolicies: { CloudWatchWritePolicy: new iam.PolicyDocument({ statements: [ new iam.PolicyStatement({ actions: [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], resources: ["*"] }) ] }) } }); const MyLambda = new lambda.Function(this, "MyLambda", { functionName: `my-function-${env}`, runtime: lambda.Runtime.PROVIDED, handler: "index.handler", code: lambda.Code.fromAsset( path.join( __dirname, "../../target/x86_64-unknown-linux-musl/release" ) ), role: lambdaRole }); } }
AppSync
AppSync作成時にauthenticationType: "AMAZON_COGNITO_USER_POOLS"
にしてUserPoolIdを渡す。
new appsync.CfnGraphQLSchema
にSchemaを渡す。
schemaは別ファイルに以下のように書いといてこれを読ます。
export const definition = ` type Hello { world: String! } type Query { getHello: Hello! } type Schema { query: Query } `;
あとはDataSourceとResolverを追加する。 DataSource用のRoleにはLambda呼び出し権限を忘れないように。
import * as path from "path"; import * as cdk from "@aws-cdk/core"; import * as appsync from "@aws-cdk/aws-appsync"; import * as cognito from "@aws-cdk/aws-cognito"; import * as lambda from "@aws-cdk/aws-lambda"; import * as iam from "@aws-cdk/aws-iam"; import { definition } from "./schema"; export class MyStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { ...省略... const gql = new appsync.CfnGraphQLApi(this, "AppSyncAPI", { name: `MyGQL-${env}`, authenticationType: "AMAZON_COGNITO_USER_POOLS", userPoolConfig: { awsRegion: "ap-northeast-1", defaultAction: "ALLOW", userPoolId: userPool.userPoolId } }); const schema = new appsync.CfnGraphQLSchema(this, "GqlSchema", { apiId: gql.attrApiId, definition }); const dataSourceIamRole = new iam.Role(this, "dataSourceIamRole", { assumedBy: new iam.ServicePrincipal("appsync.amazonaws.com"), inlinePolicies: { InvokeLambdaFunction: new iam.PolicyDocument({ statements: [ new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["lambda:invokeFunction"], resources: ["*"] }) ] }) } }); const dataSource = new appsync.CfnDataSource(this, "DataSource", { apiId: gql.attrApiId, name: "LambdaDataSource", serviceRoleArn: dataSourceIamRole.roleArn, type: "AWS_LAMBDA", lambdaConfig: { lambdaFunctionArn: MyLambda.functionArn } }); const addItemResolver = new appsync.CfnResolver(this, "HelloResolver", { apiId: gql.attrApiId, typeName: "Query", fieldName: "getHello", dataSourceName: dataSource.name, requestMappingTemplate: `{ "version": "2018-05-29", "operation": "Invoke", "payload": { "now": $util.toJson($util.time.nowISO8601()), } }`, responseMappingTemplate: `$util.toJson($ctx.result)` }); addItemResolver.addDependsOn(schema); } }
Rust
main.rsを以下のように用意。 基本的にはここに書いてある通り。
実行バイナリの名前をbootstrap
にするのを忘れないこと。
#[macro_use] extern crate lambda_runtime as lambda; #[macro_use] extern crate serde_derive; #[macro_use] extern crate log; use lambda::error::HandlerError; use std::error::Error; #[derive(Deserialize, Clone)] struct CustomEvent {} #[derive(Serialize, Clone)] struct CustomOutput { world: String, } fn main() -> Result<(), Box<dyn Error>> { simple_logger::init_with_level(log::Level::Info)?; lambda!(my_handler); Ok(()) } fn my_handler(e: CustomEvent, c: lambda::Context) -> Result<CustomOutput, HandlerError> { Ok(CustomOutput { world: format!("Dekita!"), }) }
あとはビルド。今回はmuslで。
cargo build --release --target x86_64-unknown-linux-musl
あとはCDKでデプロイすればよい。
cdk deploy -c env="dev"
ローカルでLambdaを実行
ローカルで動かすにはSAM
を使えばいいっぽい。
以下のようなtemplate.yaml
を用意し
AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Resources: TestRustFunc: Type: "AWS::Serverless::Function" Properties: Handler: sam_local_test Runtime: provided CodeUri: ./rust.zip
zipしてから
zip -j rust.zip ./target/x86_64-unknown-linux-musl/release/bootstrap
以下で一応動作は確認できた
sam local invoke "TestRustFunc" -e event.json
2020-01-29 19:34:57 Mounting /tmp/tmp6efof_c2 as /var/task:ro,delegated inside runtime container START RequestId: 136f6ca2-6a4f-121d-5ece-30d4999340b1 Version: $LATEST 2020-01-29 10:34:58,203 INFO [lambda_runtime::runtime] Received new event with AWS request id: 136f6ca2-6a4f-121d-5ece-30d4999340b1 2020-01-29 10:34:58,205 INFO [lambda_runtime::runtime] Response for 136f6ca2-6a4f-121d-5ece-30d4999340b1 accepted by Runtime API END RequestId: 136f6ca2-6a4f-121d-5ece-30d4999340b1 REPORT RequestId: 136f6ca2-6a4f-121d-5ece-30d4999340b1 Init Duration: 277.65 ms Duration: 3.08 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 9 MB {"world":"Dekita!"}
今やってるお仕事、これに置き換えたい...