Skip to content

aws-codepipeline-actions: EcrSourceAction should trigger for all tags when imageTag is empty string #20594

@ahammond

Description

@ahammond

Describe the bug

Per aws-cdk documentation, https://github.com/aws/aws-cdk/blob/main/packages/%40aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts#L39 I should be able to provide an empty string to the EcrSourceAction to have it trigger on any push to any tag.

            new aws_codepipeline_actions.EcrSourceAction({
              actionName: 'Image',
              imageTag: '', // this doesn't work as documented. Instead it is just omitted.
              repository,
              output: imageInput,
            }),

However, when I write that, CDK omits the ImageTag from the rendered CloudFormation. When I force the issue with

    const pCfn = p.node.defaultChild as aws_codepipeline.CfnPipeline;
    // However, when we set this to an empty string, Cfn refuses to deploy because the value can't be an empty string.
    pCfn.addPropertyOverride('Stages.0.Actions.0.Configuration.ImageTag', '');

CloudFormation refuses to actually deploy with

1 validation error detected: Value at 'pipeline.stages.1.member.actions.1.member.configuration' failed to satisfy constraint:
Map value must satisfy constraint: [Member must have length less than or equal to 50000, Member must have length greater than or equal to 1]
(Service: AWSCodePipeline; Status Code: 400; Error Code: ValidationException; Request ID: 2ac8177d-d157-4d78-8632-48960d278613; Proxy: null)

When I went into the console and attempted to produce the desired behaviour (CodePipeline triggers on ECR pushes to any docker tag), it instead defaulted to latest. It does not appear to be possible to configure it to trigger for pushed to any docker tag.

ECR has immutable tags as a feature. We'd like to leverage that to ensure that the tags we are deploying can be deterministically mapped back to the code the were built with.

Expected Behavior

It should do what it says.

Current Behavior

Silently defaults to latest.

Reproduction Steps

I've already wasted 2 days on this false trail. Here's what I was using.

export class CouplerPipelineStack extends core.Stack {
  constructor(scope: Construct, id: Namer, props: CouplerPipelineStackProps) {
    super(scope, id.addSuffix(['coupler']), props);

    const imageInput = new aws_codepipeline.Artifact('Image');
    const updateInput = new aws_codepipeline.Artifact('ImageDefinitions');

    const repositoryName = id.addSuffix(['repository']).pascal;
    const repository = aws_ecr.Repository.fromRepositoryAttributes(this, 'Repository', {
      repositoryArn: core.Param.get(this, 'repositoryArn', {
        rootId: name.pascal,
        stackId: repositoryName,
        constructId: name.pascal,
      }),
      repositoryName: id.kebab,
    });

    const cluster = aws_ecs.Cluster.fromClusterAttributes(this, 'Cluster', {
      clusterName: id.kebab,
      securityGroups: [],
      vpc: this.vpc,
    });

    const service = aws_ecs.FargateService.fromFargateServiceAttributes(this, 'Service', {
      cluster,
      serviceName: id.pascal,
    });

    const p = new aws_codepipeline.Pipeline(this, id.pascal, {
      pipelineName: id.pascal,
      stages: [
        {
          stageName: 'Received',
          actions: [
            new aws_codepipeline_actions.EcrSourceAction({
              actionName: 'Image',
              imageTag: '', // this doesn't work as documented. Instead it is just omitted.
              repository,
              output: imageInput,
            }),
          ],
        },
        {
          stageName: 'GenerateImageDefinitions',
          actions: [
            new aws_codepipeline_actions.CodeBuildAction({
              environmentVariables: {
                ECR_REPO_URI: { value: `${this.account}.dkr.ecr.${this.region}.amazonaws.com/${id.pascal}` },
              },
              input: imageInput,
              outputs: [updateInput],
              actionName: 'GenerateImageDefinitions',
              project: new aws_codebuild.Project(this, 'GenerateImageDefinitions', {
                buildSpec: aws_codebuild.BuildSpec.fromObject({
                  version: '0.2',
                  phases: {
                    build: {
                      commands: [
                        'printenv',
                        //https://stackoverflow.com/a/57015190
                        `TAG=$(cat imageDetail.json | python -c "import sys, json; print(json.load(sys.stdin)['ImageTags'][0])")`,
                        'echo "${ECR_REPO_URI}:${TAG}"',
                        `printf '[{\"name\":\"${id.kebab}\",\"imageUri\":\"%s\"}]' $ECR_REPO_URI:$TAG > imagedefinitions.json`,
                        'pwd; ls -al; cat imagedefinitions.json',
                      ],
                    },
                  },
                  artifacts: { files: ['imagedefinitions.json'] },
                }),
              }),
            }),
          ],
        },
        {
          stageName: 'UpdateFargate',
          actions: [
            new aws_codepipeline_actions.EcsDeployAction({
              actionName: 'Deploy',
              input: updateInput,
              service,
            }),
          ],
        },
      ],
    });

    const pCfn = p.node.defaultChild as aws_codepipeline.CfnPipeline;
    // However, when we set this to an empty string, Cfn refuses to deploy because the value can't be an empty string.
    pCfn.addPropertyOverride('Stages.0.Actions.0.Configuration.ImageTag', '');
  }
}

Possible Solution

Initially, you should probably just update the comment to reflect the unfortunate reality of CodePipeline sucking.

Additional Information/Context

No response

CDK CLI Version

2.26.0 (build a409d63)

Framework Version

No response

Node.js Version

v16.13.1

OS

Mac

Language

Typescript

Language Version

4.7.2

Other information

No response

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions