// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 import { CloudFormationCustomResourceHandler, CloudFormationCustomResourceUpdateEvent, CloudFormationCustomResourceDeleteEvent } from 'aws-lambda'; import { CodeCommit } from 'aws-sdk'; const cfnCR = require('cfn-custom-resource'); const { configure, sendResponse, LOG_VERBOSE, SUCCESS, FAILED } = cfnCR; const equal = require('deep-equal'); configure({ logLevel: LOG_VERBOSE }); const codecommit = new CodeCommit(); function buildTemplateContent(props: { ServiceToken: string; [Key: string]: any;}): string { const template = { Version: '2018-11-08', DestinationReferences: props.Template.destinationReferences || undefined, Statements: [ { Type: 'Approvers', NumberOfApprovalsNeeded: props.Template.approvers.numberOfApprovalsNeeded, ApprovalPoolMembers: props.Template.approvers.approvalPoolMembers || undefined, } ] }; return JSON.stringify(template, null, 2); } export const approvalRuleTemplate: CloudFormationCustomResourceHandler = async (event, _context) => { console.info(`Receiving ApprovalRuleEvent of CodeCommit ${JSON.stringify(event, null, 2)}`); var responseData: any; var result = SUCCESS; var reason: any = ''; var resourceId: string | undefined = undefined; try { switch (event.RequestType) { case 'Create': const createTempalteResponse = await codecommit.createApprovalRuleTemplate({ approvalRuleTemplateName: event.ResourceProperties.ApprovalRuleTemplateName, approvalRuleTemplateDescription: event.ResourceProperties.ApprovalRuleTemplateDescription || '', approvalRuleTemplateContent: buildTemplateContent(event.ResourceProperties), }).promise(); console.info(`Created approval rule template ${JSON.stringify(createTempalteResponse.$response.data, null, 2)}.`); responseData = (createTempalteResponse.$response.data as CodeCommit.Types.CreateApprovalRuleTemplateOutput).approvalRuleTemplate; resourceId = responseData.approvalRuleTemplateId; break; case 'Update': const updateEvent = event as CloudFormationCustomResourceUpdateEvent; resourceId = updateEvent.PhysicalResourceId; const changes = []; if (!equal(updateEvent.ResourceProperties.Template, updateEvent.OldResourceProperties.Template)) { changes.push(codecommit.updateApprovalRuleTemplateContent({ approvalRuleTemplateName: updateEvent.OldResourceProperties.ApprovalRuleTemplateName, newRuleContent: buildTemplateContent(updateEvent.ResourceProperties), }).promise()) } if (updateEvent.ResourceProperties.ApprovalRuleTemplateDescription !== updateEvent.OldResourceProperties.ApprovalRuleTemplateDescription) { changes.push(codecommit.updateApprovalRuleTemplateDescription({ approvalRuleTemplateName: updateEvent.OldResourceProperties.ApprovalRuleTemplateName, approvalRuleTemplateDescription: updateEvent.ResourceProperties.ApprovalRuleTemplateDescription || '', }).promise()); } if (changes.length > 0) { const updateDescAndTemplateRt = await Promise.all(changes); console.info(`Updated approval rule '${updateEvent.OldResourceProperties.ApprovalRuleTemplateName}' descirption and template content ${updateDescAndTemplateRt}.`); responseData = (updateDescAndTemplateRt[0].$response.data as CodeCommit.Types.UpdateApprovalRuleTemplateContentOutput | CodeCommit.Types.UpdateApprovalRuleTemplateDescriptionOutput).approvalRuleTemplate; } if (updateEvent.ResourceProperties.ApprovalRuleTemplateName !== updateEvent.OldResourceProperties.ApprovalRuleTemplateName) { const updatedApprovalTemplate = await codecommit.updateApprovalRuleTemplateName({ newApprovalRuleTemplateName: updateEvent.ResourceProperties.ApprovalRuleTemplateName, oldApprovalRuleTemplateName: updateEvent.OldResourceProperties.ApprovalRuleTemplateName, }).promise(); console.log(`Updated approval rule name from '${updateEvent.OldResourceProperties.ApprovalRuleTemplateName} to '${updateEvent.ResourceProperties.ApprovalRuleTemplateName}'.`) responseData = (updatedApprovalTemplate as CodeCommit.Types.UpdateApprovalRuleTemplateNameOutput).approvalRuleTemplate; } break; case 'Delete': const deleteEvent = event as CloudFormationCustomResourceDeleteEvent; resourceId = deleteEvent.PhysicalResourceId; const deleteTempalteResponse = await codecommit.deleteApprovalRuleTemplate({ approvalRuleTemplateName: event.ResourceProperties.ApprovalRuleTemplateName, }).promise(); console.info(`Deleted approval rule template ${JSON.stringify(deleteTempalteResponse.$response.data, null, 2)}.`); responseData = deleteTempalteResponse.$response.data; break; } } catch (err) { if (err instanceof Error) { console.error(`Failed to create approval rule template due to ${err}.`); responseData = err.message; result = FAILED; reason = err.message; } } return await sendResponse({ Status: result, Reason: reason, PhysicalResourceId: (resourceId ? resourceId : _context.logStreamName), Data: responseData }, event); } export const approvalRuleRepoAssociation: CloudFormationCustomResourceHandler = async (event, _context) => { console.info(`Receiving ApprovalRuleAssociationEvent of CodeCommit ${JSON.stringify(event, null, 2)}`); var responseData: any; var result = SUCCESS; var reason: any = ''; var resourceId: string | undefined = undefined; try { switch (event.RequestType) { case 'Create': const assoRsp = await codecommit.batchAssociateApprovalRuleTemplateWithRepositories({ approvalRuleTemplateName: event.ResourceProperties.ApprovalRuleTemplateName, repositoryNames: event.ResourceProperties.RepositoryNames }).promise(); console.info(`Associated ${assoRsp.associatedRepositoryNames} with ${event.ResourceProperties.ApprovalRuleTemplateName}.`); resourceId = `${event.ResourceProperties.ApprovalRuleTemplateName}-repos`; responseData = { AssociatedRepoNames: assoRsp.associatedRepositoryNames, }; break; case 'Update': const updateEvent = event as CloudFormationCustomResourceUpdateEvent; resourceId = `${updateEvent.ResourceProperties.ApprovalRuleTemplateName}-repos`; const added = updateEvent.ResourceProperties.ApprovalRuleTemplateName == updateEvent.OldResourceProperties.ApprovalRuleTemplateName ? (updateEvent.ResourceProperties.RepositoryNames as Array<string>).filter((element, index, array) => { return !(updateEvent.OldResourceProperties.RepositoryNames as Array<string>).includes(element); }) : updateEvent.ResourceProperties.RepositoryNames as Array<string>; const deleted = updateEvent.ResourceProperties.ApprovalRuleTemplateName == updateEvent.OldResourceProperties.ApprovalRuleTemplateName ? (updateEvent.OldResourceProperties.RepositoryNames as Array<string>).filter((element, index, array) => { return !(updateEvent.ResourceProperties.RepositoryNames as Array<string>).includes(element); }) : updateEvent.OldResourceProperties.RepositoryNames as Array<string>; const changes = []; if (added.length > 0) { const assoRsp = await codecommit.batchAssociateApprovalRuleTemplateWithRepositories({ approvalRuleTemplateName: updateEvent.ResourceProperties.ApprovalRuleTemplateName, repositoryNames: added }).promise(); console.info(`Associated ${assoRsp.associatedRepositoryNames} with ${updateEvent.ResourceProperties.ApprovalRuleTemplateName}.`); responseData = { AssociatedRepoNames: assoRsp.associatedRepositoryNames, }; } if (deleted.length > 0) { const disassoRsp = codecommit.batchDisassociateApprovalRuleTemplateFromRepositories({ approvalRuleTemplateName: updateEvent.OldResourceProperties.ApprovalRuleTemplateName, repositoryNames: deleted, }).promise(); console.info(`DisAssociated ${(await disassoRsp).disassociatedRepositoryNames} with ${updateEvent.OldResourceProperties.ApprovalRuleTemplateName}.`); responseData = Object.assign(responseData ? responseData : {}, { DisAssociatedRepoNames: (await disassoRsp).disassociatedRepositoryNames, }); } break; case 'Delete': const deleteEvent = event as CloudFormationCustomResourceDeleteEvent; resourceId = deleteEvent.PhysicalResourceId; const disassociateRsp = await codecommit.batchDisassociateApprovalRuleTemplateFromRepositories({ approvalRuleTemplateName: deleteEvent.ResourceProperties.ApprovalRuleTemplateName, repositoryNames: deleteEvent.ResourceProperties.RepositoryNames }).promise(); console.info(`Disassociated ${disassociateRsp.disassociatedRepositoryNames} with ${deleteEvent.ResourceProperties.ApprovalRuleTemplateName}.`); responseData = { DisAssociatedRepoNames: disassociateRsp.disassociatedRepositoryNames, }; break; } } catch (err) { if (err instanceof Error) { console.error(`Failed to associate/disassociate approval rule template with repos due to ${err}.`); responseData = err.message; result = FAILED; reason = err.message; } } return await sendResponse({ Status: result, Reason: reason, PhysicalResourceId: (resourceId ? resourceId : _context.logStreamName), Data: responseData }, event); }