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

aws-codepipeline-actions: CodeStarConnectionsSourceAction.variables inaccurate #31000

Open
dleavitt opened this issue Aug 1, 2024 · 1 comment
Labels
@aws-cdk/aws-codepipeline-actions bug This issue is a bug. effort/medium Medium work item – several days of effort p2

Comments

@dleavitt
Copy link

dleavitt commented Aug 1, 2024

Describe the bug

If you try to use CodeStarConnectionsSourceAction.variables.branchName on an execution triggered by a pull request, your build will fail with:

An action in this pipeline failed because one or more variables could not be resolved: Action name=XYZ. This can result when a variable is referenced that does not exist. Validate the configuration for this action.

Detail

#5604 added a .variables getter to a number of codepipeline.Action subclasses including CodeStarConnectionsSourceAction. It's hardcoded to return a particular set of variables, and seems to be the only public way to get at the action's variables.

public get variables(): CodeStarSourceVariables {
return {
fullRepositoryName: this.variableExpression('FullRepositoryName'),
branchName: this.variableExpression('BranchName'),
authorDate: this.variableExpression('AuthorDate'),
commitId: this.variableExpression('CommitId'),
commitMessage: this.variableExpression('CommitMessage'),
connectionArn: this.variableExpression('ConnectionArn'),
};
}

These variables are correct in some cases, but aren't correct if the execution was triggered by a pull request (see screenshots below.)

With a pullRequestFilter in place, BranchName will be unavailable, but four additional variables will be set: DestinationBranchName, PullRequestId, PullRequestTitle, SourceBranchName. There's no good way to get at these right now.

Expected Behavior

One of the below:

a. variables should return the variables that the source action actually exports (likely impossible to implement at CDK level.)
b. variables should return only variables that are always safe to use. I'm not sure if there's a public spec indicating what variables are returned under what circumstances. But BranchName is not always defined by the source action and therefore isn't safe to us.

More importantly, since the CDK doesn't/can't know what the possible variables are, I'd expect the underlying variableExpression(variableName: string) should be publicly accessible, like it is on some of the other actions:

public variable(variableName: string): string {
return this.variableExpression(variableName);
}

protected variableExpression(variableName: string): string {
this._variableReferenced = true;
return `#{${this._namespaceToken}.${variableName}}`;
}

Current Behavior

The cdk-provided variables getter misses some variables and returns others that may not exist. It doesn't provide any way to get at the missing ones.

For missed variables, a workaround is to call the protected variableExpression method via action["variableExpression"]("DestinationBranchName") or similar.

For returned variables that don't exist (BranchName), workaround is not to use it.

Reproduction Steps

Below is a construct that will reproduce the issue. Unfortunately, due to the way CodePipeline works, the minimal reproduction is not especially concise.

To use it, you'll need a CDK stack with a VPC and a CodeConnection to a git repo (I used a Github repo.)

Once you've got the stack deployed, open a pull request in the repo. It should trigger an execution. The UsesBranchName build action will fail, because the source action doesn't export the BranchName variable.

Now try manually triggering a execution with the "Release Change" button in the console. Here, the UsesBranchName build action will succeed, but the UsesDestinationBranchName will fail, because the DestinationBranchName won't have been defined (this will also happen when the pipeline is first created.)

Reproduction: https://gist.github.com/dleavitt/7950f5073bb0ebe2f3fa5049a2f44ab8

Possible Solution

  1. Add a public variable(variableName: string): string method to CodeStarConnectionsSourceAction, with the same implementation like this:
    public variable(variableName: string): string {
    return this.variableExpression(variableName);
    }

Maybe add it directly to Action (or make variableExpression public) if there are other actions where the list of variables could be dynamic.

  1. (breaking) Consider removing BranchName from CodeStarConnectionsSourceAction.variables(), since it's not always present and if missing attempting to use it will cause the build to fail.

Additional Information/Context

From what I can tell, there's an underlying issue with the implementation of the CodeStarSourceConnection Action provider in CodePipeline. For a given pipeline and action:

  • Different output variables will be available depending on how the build was triggered.
  • There's no way for subsequent stages to determine what variables are available.
  • Attempting to use a missing variable causes an unrecoverable failure of the pipeline.

Therefore the only variables that can be safely used are ones available in all cases (which BranchName is not.)

I would love to be wrong about this, let me know if there's a workaround!

Variables from a non-pr trigger

Screenshot 2024-07-31 at 17 18 50

Variables from a pull request trigger

Screenshot 2024-07-31 at 17 19 09

CDK CLI Version

2.150.0 (build 3f93027)

Framework Version

No response

Node.js Version

v20.11.1

OS

MacOS 14.3 (23D56)

Language

TypeScript

Language Version

Typescript (5.4.5)

Other information

No response

@dleavitt dleavitt added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Aug 1, 2024
@ashishdhingra ashishdhingra self-assigned this Aug 1, 2024
@ashishdhingra ashishdhingra added p2 needs-reproduction This issue needs reproduction. and removed needs-triage This issue or PR still needs to be triaged. labels Aug 1, 2024
@ashishdhingra
Copy link
Contributor

ashishdhingra commented Aug 1, 2024

Reproducible using following steps:

  • Create a CodeConnection to Git repository in AWS CodePipeline console Settings.
  • Modify the customer code to below (tweaked to add CLoudWatch log group):
import * as cdk from 'aws-cdk-lib';
import * as codebuild from "aws-cdk-lib/aws-codebuild";
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import * as codepipeline_actions from "aws-cdk-lib/aws-codepipeline-actions";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as logs from 'aws-cdk-lib/aws-logs';
import { Construct } from 'constructs';

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

    const vpc = ec2.Vpc.fromLookup(this, 'MyVpc', {isDefault: true});
    new PipelineRepro(this, 'PipelineDemo',{
      owner: '<<repository-owner>>',
      repo: '<<repository-name>>',
      connectionArn: 'arn:aws:codestar-connections:<<region>>:<<account-id>>:connection/<<connection-arn-guid>>',
      vpc: vpc
    });
  }
}

export interface PipelineReproProps {
  owner: string;
  repo: string;
  connectionArn: string;
  vpc: ec2.IVpc;
}

export class PipelineRepro extends Construct {
  pipeline: codepipeline.Pipeline;

  constructor(
    scope: Construct,
    id: string,
    { owner, repo, connectionArn, vpc }: PipelineReproProps,
  ) {
    super(scope, id);

    const sourceOutput = new codepipeline.Artifact();

    this.pipeline = new codepipeline.Pipeline(this, "Pipeline");

    // Source Stage
    //
    const pullSourceAction =
      new codepipeline_actions.CodeStarConnectionsSourceAction({
        actionName: "PullSource",
        connectionArn,
        output: sourceOutput,
        owner,
        repo,
        codeBuildCloneOutput: true,
      });

    this.pipeline.addTrigger({
      providerType: codepipeline.ProviderType.CODE_STAR_SOURCE_CONNECTION,
      gitConfiguration: {
        sourceAction: pullSourceAction,
        pullRequestFilter: [
          {
            events: [
              codepipeline.GitPullRequestEvent.OPEN,
              codepipeline.GitPullRequestEvent.UPDATED,
            ],
            branchesIncludes: ["*"],
          },
        ],
      },
    });

    this.pipeline.addStage({
      stageName: "Source",
      actions: [pullSourceAction],
    });

    // Build Stage
    //
    const projectLogGroup = new logs.LogGroup(this, "ProjectLogGroup");
    const project = new codebuild.PipelineProject(this, "Project", {
      vpc,
      buildSpec: codebuild.BuildSpec.fromObject({
        version: 0.2,
        env: { "exported-variables": ["GIT_BRANCH_NAME"] },
        phases: {
          build: { commands: ["env"] },
        },
      }),
      logging: { 
        cloudWatch: {
          logGroup: projectLogGroup
        }
      },
    });

    // will only succeed as part of the release created when the pipeline is
    // first created, will fail when triggered through a pull request
    const commitIdAction = new codepipeline_actions.CodeBuildAction({
      actionName: "UsesBranchName",
      input: sourceOutput,
      project,
      environmentVariables: {
        GIT_BRANCH_NAME: { value: pullSourceAction.variables.branchName },
      },
    });

    // will fail when release created and if doing a manual release. will
    // succeed when triggered through a pull request
    const sourceCommitIdAction = new codepipeline_actions.CodeBuildAction({
      actionName: "UsesDestinationBranchName",
      input: sourceOutput,
      project,
      environmentVariables: {
        GIT_BRANCH_NAME: {
          // workaround for accessing variable
          value: pullSourceAction["variableExpression"](
            "DestinationBranchName",
          ),
        },
      },
    });

    this.pipeline.addStage({
      stageName: "Build",
      actions: [commitIdAction, sourceCommitIdAction],
    });
  }
}

...
...
const app = new cdk.App();

new Issue31000Stack(app, 'Issue31000Stack', {
  env: { account: '<<account-id>>', region: '<<region>>'} // Required if referencing a VPC.
});
  • Create a pull request in the source repository. CodePipeline will fail with the error An action in this pipeline failed because one or more variables could not be resolved: Action name=UsesBranchName. This can result when a variable is referenced that does not exist. Validate the configuration for this action.
    Screenshot 2024-08-01 at 3 57 59 PM

@ashishdhingra ashishdhingra added effort/medium Medium work item – several days of effort and removed needs-reproduction This issue needs reproduction. labels Aug 1, 2024
@ashishdhingra ashishdhingra removed their assignment Aug 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-codepipeline-actions bug This issue is a bug. effort/medium Medium work item – several days of effort p2
Projects
None yet
Development

No branches or pull requests

2 participants