💨
github actions経由でAWS Lambda,APIGatewayにdeployしてAPIを作成してみた
やりたかったこと
github actionsにpushをすることでAWSにdelopyされ、APIを作成したかった
やったこと
前提条件
ファイル構造(ddlは無視してもらって大丈夫です)
Lambda-stackの作成
vpcは使用していません
import { Duration, Environment, Stack, StackProps } from "aws-cdk-lib";
import { DeploySetting } from "../deploy-list";
import { Construct } from "constructs";
import { SecurityGroup, SubnetType, Vpc } from "aws-cdk-lib/aws-ec2";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import { RetentionDays } from "aws-cdk-lib/aws-logs";
import * as IAM from "aws-cdk-lib/aws-iam";
export interface LambdaProps extends StackProps {
env?: Environment;
name: string;
stackNameSuffix?: string;
secretJson?: any;
vpc?: string;
securityGroup: string;
params?: DeploySetting;
}
export class LambdaStack extends Stack {
public readonly lambdas: any = {};
constructor(scope: Construct, id: string, props: LambdaProps) {
super(scope, id, props);
}
deploy(props: LambdaProps) {
const parsedJson = props.secretJson;
const lambda = new NodejsFunction(
this,
`bff-${props.name}-${props.stackNameSuffix}`,
{
functionName: `bff-${props.name}-${props.stackNameSuffix}`,
entry: `./src/action/${props.name}.ts`,
handler: "handler",
memorySize: 256,
timeout: Duration.seconds(180),
environment: {
TZ: "Asia/Tokyo",
database: parsedJson.host,
user: parsedJson.username,
password: parsedJson.password,
host: parsedJson.host,
},
securityGroups: [
SecurityGroup.fromSecurityGroupId(
this,
`${props.name}_SG`,
props.securityGroup
),
],
logRetention: RetentionDays.ONE_WEEK,
}
);
if (props.name && props.params?.lambdaRole) {
const lambdaPolicy = new IAM.PolicyStatement({
...props.params.lambdaRole,
});
lambda.role?.attachInlinePolicy(
new IAM.Policy(this, `LambdaRole-bff-${props.name}`, {
statements: [lambdaPolicy],
})
);
}
this.lambdas[props.name] = lambda;
}
}
APIGateway-stackの作成
import { CfnOutput, Stack, StackProps } from "aws-cdk-lib";
import {
CognitoUserPoolsAuthorizer,
Cors,
LambdaIntegration,
RestApi,
} from "aws-cdk-lib/aws-apigateway";
import { ServicePrincipal } from "aws-cdk-lib/aws-iam";
import { UserPool } from "aws-cdk-lib/aws-cognito";
import { Construct } from "constructs";
import { DeploySetting } from "../deploy-list";
export interface ApiProps extends StackProps {
stage: string;
stackNameSuffix?: string;
lambdas: any;
isUseCognito?: boolean;
setting: DeploySetting;
}
interface Obj {
[props: string]: any;
}
export class ApiStack extends Stack {
private restApi: any = {};
private cognitoAuthorizer: any;
constructor(scope: Construct, id: string, props: ApiProps) {
super(scope, id, props);
this.restApi = new RestApi(this, "production-pool-bff", {
restApiName: `bff-${props.stackNameSuffix}`,
description: "priduction_pool_bff by CDK",
deployOptions: {
stageName: props.stage,
},
defaultCorsPreflightOptions: {
allowOrigins: Cors.ALL_ORIGINS,
allowMethods: Cors.ALL_METHODS,
allowHeaders: Cors.DEFAULT_HEADERS,
statusCode: 200,
},
});
new CfnOutput(this, "OutputApiUrl", { value: this.restApi.url! });
//Cognitoへのアクセスに関する処理
if (props.isUseCognito) {
const userPool = UserPool.fromUserPoolArn(
this,
"cognitoのID",
"自分の環境でのarn"
);
this.cognitoAuthorizer = new CognitoUserPoolsAuthorizer(
this,
"cognitoAuthorizer",
{
authorizerName: "CognitoAuthorizer",
cognitoUserPools: [userPool],
}
);
}
}
deploy(props: ApiProps) {
const accountId = Stack.of(this).account;
const region = Stack.of(this).region;
if (!props.setting.urls) return;
//リソースの作成
let resorce = this.restApi.root.addResource(props.setting.urls[0]);
props.setting.urls.shift();
props.setting.urls.forEach((resorceItem) => {
resorce = resorce.addResource(resorceItem);
});
const stackDevideFunction = props.lambdas[props.setting.name];
const cognitoSwitch: Obj = {
requestParameter: props.setting.required,
validateRequestParameters: props.setting.required ? true : false,
};
if (props.isUseCognito) cognitoSwitch.authorizer = this.cognitoAuthorizer;
resorce.addMethod(
props.setting.method,
new LambdaIntegration(stackDevideFunction),
cognitoSwitch
);
stackDevideFunction.addPermission("myFunctionPermission", {
principal: new ServicePrincipal("apigateway.amazonaws.com"),
action: "lambda:InvokeFunction",
sourceArn: `arn:aws:execute-api:${region}:${accountId}:${this.restApi.restApiName}/*/*/*`,
});
}
}
deplory-listの作成
ここはApiGatewayの入り口です
export const deploylist: DeploySetting[] = [
{
name: "bff_test",
urls: ["bff_test"],
auth: false,
method: "GET",
required: {},
},
{
name: "userSignUp",
urls: ["userSignUp"],
auth: false,
method: "POST",
lambdaRole: {
actions: [
"cognito-idp:AdminCreateUser",
"cognito-idp:AdminSetUserPassword",
"ses:SendEmail"
],
resources: ["*"],
},
required: {},
},
];
export interface Keys {
[key: string]: boolean;
}
export interface LambdaRoleType {
[key: string]: Array<string>;
}
export interface DeploySetting {
name: string;
urls: Array<string>;
auth: boolean;
method: string;
required?: Keys;
lambdaRole?: LambdaRoleType;
}
deployments-stackの作成
import * as cdk from "aws-cdk-lib";
import { execSync } from "child_process";
import { deploylist } from "./deploy-list";
import { ApiStack } from "./serviceStack/apigateway-stack";
import { LambdaStack } from "./serviceStack/lambda-stack";
const { STAGE = "local" } = process.env;
const { STACK_NAME_SUFFIX = "local" } = process.env;
const { SECURITY_GROUP_ID = "" } = process.env;
const createApp = async (lambdaJson: any): Promise<cdk.App> => {
const app = new cdk.App();
cdk.Tags.of(app).add("Name", "PRODUCTION-POOL-BACKEND-CDK");
cdk.Tags.of(app).add("Price", "production-pool-backend");
cdk.Tags.of(app).add("CmVillingGroup", "production-pool-backend");
cdk.Tags.of(app).add("Maiking", "miyashita-hiroki");
cdk.Tags.of(app).add("Sys-group", "production-pool-backend");
const lambda = new LambdaStack(app, `lambda-bff-${STACK_NAME_SUFFIX}`, {
env:
STAGE === "local"
? undefined
: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
name: deploylist[0].name,
securityGroup: SECURITY_GROUP_ID,
});
const apiGateway = new ApiStack(app, `api-bff-${STACK_NAME_SUFFIX}`, {
stage: STAGE,
stackNameSuffix: STACK_NAME_SUFFIX,
lambdas: lambda.lambdas,
setting: {
name: "",
urls: [],
auth: false,
method: "",
required: {},
},
});
apiGateway.addDependency(lambda);
deploylist.forEach(async (deployListItem) => {
await lambda.deploy({
stackNameSuffix: STACK_NAME_SUFFIX,
name: deployListItem.name,
securityGroup: SECURITY_GROUP_ID,
secretJson: lambdaJson,
params: deployListItem,
});
await apiGateway.deploy({
stage: STAGE,
lambdas: lambda.lambdas,
isUseCognito: STAGE != "local" && deployListItem.auth,
setting: deployListItem,
});
});
return app;
};
let lambdaJson = {};
const get_command = `aws secretsmanager get-secret-value --secret-id pool/postgres`;
const result = execSync(get_command);
const secretsManagerJson = JSON.parse(result.toString());
lambdaJson = JSON.parse(secretsManagerJson.SecretString);
createApp(lambdaJson);
ymlの作成
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI
on:
push:
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- run: npm install -g aws-cdk
- run: npm install
- run: npm run build --if-present
- run: cdk bootstrap
- run: cdk deploy --all
# - run: npm test
まとめ
上記のことをやることでgitにpushした際にAWS上にdeployされる仕組みができあがります。
ただし、branchによってapiの名前などを変更したりはしていないのでそれぞれ自分の環境、仕様で
カスタムをしていってもらえればいいと思います
Discussion