UNPKG

opinionated-ci-pipeline

Version:

CI/CD on AWS with feature-branch builds, developer-environment deployments, and build status notifications.

193 lines (186 loc) 7.11 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/lambda/pipelineBuildStatus/index.ts var pipelineBuildStatus_exports = {}; __export(pipelineBuildStatus_exports, { handler: () => handler }); module.exports = __toCommonJS(pipelineBuildStatus_exports); var import_logger = require("@aws-lambda-powertools/logger"); var import_client_codepipeline = require("@aws-sdk/client-codepipeline"); // src/lambda/shared/transformStatusName.ts var transformStatusName = (repositoryType, status) => { switch (repositoryType.toLowerCase()) { case "github": return transformStatusNameForGitHub(status); case "bitbucket": return transformStatusNameForBitbucket(status); default: return null; } }; var transformStatusNameForGitHub = (status) => { switch (status) { case "STARTED": // CodePipeline case "IN_PROGRESS": return "pending"; case "SUCCEEDED": return "success"; case "FAILED": return "failure"; case "STOPPED": return "error"; default: return null; } }; var transformStatusNameForBitbucket = (status) => { switch (status) { case "STARTED": // CodePipeline case "IN_PROGRESS": return "INPROGRESS"; case "SUCCEEDED": return "SUCCESSFUL"; case "FAILED": return status; case "STOPPED": return status; default: return null; } }; // src/lambda/shared/ssm.ts var import_client_ssm = require("@aws-sdk/client-ssm"); var ssm = new import_client_ssm.SSMClient({}); var getSSMParameter = async (name) => { const repositoryToken = (await ssm.send(new import_client_ssm.GetParameterCommand({ Name: name, WithDecryption: true }))).Parameter?.Value; if (!repositoryToken) { throw new Error(`Unable to retrieve SSM parameter "${name}"`); } return repositoryToken; }; // src/lambda/shared/api.ts var githubApiCall = async (token, path, method, body) => { return await fetch(`https://api.github.com/${path}`, { method, headers: { "Authorization": `Bearer ${token}`, "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" }, body: body ? JSON.stringify(body) : void 0 }); }; var bitbucketApiCall = async (token, path, method, body) => { return await fetch(`https://api.bitbucket.org/2.0/${path}`, { method, headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, body: body ? JSON.stringify(body) : void 0 }); }; // src/lambda/shared/commitStatus.ts var import_crypto = require("crypto"); var sendCommitStatus = async (repositoryHost, repositoryName, token, status, commitSha, buildName, buildUrl, description) => { switch (repositoryHost.toLowerCase()) { case "github": return await sendGitHubCommitStatus(repositoryName, token, status, commitSha, buildName, buildUrl); case "bitbucket": return await sendBitbucketCommitStatus(repositoryName, token, status, commitSha, buildName, buildUrl, description); default: throw new Error(`Unsupported repository host to send commit status to: ${repositoryHost}`); } }; var sendGitHubCommitStatus = async (repositoryName, token, status, commitSha, buildName, buildUrl) => { const response = await githubApiCall(token, `repos/${repositoryName}/statuses/${commitSha}`, "POST", { state: status, target_url: buildUrl, context: buildName }); if (response.status >= 300) { throw new Error(`Failed to send status to GitHub. Status: ${response.status}, response: ${await response.text()}`); } }; var sendBitbucketCommitStatus = async (repositoryName, token, status, commitSha, buildName, buildUrl, description) => { const response = await bitbucketApiCall(token, `repositories/${repositoryName}/commit/${commitSha}/statuses/build`, "POST", { // hash function used, because build status key must be shorter than 40 characters key: (0, import_crypto.createHash)("md5").update(buildName).digest("hex"), state: status, name: buildName, description, url: buildUrl }); if (response.status >= 300) { throw new Error(`Failed to send status to Bitbucket. Status: ${response.status}, response: ${await response.text()}`); } }; // src/lambda/pipelineBuildStatus/index.ts var import_client_s3 = require("@aws-sdk/client-s3"); var REPOSITORY_HOST = process.env.REPOSITORY_HOST || ""; var REPOSITORY_NAME = process.env.REPOSITORY_NAME || ""; var REPOSITORY_TOKEN_PARAM_NAME = process.env.REPOSITORY_TOKEN_PARAM_NAME || ""; var DEFAULT_BRANCH_NAME = process.env.DEFAULT_BRANCH_NAME || ""; var REGION = process.env.AWS_REGION || ""; var SOURCE_BUCKET_NAME = process.env.SOURCE_BUCKET_NAME || ""; var logger = new import_logger.Logger(); var codePipeline = new import_client_codepipeline.CodePipelineClient({}); var s3 = new import_client_s3.S3Client({}); var handler = async (event) => { logger.info("Event", { event }); const { pipeline: pipelineName, "execution-id": pipelineExecutionId } = event.detail; const status = transformStatusName(REPOSITORY_HOST, event.detail.state); if (!status) { logger.warn("Ignoring unsupported state change"); return; } const execution = await codePipeline.send(new import_client_codepipeline.GetPipelineExecutionCommand({ pipelineName, pipelineExecutionId })); const mirrorVersion = execution.pipelineExecution?.artifactRevisions?.[0]?.revisionId; if (!mirrorVersion) { logger.warn("Repository mirror zip file version not found", { execution }); return; } const head = await s3.send( new import_client_s3.HeadObjectCommand({ Bucket: SOURCE_BUCKET_NAME, Key: `repository-${DEFAULT_BRANCH_NAME}.zip`, VersionId: mirrorVersion }) ); const commitSha = head.Metadata?.["commit-sha"]; if (!commitSha) { logger.warn("Commit SHA not found in repository mirror metadata", { head }); return; } const repositoryToken = await getSSMParameter(REPOSITORY_TOKEN_PARAM_NAME); const buildUrl = `https://${REGION}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${pipelineName}/executions/${pipelineExecutionId}`; await sendCommitStatus(REPOSITORY_HOST, REPOSITORY_NAME, repositoryToken, status, commitSha, pipelineName, buildUrl, "AWS CodePipeline"); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { handler });