cdk-codepipeline-badge-notification
Version:
[](https://badge.fury.io/js/cdk-codepipeline-badge-notification) [](https://badge.fury.io/py/c
249 lines (219 loc) • 7.65 kB
text/typescript
import {
CodePipelineClient,
GetPipelineExecutionCommand,
} from '@aws-sdk/client-codepipeline';
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { Callback, CodePipelineCloudWatchEvent, Context } from 'aws-lambda';
import { default as axios } from 'axios';
import url from 'url';
enum ChatProvider {
SLACK = 'slack',
GOOGLE_CHAT = 'google_chat',
TELEGRAM = 'telegram',
}
enum CodePipelineState {
STARTED = 'STARTED',
RESUMED = 'RESUMED',
CANCELED = 'CANCELED',
FAILED = 'FAILED',
SUCCEEDED = 'SUCCEEDED',
SUPERSEDED = 'SUPERSEDED',
}
interface SourceActionData {
owner?: string;
repository?: string;
sha: string;
}
const CodePipelineFailState = [
CodePipelineState.CANCELED as string,
CodePipelineState.FAILED as string,
CodePipelineState.SUCCEEDED as string,
];
export const handler = async (
event: CodePipelineCloudWatchEvent,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
context: Context,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
callback: Callback
): Promise<void> => {
console.info('Debug event\n' + JSON.stringify(event, null, 2));
const state = event.detail.state;
const stageTitle = process.env.STAGE ? `[${process.env.STAGE}] \n` : '';
const subject = `CodePIpeline Name: ${event.detail.pipeline} \n`;
const codePipelineLink = `https://ap-northeast-1.console.aws.amazon.com/codesuite/codepipeline/pipelines/${event.detail.pipeline}/view`;
const stateMessage = getStateMessage(state, event['detail-type'], codePipelineLink);
const slackUrl = (process.env.SLACK_WEBHOOK_URL as string) ?? '';
const googleChatUrl = (process.env.GOOGLE_CHAT_WEBHOOK_URL as string) ?? '';
const telegramUrl = (process.env.TELEGRAM_WEBHOOK_URL as string) ?? '';
const badgeBucket = process.env.BADGE_BUCKET_NAME as string;
const badgeBucketImageKeyName = process.env
.BADGE_BUCKET_IMAGE_KEY_NAME as string;
const passingSvgUrl =
'https://img.shields.io/badge/AWS%20CodePipeline-passing-green.svg';
const failSvgUrl =
'https://img.shields.io/badge/AWS%20CodePipeline-fail-red.svg';
const executionId = event.detail['execution-id'];
const codePipelineName = process.env.CODE_PIPELINE_NAME as string;
const githubPersonalToken =
(process.env.GITHUB_PERSONAL_TOKEN as string) ?? '';
if (slackUrl) {
await sendMessageToWebHook(ChatProvider.SLACK, slackUrl, `${stageTitle}${subject}${stateMessage}`);
}
if (googleChatUrl) {
await sendMessageToWebHook(ChatProvider.GOOGLE_CHAT, googleChatUrl, `${stageTitle}${subject}${stateMessage}`);
}
if (telegramUrl) {
await sendMessageToWebHook(ChatProvider.TELEGRAM, telegramUrl, `${stageTitle}${subject}${stateMessage}`);
}
let imageUrl: string | null = null;
if (state == CodePipelineState.SUCCEEDED) {
imageUrl = passingSvgUrl;
} else if (CodePipelineFailState.includes(state)) {
imageUrl = failSvgUrl;
}
console.log(`debug badge update image: ${imageUrl}`);
if (imageUrl) {
const imageResp = await axios.get(imageUrl, {
responseType: 'arraybuffer',
});
const s3 = new S3Client({ region: 'ap-northeast-1' });
await s3.send(
new PutObjectCommand({
Bucket: badgeBucket,
Key: badgeBucketImageKeyName,
Body: Buffer.from(imageResp.data),
ContentType: 'image/svg+xml',
CacheControl: 'cache-control: no-cache',
Expires: new Date(Date.now()),
})
);
}
const sourceActionData = await getPipelineSourceActionData(
executionId,
codePipelineName
);
let sourceActionState: string | null = null;
switch (state) {
case CodePipelineState.STARTED:
case CodePipelineState.RESUMED:
case CodePipelineState.SUPERSEDED:
sourceActionState = 'pending';
break;
case CodePipelineState.SUCCEEDED:
sourceActionState = 'success';
break;
case CodePipelineState.CANCELED:
case CodePipelineState.FAILED:
sourceActionState = 'error';
break;
}
console.log(`debug state: ${state}`);
console.log(`debug sourceActionData: ${JSON.stringify(sourceActionData)}`);
if (githubPersonalToken && sourceActionState && sourceActionData && sourceActionData.owner && sourceActionData.repository) {
console.log(
`sourceActionCommitStatusUrl:\n https://api.github.com/repos/${sourceActionData?.owner}/${sourceActionData?.repository}/statuses/${sourceActionData?.sha}`
);
const respSourceActionData = await axios
.create({
headers: {
'Context-Type': 'application/json',
Authorization: `token ${githubPersonalToken}`,
},
})
.post(
`https://api.github.com/repos/${sourceActionData?.owner}/${sourceActionData?.repository}/statuses/${sourceActionData?.sha}`,
{
state: sourceActionState,
target_url: `https://ap-northeast-1.console.aws.amazon.com/codesuite/codepipeline/pipelines/${codePipelineName}/view`,
context: 'continuous-integration/codepipeline',
description: `Build ${sourceActionState}`,
}
);
console.log(respSourceActionData);
}
};
const getStateMessage = (
state: string,
title: string,
codePipelineLink: string,
): string => {
let returnMessage = '';
switch (state) {
case CodePipelineState.STARTED:
returnMessage = `${title}: ${state} 🚀 \n `;
break;
case CodePipelineState.SUCCEEDED:
returnMessage = `${title}: ${state} ✅ \n `;
break;
case CodePipelineState.FAILED:
returnMessage = `${title}: ${state} ❌ \n See more details: ${codePipelineLink} \n `;
break;
default:
returnMessage = `${title}: ${state}`;
}
return returnMessage;
}
const sendMessageToWebHook = async (
serviceType: string,
webhookRUL: string,
message: string,
):Promise<void> => {
let respData: string;
switch (serviceType) {
case ChatProvider.SLACK:
case ChatProvider.GOOGLE_CHAT:
respData = await axios
.create({
headers: { 'Context-Type': 'application/json; charset=UTF-8' },
})
.post(webhookRUL, { text: message });
break;
case ChatProvider.TELEGRAM:
respData = await axios
.create({
headers: { 'Context-Type': 'application/json; charset=UTF-8' },
})
.get(`${webhookRUL}&text=${encodeURIComponent(message)}`);
break;
default:
respData = '';
}
console.log(`webhookUrl response:\n ${respData}`);
}
const getPipelineSourceActionData = async (
executionId: string,
pipelineName: string
): Promise<SourceActionData | null> => {
const client = new CodePipelineClient({
region: 'ap-northeast-1',
});
const result = await client.send(
new GetPipelineExecutionCommand({
pipelineExecutionId: executionId,
pipelineName: pipelineName,
})
);
console.log(`pipeline data:\n ${JSON.stringify(result)}`);
const artifactRevision = result.pipelineExecution?.artifactRevisions
? result.pipelineExecution?.artifactRevisions[0]
: null;
if (artifactRevision) {
const sha = artifactRevision.revisionId;
const revisionURL = artifactRevision?.revisionUrl;
let repoInfo = {};
if (revisionURL) {
const fullRepositoryId = new url.URL(
revisionURL as string
).searchParams.get('FullRepositoryId') as string;
repoInfo = {
owner: fullRepositoryId ? fullRepositoryId.split('/')[0] : '',
repository: fullRepositoryId ? fullRepositoryId.split('/')[1] : '',
}
}
return {
sha: sha ? sha : '',
...repoInfo
};
}
return null;
};