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
JavaScript
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
});