Skip to content

Commit

Permalink
feat: Include account alias in the human-readable label
Browse files Browse the repository at this point in the history
  • Loading branch information
markusl committed Aug 30, 2021
1 parent b9e5b96 commit e40469c
Show file tree
Hide file tree
Showing 20 changed files with 327 additions and 52 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .github/workflows/release.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .github/workflows/upgrade.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion .gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion .npmignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion .projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ const {
const AWS_CDK_LATEST_RELEASE = '2.0.0-rc.19';

const PROJECT_NAME = 'cdk-codepipeline-bitbucket-build-result-reporter';
const PROJECT_DESCRIPTION = 'A JSII construct lib for reporting AWS CodePipeline build statuses to a Bitbucket server instance';
const PROJECT_DESCRIPTION = 'A JSII construct lib for reporting AWS CodePipeline and build statuses to a Bitbucket server instance';

const project = new AwsCdkConstructLibrary({
name: PROJECT_NAME,
description: PROJECT_DESCRIPTION,
authorName: 'Markus',
authorName: 'Markus Lindqvist',
authorEmail: '[email protected]',
stability: 'stable',
repository: 'https://github.com/markusl/cdk-codepipeline-bitbucket-build-result-reporter.git',
cdkVersion: AWS_CDK_LATEST_RELEASE,
defaultReleaseBranch: 'master',
minNodeVersion: '14.15.0',
tsconfig: {
compilerOptions: {
lib: ['ES2019'],
Expand All @@ -27,10 +28,12 @@ const project = new AwsCdkConstructLibrary({
devDeps: [
'@types/aws-lambda',
'@types/node-fetch',
'aws-sdk-client-mock',
'esbuild',
'[email protected]',
],
bundledDeps: [
'@aws-sdk/client-iam',
'@aws-sdk/client-ssm',
'@aws-sdk/client-codebuild',
'@aws-sdk/client-codepipeline',
Expand All @@ -40,7 +43,6 @@ const project = new AwsCdkConstructLibrary({
});

const common_exclude = [
'.cdk.staging',
'cdk.context.json',
'cdk.out',
'coverage',
Expand Down
12 changes: 9 additions & 3 deletions package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/bitbucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const ssm = new SSM.SSMClient({});

export const getToken = async (Name: string) => {
const result = await ssm.send(new SSM.GetParameterCommand({ Name, WithDecryption: true }));
if (result.Parameter && result.Parameter.Value) {
if (result?.Parameter?.Value) {
return result.Parameter?.Value;
}
throw new Error('Cannot fetch token');
Expand Down
16 changes: 16 additions & 0 deletions src/iam-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as IAM from '@aws-sdk/client-iam';

const iam = new IAM.IAMClient({});

export const getCurrentAccountAlias = async (accountId: string) => {
try {
const aliases = await iam.send(new IAM.ListAccountAliasesCommand({}));
if (aliases.AccountAliases && aliases.AccountAliases.length > 0) {
return aliases.AccountAliases[0];
}
return accountId;
} catch (err) {
console.error(err);
return accountId;
}
};
9 changes: 5 additions & 4 deletions src/index.CodeBuildStatusHandler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as CodeBuild from '@aws-sdk/client-codebuild';
import type * as AwsLambda from 'aws-lambda';
import { BitbucketBuildStatus, putCodePipelineResultToBitBucket } from './bitbucket';
import { getCurrentAccountAlias } from './iam-helper';

const codeBuild = new CodeBuild.CodeBuildClient({ });

export const buildBitbucketBuildStatusBody = (
export const buildBitbucketBuildStatusBody = async (
event: AwsLambda.CodeBuildCloudWatchStateEvent,
actionStatus: AwsLambda.CodeBuildStateType): BitbucketBuildStatus => {
actionStatus: AwsLambda.CodeBuildStateType): Promise<BitbucketBuildStatus> => {
const detail = event.detail;
const state =
actionStatus === 'IN_PROGRESS' ? 'INPROGRESS' :
Expand All @@ -19,7 +20,7 @@ export const buildBitbucketBuildStatusBody = (
return {
state,
key: `${detail['project-name']}-${buildId}`,
name: `CodeBuild-${detail['project-name']}`,
name: `CodeBuild-${detail['project-name']} (${await getCurrentAccountAlias(event.account)})`,
url: `https://${event.region}.console.aws.amazon.com/codesuite/codebuild/${event.account}/projects/${detail['project-name']}/build/${detail['project-name']}:${buildId}/?region=${event.region}`,
description: `${detail['project-name']} build initiated by ${detail['additional-information'].initiator} at ${detail['additional-information']['build-start-time']}`,
};
Expand All @@ -40,7 +41,7 @@ exports.handler = async (event: AwsLambda.CodeBuildCloudWatchStateEvent) => {
// console.log(JSON.stringify(event, undefined, 2));

try {
const status = buildBitbucketBuildStatusBody(event, event.detail['build-status']);
const status = await buildBitbucketBuildStatusBody(event, event.detail['build-status']);
const commitId = await getCommitId(event.detail['build-id']);

// Skip S3 events. See https://docs.aws.amazon.com/codebuild/latest/userguide/sample-source-version.html
Expand Down
12 changes: 7 additions & 5 deletions src/index.CodePipelineStatusHandler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as CodePipeline from '@aws-sdk/client-codepipeline';
import type * as AwsLambda from 'aws-lambda';
import { BitbucketBuildStatus, putCodePipelineResultToBitBucket } from './bitbucket';
import { getCurrentAccountAlias } from './iam-helper';

const codePipeline = new CodePipeline.CodePipelineClient({});

export const buildBitbucketBuildStatusBody = (

export const buildBitbucketBuildStatusBody = async (
event: AwsLambda.CodePipelineCloudWatchActionEvent,
actionStatus: CodePipeline.ActionExecutionStatus): BitbucketBuildStatus => {
actionStatus: CodePipeline.ActionExecutionStatus): Promise<BitbucketBuildStatus> => {
const detail = event.detail;
const state =
actionStatus === 'InProgress' ? 'INPROGRESS' :
Expand All @@ -18,7 +20,7 @@ export const buildBitbucketBuildStatusBody = (
return {
state,
key: `${detail.stage}-${detail.action}`,
name: `${detail.stage}-${detail.action}`,
name: `${detail.stage}-${detail.action} (${await getCurrentAccountAlias(event.account)})`,
url: `https://${event.region}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${detail.pipeline}/view`,
description: `${detail.stage}-${detail.action}`,
};
Expand All @@ -38,7 +40,7 @@ const fetchExecution = async (event: AwsLambda.CodePipelineCloudWatchActionEvent
return execution.pipelineExecution;
};

const getPipelineActionLatestStatus = async (event: AwsLambda.CodePipelineCloudWatchActionEvent): Promise<CodePipeline.ActionExecutionStatus> => {
export const getPipelineActionLatestStatus = async (event: AwsLambda.CodePipelineCloudWatchActionEvent) => {
const pipelineState = await codePipeline.send(new CodePipeline.GetPipelineStateCommand({
name: event.detail.pipeline,
}));
Expand All @@ -57,7 +59,7 @@ exports.handler = async (event: AwsLambda.CodePipelineCloudWatchActionEvent) =>

try {
const actionStatus = await getPipelineActionLatestStatus(event);
const status = buildBitbucketBuildStatusBody(event, actionStatus);
const status = await buildBitbucketBuildStatusBody(event, actionStatus);

const pipelineExecution = await fetchExecution(event);
const revisions = pipelineExecution.artifactRevisions ?? [{ revisionChangeIdentifier: '' }];
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ const addCodeBuildStateChangeEventRule = (scope: Construct, states: string[], ha
});
};

const listAliasesPolicy = new iam.PolicyStatement({
actions: ['iam:ListAccountAliases'],
resources: ['*'],
});

/** Common properties */
export interface CodePipelineBitbucketBuildResultReporterProps {
/**
Expand Down Expand Up @@ -74,6 +79,7 @@ export class CodePipelineBitbucketBuildResultReporter extends Construct {
logRetention: logs.RetentionDays.ONE_MONTH,
});
accessToken.grantRead(codePipelineStatusHandler);
codePipelineStatusHandler.role?.addToPrincipalPolicy(listAliasesPolicy);
codePipelineStatusHandler.role?.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['codepipeline:GetPipelineExecution', 'codepipeline:GetPipelineState'],
resources: ['arn:aws:codepipeline:*:*:*'],
Expand All @@ -95,6 +101,7 @@ export class CodePipelineBitbucketBuildResultReporter extends Construct {
logRetention: logs.RetentionDays.ONE_MONTH,
});
accessToken.grantRead(codeBuildStatusHandler);
codeBuildStatusHandler.role?.addToPrincipalPolicy(listAliasesPolicy);
codeBuildStatusHandler.role?.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['codebuild:BatchGetBuilds'],
resources: ['*'],
Expand Down
33 changes: 33 additions & 0 deletions test/bitbucket.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as SSM from '@aws-sdk/client-ssm';
import { mockClient } from 'aws-sdk-client-mock';
import '@aws-cdk/assert/jest';
import { getToken } from '../src/bitbucket';

const ssmMock = mockClient(SSM.SSMClient);

const paramName = 'EXAMPLE_TOKEN';
const exampleToken = 'frhWbyXaEPprfZRZXiuCUfmwMr6a0SAchp1JINHW7tXN21RZjF5jTcKaRr9kbm2';

test('getToken returns the token', async () => {
ssmMock.on(SSM.GetParameterCommand, {
Name: paramName,
}).resolves({
Parameter: {
Value: exampleToken,
},
});

expect(await getToken(paramName)).toBe(exampleToken);
});

test('getToken fails with incorrect token', async () => {
ssmMock.on(SSM.GetParameterCommand, {
Name: paramName,
}).resolves({
Parameter: {
Value: exampleToken,
},
});

await expect(getToken('invalid-token')).rejects.toThrow(/Cannot fetch token/);
});
30 changes: 30 additions & 0 deletions test/iam-helper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as IAM from '@aws-sdk/client-iam';
import { mockClient } from 'aws-sdk-client-mock';
import '@aws-cdk/assert/jest';
import { getCurrentAccountAlias } from '../src/iam-helper';

const iamMock = mockClient(IAM.IAMClient);

test('getCurrentAccountAlias returns account alias', async () => {
iamMock.on(IAM.ListAccountAliasesCommand).resolves({
AccountAliases: [
'account-alias',
],
});

expect(await getCurrentAccountAlias('0987654321')).toBe('account-alias');
});

test('getCurrentAccountAlias returns account id if no aliases found', async () => {
iamMock.on(IAM.ListAccountAliasesCommand).resolves({
AccountAliases: [],
});

expect(await getCurrentAccountAlias('0987654321')).toBe('0987654321');
});

test('getCurrentAccountAlias handles promise rejection', async () => {
iamMock.on(IAM.ListAccountAliasesCommand).rejects();

expect(await getCurrentAccountAlias('0987654321')).toBe('0987654321');
});
Loading

0 comments on commit e40469c

Please sign in to comment.