Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initiate CDK Pipeline #92

Merged
merged 21 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ cdk.context.json
data/

Brewfile.lock.json
*.xml

target/
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ baseline:
test:
@yarn test

# Test only section
test-stateful:
@yarn run test ./test/stateful
test-stateless:
@yarn run test ./test/stateless

# Run all test suites - i.e. cdk app unit tests + each microservice app test suites
# Each app root should have Makefile `test` target; that run your app test pipeline including compose stack up/down
# Note by running `make suite` target from repo root means your local dev env is okay with all app toolchains i.e.
# Python (conda or venv), Rust and Cargo, TypeScript and Node environment, Docker and Container runtimes
suite: test
suite: test-stateless
@(cd lib/workload/stateless/sequence_run_manager && $(MAKE) test)
@(cd lib/workload/stateless/metadata_manager && $(MAKE) test)
@#(cd lib/workload/stateless/filemanager && $(MAKE) test) # FIXME uncomment when ready @Marko
Expand Down
4 changes: 2 additions & 2 deletions bin/orcabus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { getEnvironmentConfig } from '../config/constants';
const app = new cdk.App();
const props: cdk.StackProps = {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
account: process.env.CDK_DEPLOY_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEPLOY_REGION || process.env.CDK_DEFAULT_REGION,
},
tags: {
'umccr-org:Stack': 'OrcaBusSandboxApp',
Expand Down
6 changes: 3 additions & 3 deletions bin/pipeline.ts → bin/stateful-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
import 'source-map-support/register';

import * as cdk from 'aws-cdk-lib';
import { PipelineStack } from '../lib/pipeline/orcabus-pipeline-stack';
import { StatefulPipelineStack } from '../lib/pipeline/orcabus-stateful-pipeline-stack';

const AWS_TOOLCHAIN_ACCOUNT = '383856791668'; // Bastion
const AWS_TOOLCHAIN_REGION = 'ap-southeast-2';

const app = new cdk.App();

new PipelineStack(app, `OrcaBusPipeline`, {
new StatefulPipelineStack(app, `OrcaBusStatefulPipeline`, {
env: {
account: AWS_TOOLCHAIN_ACCOUNT,
region: AWS_TOOLCHAIN_REGION,
},
tags: {
'umccr-org:Stack': 'OrcaBusPipelineApp',
'umccr-org:Stack': 'OrcaBusStatefulPipelineApp',
'umccr-org:Product': 'OrcaBus',
},
});
21 changes: 21 additions & 0 deletions bin/stateless-pipeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env node
import 'source-map-support/register';

import * as cdk from 'aws-cdk-lib';
import { StatelessPipelineStack } from '../lib/pipeline/orcabus-stateless-pipeline-stack';

const AWS_TOOLCHAIN_ACCOUNT = '383856791668'; // Bastion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the Bastion account?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We opt to CICD within the CodePipeline. So, the GHA PR build will only do pre-flight checks - lint, format, secret-scan, dependencies audits.

Choosing the Bastion account because we still opt with our AWS accounts. There, Bastion account has been repurposed as build/toolchain account.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed; will reenable GHA PR Build with #100

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also will tear down and re-deploy into UoM AWS accounts with #102

const AWS_TOOLCHAIN_REGION = 'ap-southeast-2';

const app = new cdk.App();

new StatelessPipelineStack(app, `OrcaBusStatelessPipeline`, {
env: {
account: AWS_TOOLCHAIN_ACCOUNT,
region: AWS_TOOLCHAIN_REGION,
},
tags: {
'umccr-org:Stack': 'OrcaBusStatelessPipeline',
'umccr-org:Product': 'OrcaBus',
},
});
2 changes: 1 addition & 1 deletion cdk.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"app": "npx ts-node --prefer-ts-exts bin/pipeline.ts",
"app": "yarn run ts-node --prefer-ts-exts bin/orcabus.ts",
williamputraintan marked this conversation as resolved.
Show resolved Hide resolved
"watch": {
"include": [
"**"
Expand Down
7 changes: 5 additions & 2 deletions config/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { OrcaBusStatefulConfig } from '../lib/workload/orcabus-stateful-stack';
import { AuroraPostgresEngineVersion } from 'aws-cdk-lib/aws-rds';
import { OrcaBusStatelessConfig } from '../lib/workload/orcabus-stateless-stack';
import { Duration, aws_lambda } from 'aws-cdk-lib';
import { Duration, aws_lambda, RemovalPolicy } from 'aws-cdk-lib';

const regName = 'OrcaBusSchemaRegistry';
const eventBusName = 'OrcaBusMain';
Expand All @@ -24,7 +24,7 @@ const orcaBusStatefulConfig = {
defaultDatabaseName: 'orcabus',
version: AuroraPostgresEngineVersion.VER_15_4,
parameterGroupName: 'default.aurora-postgresql15',
username: 'admin',
username: 'postgres',
dbPort: 5432,
masterSecretName: rdsMasterSecretName,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to create per app/service db secret manager record per database to isolate them better. Instead of reusing this master (SA account). Wonder, we can do this arrangement straight up now at this point... By chance, do you happen to figure any solution to it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we should isolate on a per service level, but that means the stateless stack needs to be aware of the consuming services, right?
And any database required by any service would have to be registered to and created by the stateless stack IIRC. The stateless stack can create "user" credentials for each service, but I think they'd need to be linked to existing DBs? I don't have the understanding yet of what's possible...

I think the initial thinking here was to allow access on the cluster level to start with in order to not block any service development. Once we've figured out a better way, we could refactor.

Or do you think it's worth working that out now and safe on the refactor ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend we should work this out now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree in working this out. Could be wrong, but it doesn't look like CDK can create more than one database/user per cluster using the regular higher level constructs (see issue aws/aws-cdk#13588)? Might have to create a migration-style Lambda function which executes create database ... after the RDS cluster is up and running, e.g. using something like this. Then, I think each service would need to register their preferred database name, user and credentials somewhere (in constants.ts?).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, each service could register their own migration function to create the databases/users? That way the databases wouldn't have to be registered somewhere higher up like in constants.ts. Not sure if this makes more sense?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I wonder if the stateless stack could create IAM roles/policies for each service that would allow these services access to only their specific DB(s) on the instance. E.g. passing on the permissions to do what the service needs to do (without control/knowledge of what that may be), as long as it happens in the designated DB.

I haven't found any examples, but it looks like you can create IAM policies with conditions to restrict access by rds:DatabaseName.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will track this at issue #99

monitoring: {
Expand Down Expand Up @@ -94,6 +94,7 @@ export const getEnvironmentConfig = (
maxACU: 1,
williamputraintan marked this conversation as resolved.
Show resolved Hide resolved
enhancedMonitoringInterval: Duration.seconds(60),
enablePerformanceInsights: true,
removalPolicy: RemovalPolicy.DESTROY,
},
securityGroupProps: {
...orcaBusStatefulConfig.securityGroupProps,
Expand Down Expand Up @@ -123,6 +124,7 @@ export const getEnvironmentConfig = (
maxACU: 1,
enhancedMonitoringInterval: Duration.seconds(60),
enablePerformanceInsights: true,
removalPolicy: RemovalPolicy.DESTROY,
},
securityGroupProps: {
...orcaBusStatefulConfig.securityGroupProps,
Expand Down Expand Up @@ -150,6 +152,7 @@ export const getEnvironmentConfig = (
numberOfInstance: 1,
minACU: 0.5,
maxACU: 1,
removalPolicy: RemovalPolicy.RETAIN,
},
securityGroupProps: {
...orcaBusStatefulConfig.securityGroupProps,
Expand Down
10 changes: 10 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,14 @@ module.exports = {
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
reporters: [
'default',
[
'jest-junit',
{
outputDirectory: 'target/report',
outputName: 'infrastructureTest.xml',
},
],
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import * as cdk from 'aws-cdk-lib';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import * as pipelines from 'aws-cdk-lib/pipelines';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import { OrcaBusStatelessConfig, OrcaBusStatelessStack } from '../workload/orcabus-stateless-stack';
import * as iam from 'aws-cdk-lib/aws-iam';
import { OrcaBusStatefulConfig, OrcaBusStatefulStack } from '../workload/orcabus-stateful-stack';
import { getEnvironmentConfig } from '../../config/constants';
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';

export class PipelineStack extends cdk.Stack {
export class StatefulPipelineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);

Expand All @@ -18,34 +17,61 @@ export class PipelineStack extends cdk.Stack {
connectionArn: codeStarArn,
});

const synthAction = new pipelines.CodeBuildStep('Synth', {
const unitTest = new pipelines.CodeBuildStep('UnitTest', {
commands: ['yarn install --frozen-lockfile', 'make test-stateful'],
input: sourceFile,
commands: ['yarn install --frozen-lockfile', 'yarn cdk synth -v'],
primaryOutputDirectory: '.',
buildEnvironment: {
privileged: true,
computeType: codebuild.ComputeType.LARGE,
buildImage: codebuild.LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0,
environmentVariables: {
NODE_OPTIONS: {
value: '--max-old-space-size=8192',
},
},
},
partialBuildSpec: codebuild.BuildSpec.fromObject({
'orcabus-infrastructureStatefulReports': {
infrastructureStatefulReports: {
files: ['target/report/*.xml'],
'file-format': 'JUNITXML',
},
},
version: '0.2',
}),
});

const synthAction = new pipelines.CodeBuildStep('Synth', {
commands: ['yarn install --frozen-lockfile', 'yarn run cdk-stateful-pipeline synth'],
input: unitTest,
primaryOutputDirectory: 'cdk.out',
rolePolicyStatements: [
new PolicyStatement({
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['sts:AssumeRole'],
resources: ['*'],
conditions: {
StringEquals: {
'iam:ResourceTag/aws-cdk:bootstrap-role': 'lookup',
},
},
}),
],
});
synthAction.addStepDependency(new OrcaBusTestStep('OrcaBusUnitTest', { source: sourceFile }));

const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
synth: synthAction,
selfMutation: true,
crossAccountKeys: true,
dockerEnabledForSynth: true,
dockerEnabledForSelfMutation: true,
codeBuildDefaults: {
buildEnvironment: {
buildImage: codebuild.LinuxBuildImage.STANDARD_6_0,
computeType: codebuild.ComputeType.LARGE,
buildImage: codebuild.LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0,
environmentVariables: {
NODE_OPTIONS: {
value: '--max-old-space-size=8192',
},
},
},
},
dockerEnabledForSelfMutation: true,
});

/**
Expand All @@ -54,7 +80,7 @@ export class PipelineStack extends cdk.Stack {
const betaConfig = getEnvironmentConfig('beta');
if (!betaConfig) throw new Error(`No 'Beta' account configuration`);
pipeline.addStage(
new OrcaBusDeploymentStage(this, 'BetaDeployment', betaConfig.stackProps, {
new OrcaBusStatefulDeploymentStage(this, 'BetaDeployment', betaConfig.stackProps, {
account: betaConfig.accountId,
})
);
Expand All @@ -65,7 +91,7 @@ export class PipelineStack extends cdk.Stack {
const gammaConfig = getEnvironmentConfig('gamma');
if (!gammaConfig) throw new Error(`No 'Gamma' account configuration`);
pipeline.addStage(
new OrcaBusDeploymentStage(this, 'GammaDeployment', gammaConfig.stackProps, {
new OrcaBusStatefulDeploymentStage(this, 'GammaDeployment', gammaConfig.stackProps, {
account: gammaConfig.accountId,
}),
{ pre: [new pipelines.ManualApprovalStep('PromoteToGamma')] }
Expand All @@ -74,65 +100,28 @@ export class PipelineStack extends cdk.Stack {
/**
* Deployment to Prod account (DISABLED)
*/
// const prodConfig = getEnvironmentConfig('prod');
// if (!prodConfig) throw new Error(`No 'Prod' account configuration`);
// pipeline.addStage(
// new OrcaBusDeploymentStage(this, 'prodDeployment', prodConfig.stackProps, {
// account: gammaConfig?.accountId,
// }),
// { pre: [new pipelines.ManualApprovalStep('PromoteToProd')] }
// );
const prodConfig = getEnvironmentConfig('prod');
if (!prodConfig) throw new Error(`No 'Prod' account configuration`);
pipeline.addStage(
new OrcaBusStatefulDeploymentStage(this, 'prodDeployment', prodConfig.stackProps, {
account: gammaConfig?.accountId,
}),
{ pre: [new pipelines.ManualApprovalStep('PromoteToProd')] }
);
}
}

class OrcaBusDeploymentStage extends cdk.Stage {
class OrcaBusStatefulDeploymentStage extends cdk.Stage {
constructor(
scope: Construct,
environmentName: string,
stackProps: {
orcaBusStatefulConfig: OrcaBusStatefulConfig;
orcaBusStatelessConfig: OrcaBusStatelessConfig;
},
env?: cdk.Environment
) {
super(scope, environmentName, { env: { account: env?.account, region: 'ap-southeast-2' } });

new OrcaBusStatefulStack(this, 'OrcaBusStatefulStack', stackProps.orcaBusStatefulConfig);
new OrcaBusStatelessStack(this, 'OrcaBusStatelessStack', stackProps.orcaBusStatelessConfig);
}
}

interface OrcaBusTestStepProps {
source: pipelines.CodePipelineSource;
}
class OrcaBusTestStep extends pipelines.CodeBuildStep {
constructor(id: string, props: OrcaBusTestStepProps) {
const stepProps: pipelines.CodeBuildStepProps = {
input: props.source,
commands: [],
partialBuildSpec: codebuild.BuildSpec.fromObject({
env: {
shell: 'bash',
},
phases: {
install: {
commands: [
'wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-py310_23.3.1-0-Linux-x86_64.sh',
'bash Miniconda3-py310_23.3.1-0-Linux-x86_64.sh -b',
'export PATH=/root/miniconda3/bin:$PATH',
'conda init bash',
'source activate',
'conda create -q -n orcabus python=3.10',
'conda run -n orcabus yarn install',
],
},
build: {
commands: ['conda run -n orcabus make test'],
},
},
version: '0.2',
}),
};
super(id, stepProps);
}
}
Loading
Loading