はじめに
GitをベースにしたCI/CDパイプラインを作る際、共通モジュールをどのように管理したらよいかという課題に対してアプローチを考える。
- CodeArtifactのようなローカルリポジトリを作ってそこに共通モジュールをPushしておく
- Gitのサブモジュール機能を使う
CodeArtifactは以前記事にまとめてみたので、そちらを参照してもらうとして、今回はGitのサブモジュール機能について実際に使ってみて掘り下げをしてみる。
事前準備
今回、メインモジュールとサブモジュールのリポジトリをあらかじめCodeCommitに作成しておく。
以下のようなイメージだ。
サブモジュールを設定する
まず、CodeCommitのマネージメントコンソール画面では、サブモジュールを設定するアクションは無い。
このため、一旦EC2等にクローンしてきてからgit submoduleコマンドを使うか、Egit側から設定するしかない。
今回はEgit側から設定してみる。
まずは、上記のリポジトリのうち、メインモジュール側(-test
の方)をチェックアウトしてくる。
gitのパースペクティブを表示して、メインモジュールを右クリックする。
と、メニューに「サブモジュールの追加」が表示されるので、これをクリックすると、ダイアログが開く。
このダイアログは、メインモジュールのプロジェクト内でのパスを記載する。
「次へ」をクリックすると、以下のダイアログが開く。
ここで、最初に作ったサブモジュール側(-submodule
の方)のリポジトリURIを入れる。ホストとリポジトリー・パスは自動で埋められる。
完了ボタンを押せば、パースペクティブ上のディレクトリ構成が以下のようになり、サブモジュールの設定が完了する。
サブモジュールを使ったコードを書く
全体構成
今回の構成は以下の通り。
gitsubmodule-test
├── mainmodule
│ ├── pom.xml
│ └── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── gitsubmoduletest
│ │ │ ├── AWSLambdaHandler.java
│ │ │ ├── Hello.java
│ │ │ └── SpringCloudFunctionExampleApplication.java
│ │ └── resources
│ │ └── application.properties
│ └── test
│ └── java
│ └── com
│ └── springcloudfunctiontest
│ └── SpringCloudFunctionExampleApplicationTests.java
├── pom.xml
└── submodule
├── pom.xml
└── src
├── main
│ └── java
│ └── com
│ └── gitsubmoduletest
│ └── common
│ └── user
│ └── User.java
└── test
└── java
└── com
└── gitsubmoduletest
└── common
└── user
└── UserTests.java
依存関係の定義
とりあえず、POMはそれぞれのモジュールと全体で必要なので書いていく。
トップディレクトリのpom.xmlのポイントは
- 下位で必要なモジュールを定義する
-
<modules>
で配下のモジュールを定義する
あたりだ。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitsubmoduletest</groupId>
<artifactId>git-submodule-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>git-submodule-test</name>
<description>Git Submodule Test</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<modules>
<module>mainmodule</module>
<module>submodule</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
次に、サブモジュールのpom.xml。これ自体は特別なことはない。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>user</name>
<description>Submodule for Git Submodule Test</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>com.gitsubmoduletest</groupId>
<artifactId>git-submodule-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.16.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
最後にメインモジュール。
これも特別なことはあまりない、普通の小さいSpringCloudFunctionだが、サブモジュールの依存関係の部分はしっかり書いておこう。忘れると動作しない。
また、サブモジュールを使用しない場合同様、maven-shade-plugin
はここに書いて、FatJarを作ろう。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-function-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-cloud-function-example</name>
<description>Spring Cloud Function for Git Submodule Test</description>
<parent>
<groupId>com.gitsubmoduletest</groupId>
<artifactId>git-submodule-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>com.gitsubmoduletest</groupId>
<artifactId>user</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>aws</shadedClassifierName>
</configuration>
</plugin>
</plugins>
</build>
</project>
コード
メインモジュール
Spring Cloud Functionについては(別の記事)[https://qiita.com/neruneruo/items/710a981c0ad3877e1988]で書いているので細かいことはそちらを参照。
上記記事と同じアプリケーションで、idに紐付けてNameを返す部分をモジュールに切り出している。
package com.gitsubmoduletest;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler;
public class AWSLambdaHandler extends SpringBootRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
}
package com.gitsubmoduletest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringCloudFunctionExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFunctionExampleApplication.class, args);
}
}
package com.gitsubmoduletest;
import java.util.Map;
import java.util.function.Function;
import org.springframework.stereotype.Component;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.gitsubmoduletest.common.user.User;
@Component
public class Hello implements Function<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
@Override
public APIGatewayProxyResponseEvent apply(APIGatewayProxyRequestEvent input) {
Map<String, String> queryStringParameter = input.getQueryStringParameters();
String id = queryStringParameter.get("id");
User user = new User(id);
String name = user.getName();
APIGatewayProxyResponseEvent responseEvent = new APIGatewayProxyResponseEvent();
responseEvent.setStatusCode(200);
responseEvent.setBody("{\"name\":" + name + "}");
return responseEvent;
}
}
サブモジュール
切り出したモジュールを以下のように定義する。
package com.gitsubmoduletest.common.user;
public class User {
private String id = null;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
if( this.id.equals("11111") ) {
return "\"Taro\"";
} else if( this.id.equals("22222") ) {
return "\"Jiro\"";
} else if( this.id.equals("33333") ) {
return "\"Saburo\"";
} else {
return "\"Nanashi-no-gonbe\"";
}
}
public User(String id) {
this.id = id;
}
}
テストコード
テストコードは今回の本筋ではないので割愛。
ちゃんとトップディレクトリでmvn test
すれば、それぞれのテストコードに対するテストが自動実行される。
リポジトリへのPush
テストによる正常性確認が終わったら、リポジトリに対してPushする。
ちゃんと
- サブモジュールに対するPushはサブモジュールに反映
- メインモジュールに対するPushはメインモジュールに反映
されるようになっている。
なお、メインモジュールに対しては、サブモジュールを作ったときに作成される.gitmodulesとのファイルと、submoduleのディレクトリをPushしておこう。これは、
[submodule "submodule"]
path = submodule
url = https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/xxxxxxxx-gitsubmodule-submodule
といった感じでサブモジュールが管理されている。
これをメインモジュール側に入れておくことで、git clone --recursive
したときに、サブモジュールごと引っ張ってきてくれる。Eclipseでは以下のチェックボックスを入れるのが、--recursive
と等価である。
逆に言えば、.gitmodulesとディレクトリをPushしておかないと、--recursiveを付与してもサブモジュールはチェックアウトされないので気を付けよう。
正しくPushされていると、CodeCommitのマネージメントコンソール上では以下のように表示されるようになる。
一旦リポジトリをローカルから削除して、--recursive
なオプションでcloneすると以下のようになり、メインもサブも正しくcloneされる。
パイプラインに組み込む
概要
さて、ここまでやったらCI/CDパイプラインの動きを確認したくなるというもの。
早速、SAMテンプレートでデプロイするパイプラインを作ってみよう。
と思いきや、なんと公式のトラブルシューティングによると
問題: CodePipeline は git サブモジュールをサポートしていません。CodePipeline は、サブモジュールをサポートしていない GitHub のアーカイブリンク API を使用しています。
解決方法: 別スクリプトの一部として直接 GitHub リポジトリをクローンすることを検討してください。たとえば、Jenkins スクリプトにクローンアクションを含めることができます。
らしい。ええー……
ちなみに、この「直接GitHubリポジトリをクローンする」というのが曲者で、超大変だった。
まずは、通常はCodePipelineであればCodeCommitからのソース取り出しをCodePipelineがやってくれるので、CodeBuildのサービスロールには不要な以下の権限を付与する必要がある(直接アクセスするので)。
"codecommit:BatchGet*",
"codecommit:BatchDescribe*",
"codecommit:Describe*",
"codecommit:Get*",
"codecommit:List*",
"codecommit:GitPull",
また、clone前に、gitに認証情報を渡す必要があるため、以下のようにする。
さらに、CodePipelineで取得するソースはgit cloneするわけではなく.gitがないので、git submodule をすることができない。なので、git clone でディレクトリ名を変更して取得する必要がある。
pre_build:
commands:
- git config --global credential.helper "!aws codecommit credential-helper $@"
- git config --global credential.UseHttpPath true
- git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/xxxxxxxx-gitsubmodule-submodule submodule
さて、これが通ればあとはSAMのデプロイだけだ。
しかし、その前にTerraformでパイプラインを定義しておこう。
Terraformの定義
Terraformの全体構成としては以下の通り。
実は、これサブモジュールを駆使すれば良い感じにモジュール化できそうな気がしてきたな。
Terraform
├── 01_main.tf
├── 02_iam.tf
├── 03_s3.tf
├── 04_cloudwatchlogs.tf
├── 05_codepipeline.tf
└── 06_cloudformation_parameter.json
######################################################################
# 変数定義 #
######################################################################
variable "prefix" {
default = "GitSubModule-Test"
}
locals{
##############################################################################
# IAM #
##############################################################################
codebuild_role_name = "${var.prefix}-CodeBuildRole"
codepipeline_role_name = "${var.prefix}-CodePipelineRole"
cloudformation_role_name = "${var.prefix}-CloudFormationRole"
##############################################################################
# S3 Bucket #
##############################################################################
bucket_name = lower("${var.prefix}-artifact-bucket")
##############################################################################
# CloudWatch Logs #
##############################################################################
codebuild_logstream_name = "${var.prefix}-CodeBuildLogStream"
##############################################################################
# CodeCommit #
##############################################################################
repository_name = lower("${var.prefix}")
##############################################################################
# CodeBuild #
##############################################################################
buildspec_file_name = "buildspec.yml"
build_project_name = "${var.prefix}-BuildProject"
##############################################################################
# CodePipeline #
##############################################################################
pipeline_name = "${var.prefix}-Pipeline"
##############################################################################
# SAM #
##############################################################################
stack_name = "${var.prefix}-SAMStack"
cf_param_lambda_function_name = "${var.prefix}-Function"
cf_param_lambda_execution_role_name = "${var.prefix}-LambdaExecutionRole"
}
######################################################################
# for CodeBuild #
######################################################################
resource "aws_iam_role" "codebuild" {
name = "${local.codebuild_role_name}"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "codebuild.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy" "codebuild" {
name = "codebuild_policy"
role = "${aws_iam_role.codebuild.id}"
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"codecommit:BatchGet*",
"codecommit:BatchDescribe*",
"codecommit:Describe*",
"codecommit:Get*",
"codecommit:List*",
"codecommit:GitPull",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"${aws_s3_bucket.artifact.arn}",
"${aws_s3_bucket.artifact.arn}/*"
]
}
]
}
POLICY
}
######################################################################
# for CodePipeline #
######################################################################
resource "aws_iam_role" "codepipeline" {
name = "${local.codepipeline_role_name}"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "codepipeline.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy" "codepipeline" {
name = "codepipeline_policy"
role = "${aws_iam_role.codepipeline.id}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect":"Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketVersioning",
"s3:PutObject"
],
"Resource": [
"${aws_s3_bucket.artifact.arn}",
"${aws_s3_bucket.artifact.arn}/*"
]
},
{
"Effect": "Allow",
"Action": [
"cloudformation:DescribeStacks",
"cloudformation:CreateStack",
"cloudformation:UpdateStack",
"codecommit:GetRepository",
"codecommit:GetBranch",
"codecommit:GetCommit",
"codecommit:ListBranches",
"codecommit:GetUploadArchiveStatus",
"codecommit:UploadArchive",
"codecommit:CancelUploadArchive",
"codebuild:StartBuild",
"codebuild:StopBuild",
"codebuild:BatchGet*",
"codebuild:Get*",
"codebuild:List*",
"iam:PassRole"
],
"Resource": "*"
}
]
}
EOF
}
######################################################################
# for CloudFormation #
######################################################################
resource "aws_iam_role" "cloudformation" {
name = "${local.cloudformation_role_name}"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudformation.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy" "cloudformation" {
name = "cloudformation_policy"
role = "${aws_iam_role.cloudformation.id}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"apigateway:*",
"cloudformation:CreateChangeSet",
"codedeploy:*",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:CreateTargetGroup",
"elasticloadbalancing:DeleteTargetGroup",
"elasticloadbalancing:ModifyTargetGroup",
"elasticloadbalancing:RegisterTargets",
"elasticloadbalancing:DeRegisterTargets",
"elasticloadbalancing:DescribeListeners",
"elasticloadbalancing:CreateListener",
"elasticloadbalancing:DeleteListener",
"iam:GetRole",
"iam:CreateRole",
"iam:DeleteRole",
"iam:PutRolePolicy",
"iam:AttachRolePolicy",
"iam:DeleteRolePolicy",
"iam:DetachRolePolicy",
"iam:PassRole",
"iam:UpdateAssumeRolePolicy",
"lambda:*",
"logs:*",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketVersioning",
"s3:PutObject"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
EOF
}
resource "aws_s3_bucket" "artifact" {
bucket = "${local.bucket_name}"
}
resource "aws_cloudwatch_log_group" "codebuild" {
name = "${local.codebuild_logstream_name}"
}
resource "aws_codebuild_project" "application" {
name = "${local.build_project_name}"
service_role = "${aws_iam_role.codebuild.arn}"
source {
type = "CODEPIPELINE"
buildspec = "${local.buildspec_file_name}"
}
artifacts {
type = "CODEPIPELINE"
}
environment {
type = "LINUX_CONTAINER"
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
privileged_mode = "true"
environment_variable {
name = "CF_BUCKET_NAME"
value = "${local.bucket_name}"
}
}
logs_config {
cloudwatch_logs {
group_name = "${aws_cloudwatch_log_group.codebuild.name}"
}
}
cache {
type = "LOCAL"
modes = [
"LOCAL_CUSTOM_CACHE",
]
}
}
resource "aws_codepipeline" "pipeline" {
name = "${local.pipeline_name}"
role_arn = "${aws_iam_role.codepipeline.arn}"
artifact_store {
type = "S3"
location = "${aws_s3_bucket.artifact.bucket}"
}
stage {
name = "Source"
action {
run_order = 1
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeCommit"
version = "1"
output_artifacts = ["SourceArtifact"]
configuration = {
RepositoryName = "${local.repository_name}"
BranchName = "master"
}
}
}
stage {
name = "Build"
action {
run_order = 2
name = "Build"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
version = "1"
input_artifacts = ["SourceArtifact"]
output_artifacts = ["BuildArtifact"]
configuration = {
ProjectName = "${aws_codebuild_project.application.name}"
}
}
}
stage {
name = "Deploy"
action {
run_order = 3
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "CloudFormation"
version = "1"
input_artifacts = [
"BuildArtifact",
]
configuration = {
StackName = "${local.stack_name}"
ActionMode = "CREATE_UPDATE"
RoleArn = "${aws_iam_role.cloudformation.arn}"
TemplatePath = "BuildArtifact::output-template.yml"
Capabilities = "CAPABILITY_AUTO_EXPAND,CAPABILITY_NAMED_IAM"
ParameterOverrides = "${data.template_file.cloudformation_parameter.rendered}"
}
}
}
}
data "template_file" "cloudformation_parameter" {
template = file("${path.module}/06_cloudformation_parameter.json")
vars = {
cf_param_lambda_function_name = "${local.cf_param_lambda_function_name}"
cf_param_lambda_execution_role_name = "${local.cf_param_lambda_execution_role_name}"
}
}
{
"LambdaFunctionName": "${cf_param_lambda_function_name}",
"LambdaExecutionRoleName": "${cf_param_lambda_execution_role_name}"
}
TerraformとSAMが混ざると本当にアレで、SAM側でリソースの参照ができないので、CloudFormationのパラメータで無理矢理渡している。CodePipelineの provider = "CloudFormation"
の場合のconfiguration
の書き方がググっても全然見つからなくて、ParameterOverrides
はJSON形式で書く必要があるので、結局は外部ファイルに出してあげることにした。
Buildspec
buildspecは、前述したCodeCommitの面倒くささはあるものの、素直に書ける。
version: 0.2
phases:
install:
runtime-versions:
java: corretto8
commands:
- pip install --upgrade awscli
pre_build:
commands:
- git config --global credential.helper "!aws codecommit credential-helper $@"
- git config --global credential.UseHttpPath true
- git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/xxxxxxxx-gitsubmodule-submodule submodule
build:
commands:
- echo Build started on `date`
- mvn package
post_build:
commands:
- echo Build ended on `date`
- echo CloudFormation Package started on `date`
- aws cloudformation package --template-file template.yml --output-template-file output-template.yml --s3-bucket ${CF_BUCKET_NAME}
- echo CloudFormation Package ended on `date`
artifacts:
type: zip
files:
- output-template.yml
cache:
paths:
- '/root/.m2/**/*'
最後にSAMテンプレートを以下のように定義する。
今回はLambdaを作るところまでなので少し手抜き。
ちゃんとParameters
をTerraformと連動できるようにしておこう。
ちなみに、超簡単なプログラムではあるが、メモリは128MBではOut Of Memoryになった……。
やっぱりSpring Cloud Functions、お手軽にやる分には燃費が悪いのでは……。
あと、注意すべき点としては、CodeUri
に指定するJARファイルは、必ず-aws
の方を指定すること。無印の方はハンドラ等を含まず正しく動作しない。
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Test for Lambda CI/CD Pipeline
Parameters:
LambdaFunctionName:
Description: "Lambda Function Name"
Type: "String"
Default: "LambdaFunctionName"
LambdaExecutionRoleName:
Description: "Lambda Execution Role Name"
Type: "String"
Default: "LambdaExecutionRoleName"
Globals:
Function:
Timeout: 60
Resources:
# ------------------------------------------------------------#
# IAM Role
# ------------------------------------------------------------#
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${LambdaExecutionRoleName}
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
LambdaTest:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${LambdaFunctionName}
Handler: org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler
Runtime: java8
MemorySize: 256
Role: !GetAtt LambdaExecutionRole.Arn
CodeUri: ./mainmodule/target/spring-cloud-function-example-0.0.1-SNAPSHOT-aws.jar
AutoPublishAlias: Prod
DeploymentPreference:
Type: AllAtOnce
ちなみに、メイン側のモジュールから見ると、サブモジュールに対してはリンクしている状態だけなので、サブモジュール側をPushしたとしても、パイプラインは走らない(トリガが違う)。
サブモジュールの更新に合わせて呼び出し元を更新するのであれば、サブモジュールのパイプライン完了のステータスを拾うCloudWatch Eventを設定する必要があるが、それはかなりメンテナンスが大変だろうから、どのようにするのがベストプラクティスなのだろうか……。