diff --git a/demos-pipeline/example/bin/poky-pipeline.ts b/demos-pipeline/example/bin/poky-pipeline.ts index b0fd1d1..5eacc44 100644 --- a/demos-pipeline/example/bin/poky-pipeline.ts +++ b/demos-pipeline/example/bin/poky-pipeline.ts @@ -50,7 +50,7 @@ new BuildImagePipelineStack(app, "BuildImagePipeline", { /** * Set up networking to allow us to securely attach EFS to our CodeBuild instances. */ -const vpc = new PipelineNetworkStack(app, { +const vpc = new PipelineNetworkStack(app, { ...defaultProps, }); diff --git a/demos-pipeline/lib/lib/demo-pipeline.ts b/demos-pipeline/lib/lib/demo-pipeline.ts index 6978f27..b7766a4 100644 --- a/demos-pipeline/lib/lib/demo-pipeline.ts +++ b/demos-pipeline/lib/lib/demo-pipeline.ts @@ -2,6 +2,12 @@ import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; import * as codepipeline from "aws-cdk-lib/aws-codepipeline"; import * as codepipeline_actions from "aws-cdk-lib/aws-codepipeline-actions"; +import * as events from "aws-cdk-lib/aws-events"; +import * as targets from "aws-cdk-lib/aws-events-targets"; +import * as lambda from "aws-cdk-lib/aws-lambda"; +import * as iam from "aws-cdk-lib/aws-iam"; +import * as efs from "aws-cdk-lib/aws-efs"; + import { BuildSpec, ComputeType, @@ -10,7 +16,7 @@ import { PipelineProject, } from "aws-cdk-lib/aws-codebuild"; import { IRepository } from "aws-cdk-lib/aws-ecr"; -import * as efs from "aws-cdk-lib/aws-efs"; + import { ISecurityGroup, IVpc, @@ -20,6 +26,7 @@ import { } from "aws-cdk-lib/aws-ec2"; import { Bucket } from "aws-cdk-lib/aws-s3"; import { SourceRepo, DistributionKind } from "./constructs/source-repo"; +import { CodeCommitTrigger } from "aws-cdk-lib/aws-codepipeline-actions"; /** * Properties to allow customizing the build. @@ -70,6 +77,7 @@ export class DemoPipelineStack extends cdk.Stack { const sourceOutput = new codepipeline.Artifact(); const sourceAction = new codepipeline_actions.CodeCommitSourceAction({ + // trigger: CodeCommitTrigger.NONE, output: sourceOutput, actionName: "Source", repository: sourceRepo.repo, @@ -121,15 +129,62 @@ export class DemoPipelineStack extends cdk.Stack { versioned: true, enforceSSL: true, }); - const artifactAction = new codepipeline_actions.S3DeployAction({ actionName: "Demo-Artifact", input: buildOutput, bucket: artifactBucket, }); + /** Here we create the logic to check for presence of ECR image on the CodePipeline automatic triggering upon resource creation, + * and stop the execution if the image does not exist. */ + const fnOnPipelineCreate = new lambda.Function( + this, + "OSImageCheckOnStart", + { + runtime: lambda.Runtime.PYTHON_3_10, + handler: "index.handler", + code: lambda.Code.fromInline(` +import boto3 +import json + +ecr_client = boto3.client('ecr') +codepipeline_client = boto3.client('codepipeline') + +def handler(event, context): + print("Received event: " + json.dumps(event, indent=2)) + response = ecr_client.describe_images(repositoryName='${props.imageRepo.repositoryName}', filter={'tagStatus': 'TAGGED'}) + for i in response['imageDetails']: + if '${props.imageTag}' in i['imageTags']: + break + else: + print('OS image not found. Stopping execution.') + response = codepipeline_client.stop_pipeline_execution( + pipelineName=event['detail']['pipeline'], + pipelineExecutionId=event['detail']['execution-id'], + abandon=True, + reason='OS image not found in ECR repository. Stopping pipeline until image is present.') + `), + } + ); + + const pipelineCreateRule = new events.Rule(this, "OnPipelineStartRule", { + eventPattern: { + detailType: ["CodePipeline Pipeline Execution State Change"], + source: ["aws.codepipeline"], + detail: { + state: ["STARTED"], + "execution-trigger": { + "trigger-type": ["CreatePipeline"], + }, + }, + }, + }); + pipelineCreateRule.addTarget( + new targets.LambdaFunction(fnOnPipelineCreate) + ); + /** Now create the actual Pipeline */ - new codepipeline.Pipeline(this, "DemoPipeline", { + const pipeline = new codepipeline.Pipeline(this, "DemoPipeline", { restartExecutionOnUpdate: true, stages: [ { @@ -146,6 +201,21 @@ export class DemoPipelineStack extends cdk.Stack { }, ], }); + + const stopPipelinePolicy = new iam.PolicyStatement({ + actions: ["codepipeline:StopPipelineExecution"], + resources: [pipeline.pipelineArn], + }); + + const ecrPolicy = new iam.PolicyStatement({ + actions: ["ecr:DescribeImages"], + resources: [props.imageRepo.repositoryArn], + }); + fnOnPipelineCreate.role?.attachInlinePolicy( + new iam.Policy(this, "CheckOSAndStop", { + statements: [stopPipelinePolicy, ecrPolicy], + }) + ); } /** diff --git a/demos-pipeline/lib/lib/network.ts b/demos-pipeline/lib/lib/network.ts index c28b2d4..87000b7 100644 --- a/demos-pipeline/lib/lib/network.ts +++ b/demos-pipeline/lib/lib/network.ts @@ -10,7 +10,7 @@ export class PipelineNetworkStack extends cdk.Stack { public readonly vpc: ec2.IVpc; constructor(scope: Construct, props?: cdk.StackProps) { - super(scope, 'PipelineNetwork', props); + super(scope, "PipelineNetwork", props); // We will create a VPC with 3 Private and Public subnets for AWS // Resources that have network interfaces (e.g. Connecting and EFS