Skip to content
This repository was archived by the owner on Jun 28, 2022. It is now read-only.

Commit

Permalink
feat: add repository image updater (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
arantespp authored Mar 9, 2022
1 parent 5e1e1a2 commit cbcbdea
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 166 deletions.
176 changes: 86 additions & 90 deletions packages/cli/src/deploy/cicd/cicd.template.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,113 +15,109 @@ import {
PIPELINES_ROLE_LOGICAL_ID,
PIPELINES_MAIN_LOGICAL_ID,
PIPELINES_TAG_LOGICAL_ID,
IMAGE_UPDATER_SCHEDULE_SERVERLESS_FUNCTION_LOGICAL_ID,
getCicdTemplate,
} from './cicd.template';

describe('testing CICD template', () => {
test('should have serverless API', () => {
const template = getCicdTemplate({
pipelines: ['main'],
s3: {
bucket: 'bucket',
key: 'key',
versionId: 'versionId',
},
});
const s3 = {
bucket: 'bucket',
key: 'key',
versionId: 'versionId',
};

expect(template.Resources).toHaveProperty(API_LOGICAL_ID);
expect(template.Resources[API_LOGICAL_ID].Type).toEqual(
'AWS::Serverless::Api',
);
expect(
template.Resources.GitHubWebhooksApiV1ServerlessFunction.Properties
.Environment.Variables.TRIGGER_PIPELINES_OBJECT_KEY_PREFIX,
).toEqual(`cicd/pipelines/triggers/${projectName}`);
});
});
test('should have image updater schedule serverless function', () => {
const template = getCicdTemplate({ s3 });

const resource =
template.Resources[IMAGE_UPDATER_SCHEDULE_SERVERLESS_FUNCTION_LOGICAL_ID];

describe('pipeline resources', () => {
test.each<[Pipeline[]]>([[[]], [['pr']]])(
"don't create pipeline resources: %s",
(pipelines) => {
const template = getCicdTemplate({
pipelines,
s3: {
bucket: 'bucket',
key: 'key',
versionId: 'versionId',
},
});

expect(
template.Resources[PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID],
).toBeUndefined();

expect(
template.Resources[PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID],
).toBeUndefined();

expect(template.Resources[PIPELINES_ROLE_LOGICAL_ID]).toBeUndefined();
},
expect(resource).toBeDefined();

expect(resource.Properties.Handler).toEqual(
'index.imageUpdaterScheduleHandler',
);
});

test('should have serverless API', () => {
const template = getCicdTemplate({
pipelines: ['main'],
s3,
});

test.each<[Pipeline[]]>([[['main']], [['tag', 'main']], [['tag']]])(
'create pipeline resources: %s',
(pipelines) => {
const template = getCicdTemplate({
pipelines,
s3: {
bucket: 'bucket',
key: 'key',
versionId: 'versionId',
},
});

expect(
template.Resources[PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID],
).toBeDefined();

expect(
template.Resources[PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID],
).toBeDefined();

expect(template.Resources[PIPELINES_ROLE_LOGICAL_ID]).toBeDefined();
},
expect(template.Resources).toHaveProperty(API_LOGICAL_ID);
expect(template.Resources[API_LOGICAL_ID].Type).toEqual(
'AWS::Serverless::Api',
);
expect(
template.Resources.GitHubWebhooksApiV1ServerlessFunction.Properties
.Environment.Variables.TRIGGER_PIPELINES_OBJECT_KEY_PREFIX,
).toEqual(`cicd/pipelines/triggers/${projectName}`);
});

test.each<[Pipeline[]]>([
[['main']],
[['main', 'pr']],
[['main', 'tag']],
[['main', 'tag', 'pr']],
])('create main pipeline resources: %s', (pipelines) => {
test.each<[Pipeline[]]>([[[]], [['pr']]])(
"don't create pipeline resources: %s",
(pipelines) => {
const template = getCicdTemplate({
pipelines,
s3: {
bucket: 'bucket',
key: 'key',
versionId: 'versionId',
},
s3,
});

expect(template.Resources[PIPELINES_MAIN_LOGICAL_ID]).toBeDefined();
});
expect(
template.Resources[PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID],
).toBeUndefined();

expect(
template.Resources[PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID],
).toBeUndefined();

test.each<[Pipeline[]]>([
[['tag']],
[['tag', 'pr']],
[['tag', 'main']],
[['tag', 'main', 'pr']],
])('create tag pipeline resources: %s', (pipelines) => {
expect(template.Resources[PIPELINES_ROLE_LOGICAL_ID]).toBeUndefined();
},
);

test.each<[Pipeline[]]>([[['main']], [['tag', 'main']], [['tag']]])(
'create pipeline resources: %s',
(pipelines) => {
const template = getCicdTemplate({
pipelines,
s3: {
bucket: 'bucket',
key: 'key',
versionId: 'versionId',
},
s3,
});

expect(template.Resources[PIPELINES_TAG_LOGICAL_ID]).toBeDefined();
expect(
template.Resources[PIPELINES_ARTIFACT_STORE_S3_BUCKET_LOGICAL_ID],
).toBeDefined();

expect(
template.Resources[PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID],
).toBeDefined();

expect(template.Resources[PIPELINES_ROLE_LOGICAL_ID]).toBeDefined();
},
);

test.each<[Pipeline[]]>([
[['main']],
[['main', 'pr']],
[['main', 'tag']],
[['main', 'tag', 'pr']],
])('create main pipeline resources: %s', (pipelines) => {
const template = getCicdTemplate({
pipelines,
s3,
});

expect(template.Resources[PIPELINES_MAIN_LOGICAL_ID]).toBeDefined();
});

test.each<[Pipeline[]]>([
[['tag']],
[['tag', 'pr']],
[['tag', 'main']],
[['tag', 'main', 'pr']],
])('create tag pipeline resources: %s', (pipelines) => {
const template = getCicdTemplate({
pipelines,
s3,
});

expect(template.Resources[PIPELINES_TAG_LOGICAL_ID]).toBeDefined();
});
63 changes: 50 additions & 13 deletions packages/cli/src/deploy/cicd/cicd.template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
CloudFormationTemplate,
getIamPath,
getProjectName,
getEnvironment,
} from '../../utils';

import {
Expand All @@ -15,6 +16,8 @@ import {
BASE_STACK_VPC_PUBLIC_SUBNET_2_EXPORTED_NAME,
} from '../baseStack/config';

import { getCicdConfig, CicdCommandOptions } from './command.options';

import type { Pipeline } from './pipelines';
import {
ECS_TASK_DEFAULT_CPU,
Expand Down Expand Up @@ -76,6 +79,9 @@ export const PIPELINES_TAG_LOGICAL_ID = 'PipelinesTagCodePipeline';
export const PIPELINES_HANDLER_LAMBDA_FUNCTION_LOGICAL_ID =
'PipelinesHandlerLambdaFunction';

export const IMAGE_UPDATER_SCHEDULE_SERVERLESS_FUNCTION_LOGICAL_ID =
'ImageUpdaterScheduleServerlessFunction';

/**
* An [AWS CodeBuild](https://aws.amazon.com/codebuild/) project is created
* to build (create and update) repository images. It uses a
Expand Down Expand Up @@ -340,6 +346,19 @@ export const getCicdTemplate = ({

resources[ECR_REPOSITORY_LOGICAL_ID] = getEcrRepositoryResource();

const commonFunctionProperties = {
CodeUri: {
Bucket: s3.bucket,
Key: s3.key,
Version: s3.versionId,
},
Role: {
'Fn::GetAtt': [FUNCTION_IAM_ROLE_LOGICAL_ID, 'Arn'],
},
Runtime: 'nodejs14.x',
Timeout: 60,
};

/**
* CodeBuild
*/
Expand Down Expand Up @@ -404,6 +423,37 @@ export const getCicdTemplate = ({

resources[REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID] =
getRepositoryImageBuilder();

const cicdConfig: CicdCommandOptions & { environment: any } = {
...getCicdConfig(),
'ssh-key': '/root/.ssh/id_rsa',
environment: getEnvironment(),
};

resources[IMAGE_UPDATER_SCHEDULE_SERVERLESS_FUNCTION_LOGICAL_ID] = {
Type: 'AWS::Serverless::Function',
Properties: {
...commonFunctionProperties,
Events: {
Schedule: {
Type: 'Schedule',
Properties: {
Schedule: 'rate(7 days)',
},
},
},
Environment: {
Variables: {
[PROCESS_ENV_REPOSITORY_IMAGE_CODE_BUILD_PROJECT_NAME]: {
Ref: REPOSITORY_IMAGE_CODE_BUILD_PROJECT_LOGICAL_ID,
},
CICD_CONFIG: JSON.stringify(cicdConfig),
...executeEcsTaskVariables,
},
},
Handler: 'index.imageUpdaterScheduleHandler',
},
};
})();

const createApiResources = () => {
Expand Down Expand Up @@ -517,19 +567,6 @@ export const getCicdTemplate = ({
},
};

const commonFunctionProperties = {
CodeUri: {
Bucket: s3.bucket,
Key: s3.key,
Version: s3.versionId,
},
Role: {
'Fn::GetAtt': [FUNCTION_IAM_ROLE_LOGICAL_ID, 'Arn'],
},
Runtime: 'nodejs14.x',
Timeout: 60,
};

/**
* Called after ECS task execution success or failure.
*/
Expand Down
81 changes: 81 additions & 0 deletions packages/cli/src/deploy/cicd/command.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* eslint-disable no-param-reassign */
import { camelCase } from 'change-case';
import yargs from 'yargs';

import { pipelines } from './pipelines';

export const options = {
cpu: {
type: 'string',
},
memory: {
type: 'string',
},
pipelines: {
choices: pipelines,
coerce: (values: string[]) => values.map((value) => camelCase(value)),
default: [],
description: 'Pipelines that will be implemented with the CICD stack.',
type: 'array',
},
'update-repository': {
alias: ['ur'],
description: 'Determine if the repository image will be updated.',
default: true,
type: 'boolean',
},
'ssh-key': {
demandOption: true,
type: 'string',
},
'ssh-url': {
demandOption: true,
type: 'string',
},
'slack-webhook-url': {
type: 'string',
},
/**
* This option has the format:
*
* ```ts
* Array<{
* name: string,
* value: string,
* }>
* ```
*/
'task-environment': {
alias: ['te'],
default: [],
describe:
'A list of environment variables that will be passed to the ECS container task.',
type: 'array',
},
} as const;

export type CicdCommandOptions = Partial<{
[key in keyof typeof options]: any;
}>;

export const getCicdConfig = () => {
const { parsed } = yargs.config();

if (!parsed) {
return false;
}

const { argv } = parsed;

const config: CicdCommandOptions = Object.keys(options).reduce((acc, key) => {
const value = argv[key];

if (value) {
acc[key] = value;
}

return acc;
}, {} as any);

return config;
};
Loading

0 comments on commit cbcbdea

Please sign in to comment.