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

feat(aws-codepipeline): make input and output artifact names optional when creating Actions #845

Merged
merged 1 commit into from
Oct 11, 2018

Conversation

skinny85
Copy link
Contributor

@skinny85 skinny85 commented Oct 3, 2018

Previously, we always required customers to explicitly name the output artifacts the Actions used in the Pipeline, and to explicitly "wire together" the outputs of one Action as inputs to another. With this change, the CodePipeline Construct generates artifact names, if the customer didn't provide one explicitly, and tries to find the first available output artifact to use as input to a newly created Action that needs it, thus turning both the input and output artifacts from required to optional properties.


@eladb @RomainMuller I also had one more suggestion. I've always hated the artifactName property name of the Actions. It's misleading, and inconsistent with inputArtifact. How would you guys feel about a breaking change to rename that property to outputArtifactName?


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license.

@RomainMuller
Copy link
Contributor

I'm in favor of changing to outputArtifactName. Being said, I've also been a bit startled by the CodeBuild action having .artifact as well as .outputArtifacts and not being clear on whether they overlap or not (aka whether .artifact is the first entry of .outputArtifacts or not).

@@ -89,7 +89,7 @@ export abstract class ProjectRef extends cdk.Construct implements events.IEventR
* @param props the properties of the new Action
* @returns the newly created {@link PipelineBuildAction} build Action
*/
public addBuildToPipeline(stage: codepipeline.IStage, name: string, props: CommonPipelineBuildActionProps): PipelineBuildAction {
public addBuildToPipeline(stage: codepipeline.IStage, name: string, props?: CommonPipelineBuildActionProps): PipelineBuildAction {
Copy link
Contributor

Choose a reason for hiding this comment

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

Default props to {}.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.

@@ -68,7 +68,7 @@ export abstract class RepositoryRef extends cdk.Construct {
* @param props the properties of the new Action
* @returns the newly created {@link PipelineSourceAction}
*/
public addToPipeline(stage: actions.IStage, name: string, props: CommonPipelineSourceActionProps): PipelineSourceAction {
public addToPipeline(stage: actions.IStage, name: string, props?: CommonPipelineSourceActionProps): PipelineSourceAction {
Copy link
Contributor

Choose a reason for hiding this comment

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

Default props to {}.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.

@@ -192,13 +211,19 @@ export abstract class Action extends cdk.Construct {
}
}

protected addOutputArtifact(name: string): Artifact {
const artifact = new Artifact(this, name);
protected addOutputArtifact(name?: string): Artifact {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can instead do:

protected addOutputArtifact(name: string = this.stage._generateOutputArtifactName(this)) {
  const artifact = new Artifact(this, outputArtifactName);
  retenu artifact;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that will work. I want to call this method from the Action subclasses with props.outputArtifactName, where outputArtifactName is an optional string.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think @RomainMuller is right, you should be able to do that. If you pass undefined to name (which will be the case if props.outputArtifactName is not defined, it will apply the default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I stand corrected :) will change.

return artifact;
}

protected addInputArtifact(artifact: Artifact): Action {
this._inputArtifacts.push(artifact);
protected addInputArtifact(artifact?: Artifact): Action {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar sugaring tip as before applies there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same reply as above (I'm actually using the optional parameter).

@@ -146,6 +146,14 @@ export class Stage extends cdk.Construct implements actions.IStage {
}
}

public _generateOutputArtifactName(action: actions.Action): string {
return (this.pipeline as any)._generateOutputArtifactName(this, action);
Copy link
Contributor

Choose a reason for hiding this comment

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

Honestly I don't like having to do this. I'd rather actually make the methods public & mark them s internal-use-only, which is already semi-obvious from their _-prepended names.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does it make much difference though?

Copy link
Contributor

Choose a reason for hiding this comment

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

Main difference is the compiler stops trying to help the moment you've done as any, so you might miss breaking changes if you rename stuff & there is no test coverage through there.

private _generateOutputArtifactName(stage: actions.IStage, action: actions.Action): string {
// for now, just return a generic output artifact,
// ignoring the names of both the Stage, and the Action
return 'Artifact_' + (++this.artifactsCounter);
Copy link
Contributor

Choose a reason for hiding this comment

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

The drawback is that this makes artifact names dependent on generation order. This means a user refactoring code & trying to ensure they didn't inadvertently change their infrastructure cannot rely on a simple diff test.

I would like this to be order-independent unless we have a very good reason why it's better otherwise (I understand this is a simplicity vs. user-friendliness tradeoff).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. What would you see as the artifact name then? Some combination of Stage & Action names? `Artifact_${stage.name}_${action.name}`?

Copy link
Contributor

Choose a reason for hiding this comment

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

We can use the uniqueId of the construct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One disadvantage of this: the artifact names are now super ugly (Artifact_awscdkcodepipelinecodecommitcodebuildMyBuildProjectbuild61B48DC4).

Copy link
Contributor

Choose a reason for hiding this comment

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

But does anyone care?


// ignore unused private method (it's actually used in Stage)
// @ts-ignore
private _findInputArtifactFor(stage: actions.IStage, action: actions.Action): actions.Artifact {
Copy link
Contributor

Choose a reason for hiding this comment

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

The defaulting behavior that involves this particular element has a caveat that changing the runOrder after the defaulting behavior has happened will cause incorrect values to be used... A solutions is to make the runOder immutable & set at construction time?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. Not sure if it's a huge deal, as you can always name the artifacts directly, but I'm fine with making runOrder immutable (the rest of the properties of Actions already are).

@skinny85
Copy link
Contributor Author

skinny85 commented Oct 4, 2018

I'm in favor of changing to outputArtifactName. Being said, I've also been a bit startled by the CodeBuild action having .artifact as well as .outputArtifacts and not being clear on whether they overlap or not (aka whether .artifact is the first entry of .outputArtifacts or not).

Actually, that's a great point. The issue is that .outputArtifacts is an 'internal' thing (customers are not supposed to call it), while .artifact is not (you should call it, when wiring the Actions together). Unfortunately, we don't have a great mechanism for differentiating these - we could conceivably change .outputArtifacts to ._outputArtifacts... But I agree 100% that .artifact should be called .outputArtifact.

* The name of the build's output artifact
* The name of the build's output artifact.
*
* @default an auto-generated name will be used
*/
artifactName?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 on changing this to outputArtifactName

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done (everywhere).

@@ -89,7 +89,7 @@ export abstract class ProjectRef extends cdk.Construct implements events.IEventR
* @param props the properties of the new Action
* @returns the newly created {@link PipelineBuildAction} build Action
*/
public addBuildToPipeline(stage: codepipeline.IStage, name: string, props: CommonPipelineBuildActionProps): PipelineBuildAction {
public addBuildToPipeline(stage: codepipeline.IStage, name: string, props?: CommonPipelineBuildActionProps): PipelineBuildAction {
return new PipelineBuildAction(this.parent!, name, {
Copy link
Contributor

Choose a reason for hiding this comment

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

change this.parent! to this please

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed.

@@ -68,7 +68,7 @@ export abstract class RepositoryRef extends cdk.Construct {
* @param props the properties of the new Action
* @returns the newly created {@link PipelineSourceAction}
*/
public addToPipeline(stage: actions.IStage, name: string, props: CommonPipelineSourceActionProps): PipelineSourceAction {
public addToPipeline(stage: actions.IStage, name: string, props?: CommonPipelineSourceActionProps): PipelineSourceAction {
return new PipelineSourceAction(this.parent!, name, {
Copy link
Contributor

Choose a reason for hiding this comment

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

this.parent! => this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed.

*
* @param action the Action to generate the output artifact name for
*/
_generateOutputArtifactName(action: Action): string;
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it would be more elegant to hide all these internal methods under some _internal scope:

interface IStageInternal {
  attachAction(...);
  generateOutputArtifactName(...);
  findInputArtifactFor(...);
};

interface IStage {
  // public API

  // internal API
  _internal: IStageInternal;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -192,13 +211,19 @@ export abstract class Action extends cdk.Construct {
}
}

protected addOutputArtifact(name: string): Artifact {
const artifact = new Artifact(this, name);
protected addOutputArtifact(name?: string): Artifact {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think @RomainMuller is right, you should be able to do that. If you pass undefined to name (which will be the case if props.outputArtifactName is not defined, it will apply the default.

if (props.artifactName) {
this.artifact = this.addOutputArtifact(props.artifactName);
}
this.artifact = this.addOutputArtifact(props.artifactName);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a change in behavior. This means that now BuildAction will always have an output artifact defined for it. Could be totally fine, but what happens if the CodeBuild project doesn't specify any artifacts in buildspec.yaml but you still define an output artifact?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call out. I'll check what happens.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confirmed it doesn't cause any problems - there simply is no output artifact produced from the CodeBuild Project in this case, but the build Action itself succeeds without issues.

private _generateOutputArtifactName(stage: actions.IStage, action: actions.Action): string {
// for now, just return a generic output artifact,
// ignoring the names of both the Stage, and the Action
return 'Artifact_' + (++this.artifactsCounter);
Copy link
Contributor

Choose a reason for hiding this comment

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

We can use the uniqueId of the construct.


// ignore unused private method (it's actually used in Stage)
// @ts-ignore
private _findInputArtifactFor(stage: actions.IStage, action: actions.Action): actions.Artifact {
Copy link
Contributor

Choose a reason for hiding this comment

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

add doc that describes what this method is doing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

*
* @param action the Action to find the input artifact for
*/
_findInputArtifactFor(action: Action): Artifact;
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename to findInputArtifact

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

* Finds an input artifact for the given Action
* among the existing output artifacts currently in the Pipeline.
* This is an internal operation -
* you should never need to call it directly.
Copy link
Contributor

Choose a reason for hiding this comment

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

Provide some details on the algorithm

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done (it's not an easy one to describe...).

@skinny85
Copy link
Contributor Author

skinny85 commented Oct 5, 2018

Updated with review comments.

There are 2 more things that could be nice to change while we're in this area:

  1. The action.outputArtifacts property that @RomainMuller mentioned. Now we have action.outputArtifacts and action.outputArtifact, which is pretty confusing. I think it might be a good time to rename outputArtifacts to _outputArtifacts.
  2. I find this code very weird:
  protected addChild(child: cdk.Construct, name: string) {
    super.addChild(child, name);
    if (child instanceof Artifact) {
      this._outputArtifacts.push(child);
    }
  }

  protected addOutputArtifact(name: string = this.stage._internal._generateOutputArtifactName(this)): Artifact {
    const artifact = new Artifact(this, name);
    return artifact;
  }

It kind of abuses the Construct hierarchy, and I have no idea what these extra artifacts would accomplish - I think Action should only take artifacts as arguments when constructing them. I would like to remove this code.

@RomainMuller @eladb thoughts on these 2 points?

@RomainMuller
Copy link
Contributor

Regarding the outputArtifact and _outputArtifacts - I'm still not so clear how we deal with the case when an action produces more than one artifact (e.g. CodeBuild step with additional artifacts). I'm definitely in favor of marking the "internal-only" property with a leading _, however want to make sure we're not "losing" feature surface in doing so.

Regarding the second point, I think I'm okay dropping those if we don't have a clear use-case for them. We can always re-introduce if it turns out to be needed for something that cannot be done another way (unlikely).

@eladb
Copy link
Contributor

eladb commented Oct 5, 2018

@skinny85 I agree that output artifacts should be defined during initialization and we can get rid of addOutputArtifact

@skinny85
Copy link
Contributor Author

skinny85 commented Oct 5, 2018

Regarding the outputArtifact and _outputArtifacts - I'm still not so clear how we deal with the case when an action produces more than one artifact (e.g. CodeBuild step with additional artifacts). I'm definitely in favor of marking the "internal-only" property with a leading _, however want to make sure we're not "losing" feature surface in doing so.

Can you elaborate on this @RomainMuller ? When can a CodeBuild project produce multiple artifacts?

@skinny85
Copy link
Contributor Author

skinny85 commented Oct 8, 2018

@RomainMuller ping

According to a table in the AWS docs, CodeBuild can have a maximum of 1 output.

@RomainMuller
Copy link
Contributor

@skinny85 well that's interesting... What happens now when your build spec specifies secondary-artifacts?

And then, this still leaves me with the question of how do I access this "one" artifact... Right now the only way I found to work is codeBuldAction.outputArtifacts[0], which looks... inelegant. And would look even more so once renamed to _outputArtifacts. There is an artifact attribute, but it is always undefined.

@skinny85
Copy link
Contributor Author

@skinny85 well that's interesting... What happens now when your build spec specifies secondary-artifacts?

I assume they're ignored by CodePipeline.

And then, this still leaves me with the question of how do I access this "one" artifact... Right now the only way I found to work is codeBuldAction.outputArtifacts[0], which looks... inelegant. And would look even more so once renamed to _outputArtifacts. There is an artifact attribute, but it is always undefined.

This is the buildAction.artifact property, renamed in this PR to buildAction.outputArtifact to make it clear what it does.

Do the changes here now make sense?

@RomainMuller
Copy link
Contributor

Thanks for the clarification, @skinny85. I'm cool with this (and sorry the conversation dragged for so long here).

@skinny85
Copy link
Contributor Author

Rebased & made the two changes we discussed above.

… when creating Actions.

BREAKING CHANGE: this commit contains the following breaking changes:
* Rename 'artifactName' in Action construction properties to 'outputArtifactName'
* Rename the 'artifact' property of Actions to 'outputArtifact'
* No longer allow adding output artifacts to Actions by instantiating the Artifact class
* Rename Action#input/outputArtifacts properties to _input/_outputArtifacts

Previously, we always required customers to explicitly name the output artifacts the Actions used in the Pipeline,
and to explicitly "wire together" the outputs of one Action as inputs to another.
With this change, the CodePipeline Construct generates artifact names,
if the customer didn't provide one explicitly,
and tries to find the first available output artifact to use as input to a newly created Action that needs it,
thus turning both the input and output artifacts from required to optional properties.
@skinny85
Copy link
Contributor Author

Missed a usage of the old inputArtifacts property in a CFN unit test.

@skinny85
Copy link
Contributor Author

Thanks for the clarification, @skinny85. I'm cool with this (and sorry the conversation dragged for so long here).

No worries, and thanks for the review!

@skinny85 skinny85 merged commit 3d91c93 into aws:master Oct 11, 2018
@skinny85 skinny85 deleted the feature/artifacts-api branch October 11, 2018 20:07
RomainMuller added a commit that referenced this pull request Oct 12, 2018
__IMPORTANT NOTE__: This release includes a [breaking change](#845)
in the AWS CodePipeline construct library:
* The `inputArtifacts` and `outputArtifacts` properties of `Action` were intended for internal usage
  only, and have consequently been renamed to `_inputArtifacts` and `_outputArtifacts` respectively.
* The `artifact` property of `Action` classes was renamed to `outputArtifact`.
* The `artifactName` property of `Action` classes was renamed to `outputArtifactName`.
* It is no longer possible to add output artifacts to `Actions` by instantiating `Artifact`.

This release also includes a [fix](#911) for a bug
that would make the toolkit unusable for multi-stack applications. In order to benefit from
this fix, a globally installed CDK toolkit must also be updated:

```shell
$ npm i -g aws-cdk
$ cdk --version
0.12.0 (build ...)
```

Like always, you will also need to update your project's library versions:

|Language|Update?|
|--------|-------|
|JavaScript/TypeScript (npm)|[`npx npm-check-updates -u`](https://www.npmjs.com/package/npm-check-updates)|
|Java (maven)|[`mvn versions:use-latest-versions`](https://www.mojohaus.org/versions-maven-plugin/use-latest-versions-mojo.html)
|.NET (NuGet)|[`nuget update`](https://docs.microsoft.com/en-us/nuget/tools/cli-ref-update)

* **aws-cdk:** multi-stack apps can be synthesized or deployed [#911](#911).
* **@aws-cdk/aws-codebuild:** allow passing oauth token to GitHubEnterpriseSource [#908](#908)

* **@aws-cdk/aws-codepipeline:** make input and output artifact names optional when creating Actions. [#845](#945)

* **@aws-cdk/aws-cloudformation:** add permission management to CreateUpdate and Delete Stack CodePipeline Actions. [#880](#880)
RomainMuller added a commit that referenced this pull request Oct 12, 2018
* **aws-codebuild:** allow passing oauth token to GitHubEnterpriseSource ([#908](#908)) ([c23da91](c23da91))
* **toolkit:** multi-stack apps cannot be synthesized or deployed ([#911](#911)) ([5511076](5511076)), closes [#868](#868) [#294](#294) [#910](#910)

* **aws-cloudformation:** add permission management to CreateUpdate and Delete Stack CodePipeline Actions. ([#880](#880)) ([8b3ae43](8b3ae43))
* **aws-codepipeline:** make input and output artifact names optional when creating Actions. ([#845](#845)) ([3d91c93](3d91c93))

* **aws-codepipeline:** this commit contains the following breaking changes:
* Rename 'artifactName' in Action construction properties to 'outputArtifactName'
* Rename the 'artifact' property of Actions to 'outputArtifact'
* No longer allow adding output artifacts to Actions by instantiating the Artifact class
* Rename Action#input/outputArtifacts properties to _input/_outputArtifacts

Previously, we always required customers to explicitly name the output artifacts the Actions used in the Pipeline,
and to explicitly "wire together" the outputs of one Action as inputs to another.
With this change, the CodePipeline Construct generates artifact names,
if the customer didn't provide one explicitly,
and tries to find the first available output artifact to use as input to a newly created Action that needs it,
thus turning both the input and output artifacts from required to optional properties.
@RomainMuller RomainMuller mentioned this pull request Oct 12, 2018
RomainMuller added a commit that referenced this pull request Oct 12, 2018
* **aws-codebuild:** allow passing oauth token to GitHubEnterpriseSource ([#908](#908)) ([c23da91](c23da91))
* **toolkit:** multi-stack apps cannot be synthesized or deployed ([#911](#911)) ([5511076](5511076)), closes [#868](#868) [#294](#294) [#910](#910)

* **aws-cloudformation:** add permission management to CreateUpdate and Delete Stack CodePipeline Actions. ([#880](#880)) ([8b3ae43](8b3ae43))
* **aws-codepipeline:** make input and output artifact names optional when creating Actions. ([#845](#845)) ([3d91c93](3d91c93))

* **aws-codepipeline:** this commit contains the following breaking changes:
* Rename 'artifactName' in Action construction properties to 'outputArtifactName'
* Rename the 'artifact' property of Actions to 'outputArtifact'
* No longer allow adding output artifacts to Actions by instantiating the Artifact class
* Rename Action#input/outputArtifacts properties to _input/_outputArtifacts

Previously, we always required customers to explicitly name the output artifacts the Actions used in the Pipeline,
and to explicitly "wire together" the outputs of one Action as inputs to another.
With this change, the CodePipeline Construct generates artifact names,
if the customer didn't provide one explicitly,
and tries to find the first available output artifact to use as input to a newly created Action that needs it,
thus turning both the input and output artifacts from required to optional properties.
@NGL321 NGL321 added the contribution/core This is a PR that came from AWS. label Sep 27, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution/core This is a PR that came from AWS.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants