この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
テントの中から失礼します、IoT 事業部のてんとタカハシです!
GitHub と CodePipeline を連携した際、デフォルトでは Push をトリガーにして、パイプラインを起動する設定になりますが、ブランチの運用手段によっては Release など他のイベントをトリガーにしたいケースがあります。
今回は、GitHub リポジトリ上での Release をトリガーにして、パイプラインを起動する構成を CDK で構築します。
尚、GitHub のソースアクションは「Version 1」と「Version 2」の2つが存在しますが、本記事では既に非推奨となっている「Version 1」を使用する点についてご注意ください。「Version 2」を使用した場合の構築については別記事にて記載します。
環境
% sw_vers
ProductName: macOS
ProductVersion: 12.6
BuildVersion: 21G115
% aws --version
aws-cli/2.8.2 Python/3.9.11 Darwin/21.6.0 exe/x86_64 prompt/off
% cdk --version
2.46.0 (build 5a0595e)
GitHub のアクセストークンを準備する
GitHub のソースアクション「Version 1」を使用して、CodePipeline から GitHub のリポジトリにアクセスするためには、GitHub のアクセストークンが必要です。
https://github.com/settings/tokens にアクセスして「Generate new token (classic)」をクリックします。
アクセストークンの名前を入力した後、repo
と
admin:repo_hook
を許可するためにチェックを入れます。
「Generate token」をクリックして、アクセストークンを生成します。
アクセストークンが表示されますので、コピーしてください。
アクセストークンをソースコードに埋め込むわけにはいかないので、Secrets Manager に登録します。
% aws secretsmanager create-secret \
--name github-sample-token \
--secret-string <GITHUB_ACCESS_TOKEN>
{
"ARN": "arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:github-sample-token-jvRSBB",
"Name": "github-sample-token",
"VersionId": "95c08eaa-a640-41b2-a2c7-f5673f65f6a3"
}
Push をトリガーにする実装
まずはデフォルトの Push をトリガーにして、パイプラインを起動できるように実装します。
cdk.json
githubOwnerName
、githubRepositoryName
は各自の環境に合わせて変更してください。ここでは、main ブランチに Push したタイミングでパイプラインを起動するようにします。githubTokenName
には、前の手順で Secrets Manager に登録したシークレットの名前を入力しています。
cdk.json
{
"app": "npx ts-node --prefer-ts-exts bin/codepipeline-triggered-by-github-releases.ts",
"watch": {
...
},
"context": {
"projectName": "codepipeline-triggered-by-github-releases",
"githubOwnerName": "iam326",
"githubRepositoryName": "codepipeline-triggered-by-github-releases",
"githubBranchName": "main",
"githubTokenName": "github-sample-token",
...
}
}
bin
cdk.json に埋め込んだ各種情報をスタックに渡しています。
bin/codepipeline-triggered-by-github-releases.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CodepipelineTriggeredByGithubReleasesStack } from '../lib/codepipeline-triggered-by-github-releases-stack';
const app = new cdk.App();
const projectName = app.node.tryGetContext('projectName');
const githubOwnerName = app.node.tryGetContext('githubOwnerName');
const githubRepositoryName = app.node.tryGetContext('githubRepositoryName');
const githubBranchName = app.node.tryGetContext('githubBranchName');
const githubTokenName = app.node.tryGetContext('githubTokenName');
new CodepipelineTriggeredByGithubReleasesStack(app, `${projectName}-cicd`, {
projectName,
githubOwnerName,
githubRepositoryName,
githubBranchName,
githubTokenName,
});
CICD スタック
GitHub からソースコードをダウンロードする source アクション(GitHub ソースアクション Version 1)と、CodeBuild を実行する deploy アクションを持つパイプラインを作成します。GitHub のアクセストークンは Secrets Manager から取得しています。
lib/codepipeline-triggered-by-github-releases-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as codeBuild from 'aws-cdk-lib/aws-codebuild';
import * as codePipeline from 'aws-cdk-lib/aws-codepipeline';
import * as codePipelineActions from 'aws-cdk-lib/aws-codepipeline-actions';
export interface CodepipelineTriggeredByGithubReleaseStackProps
extends cdk.StackProps {
projectName: string;
githubOwnerName: string;
githubRepositoryName: string;
githubBranchName: string;
githubTokenName: string;
}
export class CodepipelineTriggeredByGithubReleasesStack extends cdk.Stack {
constructor(
scope: Construct,
id: string,
props: CodepipelineTriggeredByGithubReleaseStackProps
) {
super(scope, id, props);
const {
projectName,
githubOwnerName,
githubRepositoryName,
githubBranchName,
githubTokenName,
} = props;
const githubToken =
cdk.SecretValue.secretsManager(githubTokenName).unsafeUnwrap();
const sourceArtifact = new codePipeline.Artifact();
const codeBuildDeployProject = new codeBuild.PipelineProject(
this,
'CodeBuildDeployProject',
{
projectName: `${projectName}-deploy-project`,
buildSpec: codeBuild.BuildSpec.fromSourceFilename('./buildspec.yml'),
}
);
const sourceAction = new codePipelineActions.GitHubSourceAction({
actionName: 'source',
owner: githubOwnerName,
repo: githubRepositoryName,
branch: githubBranchName,
oauthToken: new cdk.SecretValue(githubToken),
output: sourceArtifact,
});
const deployAction = new codePipelineActions.CodeBuildAction({
actionName: 'deploy',
project: codeBuildDeployProject,
input: sourceArtifact,
});
const deployPipeline = new codePipeline.Pipeline(this, 'DeployPipeline', {
pipelineName: `${projectName}-deploy-pipeline`,
stages: [
{
stageName: 'source',
actions: [sourceAction],
},
{
stageName: 'deploy',
actions: [deployAction],
},
],
});
}
}
buildspec.yml
CodeBuild が起動すれば良いだけですので、中身は特にありません。
buildspec.yml
version: 0.2
phases:
install:
commands:
- echo "install ok"
build:
commands:
- echo "build ok"
デモ
下記で CICD スタックをデプロイします。
% cdk deploy "*"
続いて、空コミットを作成してから、main ブランチに Push します。
% git commit --allow-empty -m "cicd test"
% git push origin main
すると、パイプラインが起動することを確認できます。
Release をトリガーにする実装
前の手順で作成したリソースを基に、パイプラインを起動するトリガーを Release に変更します。
Webhook 用のシークレットトークンを準備する
GitHub の Release をトリガーにして Webhook で送られてくるペイロードを受付可能なエンドポイントを CodePipeline 側に作成するのですが、その際にシークレットトークンを指定する必要があります。ここでは、ローカルで生成した UUID をシークレットトークンとして扱います。GitHub のアクセストークンと同様に Secrets Manager に登録します。
% aws secretsmanager create-secret \
--name webhook-sample-secret-token \
--secret-string $(uuidgen)
{
"ARN": "arn:aws:secretsmanager:ap-northeast-1:xxxxxxxxxxxx:secret:webhook-sample-secret-token-Ve5AE4",
"Name": "webhook-sample-secret-token",
"VersionId": "47639002-1651-47f6-b64c-fd994bd931f5"
}
cdk.json
webhookSecretTokenName
を追加します。前の手順で Secrets Manager に登録したシークレットの名前を入力しています。
cdk.json
...
"context": {
"projectName": "codepipeline-triggered-by-github-releases",
"githubOwnerName": "iam326",
"githubRepositoryName": "codepipeline-triggered-by-github-releases",
"githubBranchName": "main",
"githubTokenName": "github-sample-token",
"webhookSecretTokenName": "webhook-sample-secret-token",
...
bin
cdk.json に追加したwebhookSecretTokenName
をスタックに渡すようにします。
bin/codepipeline-triggered-by-github-releases.ts
...
const webhookSecretTokenName = app.node.tryGetContext('webhookSecretTokenName');
new CodepipelineTriggeredByGithubReleasesStack(app, `${projectName}-cicd`, {
projectName,
githubOwnerName,
githubRepositoryName,
githubBranchName,
githubTokenName,
webhookSecretTokenName,
});
CICD スタック
デフォルトのトリガーを無効にしつつ、Webhook 用のシークレットトークンを使用して新たな Webhook のエンドポイントを作成します。このエンドポイントでは、GitHub の Release イベントをトリガーとして送られてくるペイロードを通すためのフィルターを設定します。
Release イベントで送られてくるペイロードの例はこちらに記載があります。
lib/codepipeline-triggered-by-github-releases-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as codeBuild from 'aws-cdk-lib/aws-codebuild';
import * as codePipeline from 'aws-cdk-lib/aws-codepipeline';
import * as codePipelineActions from 'aws-cdk-lib/aws-codepipeline-actions';
export interface CodepipelineTriggeredByGithubReleaseStackProps
extends cdk.StackProps {
projectName: string;
githubOwnerName: string;
githubRepositoryName: string;
githubBranchName: string;
githubTokenName: string;
webhookSecretTokenName: string;
}
export class CodepipelineTriggeredByGithubReleasesStack extends cdk.Stack {
constructor(
scope: Construct,
id: string,
props: CodepipelineTriggeredByGithubReleaseStackProps
) {
super(scope, id, props);
const {
projectName,
githubOwnerName,
githubRepositoryName,
githubBranchName,
githubTokenName,
webhookSecretTokenName,
} = props;
const githubToken =
cdk.SecretValue.secretsManager(githubTokenName).unsafeUnwrap();
const webhookSecretToken = cdk.SecretValue.secretsManager(
webhookSecretTokenName
).unsafeUnwrap();
const sourceArtifact = new codePipeline.Artifact();
const codeBuildDeployProject = new codeBuild.PipelineProject(
this,
'CodeBuildDeployProject',
{
projectName: `${projectName}-deploy-project`,
buildSpec: codeBuild.BuildSpec.fromSourceFilename('./buildspec.yml'),
}
);
const sourceAction = new codePipelineActions.GitHubSourceAction({
actionName: 'source',
owner: githubOwnerName,
repo: githubRepositoryName,
branch: githubBranchName,
oauthToken: new cdk.SecretValue(githubToken),
output: sourceArtifact,
// デフォルトのトリガーを無効にする
trigger: codePipelineActions.GitHubTrigger.NONE,
});
const deployAction = new codePipelineActions.CodeBuildAction({
actionName: 'deploy',
project: codeBuildDeployProject,
input: sourceArtifact,
});
const deployPipeline = new codePipeline.Pipeline(this, 'DeployPipeline', {
pipelineName: `${projectName}-deploy-pipeline`,
stages: [
{
stageName: 'source',
actions: [sourceAction],
},
{
stageName: 'deploy',
actions: [deployAction],
},
],
});
new codePipeline.CfnWebhook(this, 'WebhookResource', {
authentication: 'GITHUB_HMAC',
authenticationConfiguration: {
secretToken: webhookSecretToken,
},
// GitHub の Release イベントで送られてくるペイロードを通すためのフィルター
filters: [
{
jsonPath: '$.action',
matchEquals: 'published',
},
],
targetAction: sourceAction.actionProperties.actionName,
targetPipeline: deployPipeline.pipelineName,
targetPipelineVersion: 1,
registerWithThirdParty: true,
});
}
}
GitHub で Release をトリガーに Webhook を投げる
上記の実装により、CodePipeline 側が GitHub の Release イベントで送られてくるペイロードを受け付けることができるようになりました。続いて、GitHub 側が Release をトリガーに Webhook を投げるための設定を行います。
先に上記のスタックを更新しておきます。
% cdk deploy "*"
下記 URL にアクセスして、対象となるリポジトリの Webhook 一覧画面を表示した後、「Edit」をクリックします。
https://github.com/[USER_NAME]/[REPOSITORY_NAME]/settings/hooks
「Let me select individual events.」を選択すると、イベントの一覧が表示されるので、
Pushes
のチェックを外して、代わりにReleases
にチェックを入れます。
「Update webhook」をクリックします。
Webhook 一覧画面に戻り、対象の Webhook に (release) と表示されていれば OK です。これで準備完了です。
デモ
試しに空コミットを作成して main ブランチに Push してみます。
% git commit --allow-empty -m "cicd test2"
% git push origin main
すると、Push ではパイプラインが起動しなくなったことを確認できます(下記画像では source アクションの成功が44分前になっている)。
続いて、GitHub で Release をしてみます。下記 URL にアクセスして、対象となるリポジトリの Release 一覧を表示した後、「Create a new release」をクリックします。
https://github.com/[USER_NAME]/[REPOSITORY_NAME]/releases
「Choose a tag」で適当な名前のタグを作成します。
「Publish release」をクリックして、Release します。
すると、パイプラインが起動することを確認できます。
おわりに
GitHub の Release をトリガーにして、パイプラインを起動するためには少し煩雑な手順が必要でした。しかし、この手順を活用すれば、Release 以外のイベントについてもトリガーにすることが可能ですので、覚えておいて損は無いと思います。
近いうちに、GitHub のソースアクション「Version 2」を使用した場合の構成についても記事にしようと思うので、そちらも確認いただけると幸いです。
今回は以上になります。最後まで読んで頂きありがとうございました!