Skip to content

Instantly share code, notes, and snippets.

@sisyphushappy
Created May 24, 2022 04:11
Show Gist options
  • Save sisyphushappy/ba38731cde9655c7db04c4711cce5458 to your computer and use it in GitHub Desktop.
Save sisyphushappy/ba38731cde9655c7db04c4711cce5458 to your computer and use it in GitHub Desktop.
Build a docker image asset, deploy it to ECR, and update a file in a remote GitHub repo with the docker image asset hash
import {Duration, Stack, StackProps} from "aws-cdk-lib";
import {Construct} from "constructs";
import {Repository} from "aws-cdk-lib/aws-ecr";
import {Function, LayerVersion, Runtime} from "aws-cdk-lib/aws-lambda";
import {ArnPrincipal, Effect, ManagedPolicy, PolicyStatement} from "aws-cdk-lib/aws-iam";
import {PythonFunction} from "@aws-cdk/aws-lambda-python-alpha";
import {DockerImageAsset} from "aws-cdk-lib/aws-ecr-assets";
import * as path from "path";
import * as ecrdeploy from 'cdk-ecr-deployment';
import {Secret} from "aws-cdk-lib/aws-secretsmanager";
import {AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId} from "aws-cdk-lib/custom-resources";
export class DockerStack extends Stack {
private ecrRepository: Repository;
private dockerImageAsset: DockerImageAsset;
private ecrDeployment: ecrdeploy.ECRDeployment;
private updateGitRepoTagFileFunction : Function;
private deploymentTriggerFunction: AwsCustomResource;
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// create an image repository
this.createImageRepository();
// create a docker image asset
this.createDockerImageAsset();
// copy the docker image asset to ecr
this.createEcrDeployment();
// create a python lambda that clones a target Github repo, updates a file
// with the hash of the docker image asset, and pushes back to the remote
this.createPythonFunction();
// trigger the python lambda
this.createTriggerFunction();
}
createImageRepository() {
// Create the repository
this.ecrRepository = new Repository(this, 'ExampleImageRepository', {
repositoryName: 'exaample-image-asset',
lifecycleRules: [{
description: "Keeps a maximum number of images to minimize storage",
maxImageCount: 10
}]
});
const exampleAccountPrincipal = new ArnPrincipal(`arn:aws:iam::<example-aws-account>:root`);
this.ecrRepository.addToResourcePolicy(new PolicyStatement({
sid: "AllowCrossAccountPull",
effect: Effect.ALLOW,
actions: [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage"
],
principals: [ exampleAccountPrincipal ]
}))
this.ecrRepository.grantPull(exampleAccountPrincipal);
}
createDockerImageAsset() {
this.dockerImageAsset = new DockerImageAsset(this, 'DockerImageAsset', {
directory: path.join(__dirname, '../example-app-source-root'),
buildArgs: {},
invalidation: {
buildArgs: false,
},
});
}
createEcrDeployment() {
const dockerImageAssetHash = this.dockerImageAsset.assetHash;
const destinationImageName = `${this.ecrRepository.repositoryUri}:${dockerImageAssetHash}`;
this.ecrDeployment = new ecrdeploy.ECRDeployment(this, 'EcrDeployment', {
src: new ecrdeploy.DockerImageName(this.dockerImageAsset.imageUri),
dest: new ecrdeploy.DockerImageName(destinationImageName),
});
}
createPythonFunction() {
const githubAccessTokenSecret = Secret.fromSecretNameV2(this, 'GithubAccessTokenSecret', 'github-token-id');
this.updateGitRepoTagFileFunction = new PythonFunction(this, 'UpdateTagFileFunction', {
entry: path.join(__dirname, '..', 'resources', 'update_github_remote_tag_file_handler'),
runtime: Runtime.PYTHON_3_9,
environment: {
// required environment variables for python script
'AUTHOR_NAME': 'Alice Smith',
'AUTHOR_EMAIL': '[email protected]',
'TAG_FILE_NAME': 'EXAMPLE_TAG_FILE_NAME',
'TAG_VALUE': this.dockerImageAsset.assetHash,
'GITHUB_ACCOUNT_NAME': 'example-account-name',
'GITHUB_REPO_NAME': 'example-cdk-pipeline',
'GITHUB_USERNAME': 'example-username',
'GITHUB_ACCESS_TOKEN_SECRET_ARN': githubAccessTokenSecret.secretArn,
'REGION': 'us-east-1'
},
timeout: Duration.minutes(15),
// lambda layer adds git to path (see: https://github.com/lambci/git-lambda-layer)
layers: [ LayerVersion.fromLayerVersionArn(this, 'GitLambdaLayer', 'arn:aws:lambda:us-east-2:553035198032:layer:git-lambda2:8') ],
});
this.updateGitRepoTagFileFunction.role!.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("SecretsManagerReadWrite"));
}
createTriggerFunction() {
this.deploymentTriggerFunction = new AwsCustomResource(this, 'DeploymentTriggerFunctoin', {
policy: AwsCustomResourcePolicy.fromStatements([new PolicyStatement({
actions: ['lambda:InvokeFunction'],
effect: Effect.ALLOW,
resources: [this.updateGitRepoTagFileFunction.functionArn]
})]),
timeout: Duration.minutes(15),
onCreate: {
service: 'Lambda',
action: 'invoke',
parameters: {
FunctionName: this.updateGitRepoTagFileFunction.functionName,
InvocationType: 'Event'
},
physicalResourceId: PhysicalResourceId.of(Date.now().toString()),
},
onUpdate: {
service: 'Lambda',
action: 'invoke',
parameters: {
FunctionName: this.updateGitRepoTagFileFunction.functionName,
InvocationType: 'Event'
},
physicalResourceId: PhysicalResourceId.of(Date.now().toString()),
}
});
deploymentTriggerFunction.node.addDependency(this.ecrDeployment);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment