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(servicecatalog): Create TagOptions Construct #18314

Merged
merged 19 commits into from
Jan 25, 2022

Conversation

arcrank
Copy link
Contributor

@arcrank arcrank commented Jan 7, 2022

Fixes: #17753

Previously TagOptions were defined via an interface and we only created the underlying
resources upon an association. This broke CX if tagoptions were mangaged centrally. We move to
make the TagOptions class a wrapper around aggregate individual TagOptions.

BREAKING CHANGE: TagOptions now have scope and props argument in constructor, and data is now passed via a allowedValueForTags field in props


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

Previously TagOptions were defined via an interface and we only created the underlying
resources upon an association.  This broke CX if tagoptions were mangaged centrally.  We move to
make the TagOptions class a wrapper around aggregate individual TagOptions.

Based on updated contributing guidelines, need to figure out how to note this as a breaking change, or figure out how to properly deprecate old version.
@gitpod-io
Copy link

gitpod-io bot commented Jan 7, 2022

@github-actions github-actions bot added the @aws-cdk/aws-servicecatalog Related to AWS Service Catalog label Jan 7, 2022
@arcrank
Copy link
Contributor Author

arcrank commented Jan 10, 2022

This should address: #17753

Copy link
Contributor

@skinny85 skinny85 left a comment

Choose a reason for hiding this comment

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

First round of comments. We definitely need to minimize the amount of changes we are making to the tests in this PR, as we're changing production code at the same time. I also thing TagOptions should become a full Construct.

@skinny85
Copy link
Contributor

This should address: #17753

Let's add a Fixes #17753 to the PR description then.

@mergify mergify bot dismissed skinny85’s stale review January 14, 2022 03:53

Pull request has been modified.

Template.fromStack(stack).resourceCountIs('AWS::ServiceCatalog::TagOption', 3); //Generates a resource for each unique key-value pair
Template.fromStack(stack).resourceCountIs('AWS::ServiceCatalog::TagOptionAssociation', 3);
}),

test('fails to add tag options with invalid minimum key length', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are also not really valid but we can modify it slightly to still work, unfortunately still a lot of git churn. Moving the creation away from the association means that early failures happen on creation not via association calls.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK. If these now fail on creation, let's modify the test and its description to reflect that (being careful to minimize the changes still).

I understand this will mean these tests are now in the wrong file, but we'll live with that for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated with modified wording and minimized changes.

*/
public readonly _tagOptionsMap: { [key: string]: CfnTagOption };

constructor(scope: Construct, id: string, tagOptions: {[key: string]: string[] }) {
Copy link
Contributor Author

@arcrank arcrank Jan 14, 2022

Choose a reason for hiding this comment

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

The cdk linting doesn't like this setup (no interface and no props struct) but I don't particularly see value of having classes that are just pass throughs. I don't know if making it props with a tagOptions field is better since it seems redundant to do

const tagO = new servicecatalog.TagOptions(this, 'TagOptions', {
  tagOptions : {
    key1: ['v1', 'v2', 'v3'],
  }
});

Only if its a little bit of future proofing in case we want to add another field to props then it would be easier to make change.
I will defer to you though on 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.

I think linting rules have become more stringent? Build is failing since you apparently need to disable interface ref in js docs ( I had hoped I could just do it in package.json).

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's just introduce a TagOptionsProps interface as the third argument, like you suggested in #18314 (comment).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that works, leaves more wiggle room to not break things in future should changes happen and we want to modify props.

@arcrank arcrank requested a review from skinny85 January 18, 2022 15:42
Copy link
Contributor

@skinny85 skinny85 left a comment

Choose a reason for hiding this comment

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

We are getting closer, but I think this needs a couple more iterations before we can call it "Done".

packages/@aws-cdk/aws-servicecatalog/lib/tag-options.ts Outdated Show resolved Hide resolved
* List of CfnTagOption
*/
public readonly tagOptionsMap: { [key: string]: string[] };
* TagOption keys and associated list of possible values.
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like these docs need some love. How about something like:

Suggested change
* TagOption keys and associated list of possible values.
* The values that are allowed to be set for specific tags.
* The keys of the map are the names of the tags,
* and the values of the map are a list of allowed values of that particular tag.

Feel free to add more as you see fit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can be confusing with keys in a map struct and keys to a tag, but I think we should stick with using tag key as the wording. I don't know if I've seen tags referenced with a name and not just key-value.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fine, then use the name "tag key". That's why I said "something like that" and "feel free to add more as you see fit" 🙂.


constructor(scope: Construct, id: string, props: TagOptionsProps) {
super(scope, id);
this._tagOptionsMap = this.createUnderlyingTagOptions(props.tagOptions);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it valid to have an empty map for props.tagOptions? I would guess 'no', right? Perhaps we need to validate that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because we iterate over the underlying values for creation and associating, if you do an empty one it will not fail and it won't create anything. I can add a test for this case as well, or since this it's mostly useless we could validate and fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's better for this to fail since it wouldn't align with other constructs to instantiate one with a certain (empty) configuration that wouldn't yield anything in template. I've therefore added validation to make sure that the struct is not empty, and then that for each key the tag values are not empty, along with two tests for this. There is not a max length for these lists since I believe that these can be altered by the customer.

@mergify mergify bot dismissed skinny85’s stale review January 19, 2022 22:49

Pull request has been modified.

@arcrank arcrank changed the title feat(servicecatalog): Create standalone TagOptions Construct feat(servicecatalog): Create TagOptions Construct Jan 21, 2022
@arcrank arcrank requested a review from skinny85 January 21, 2022 18:19
Copy link
Contributor

@skinny85 skinny85 left a comment

Choose a reason for hiding this comment

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

Looks great @arcrank. I basically have one last major question, and after that one is resolved, we can merge this one in.

});
};
for (const [tagOptionIdentifier, cfnTagOption] of Object.entries(tagOptions._tagOptionsMap)) {
const tagAssocationConstructId = `TagOptionAssociation${hashValues(resource.node.addr, tagOptionIdentifier)}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

So, this is interesting - we are actually changing the algorithm used for generating the identifier of the CfnTagOptionAssociation resource. Previously, it was `TagOptionAssociation${hashValues(key, value, resource.node.addr)}` - now, it's `TagOptionAssociation${hashValues(resource.node.addr, tagOptionIdentifier)}`.

I wonder why this change? Wouldn't keeping the same algorithm reduce the number of changes in the template for customers upgrading to a new CDK version that will include these changes?

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 have been able to maintain current behavior here. I didn't try to maintain this before because from my OOP life it felt wrong to add a more coupling between the two resources, and I more or less tracked the impact of changes that would happen as all or nothing - since we could expect to see the TagOption resources changing I thought it might just go hand in hand to expect associations to change as well.

If this new version is fine, my understanding is now better for it.

value: tagValue,
active: true,
});
tagOptionMap[tagOptionIdentifier] = tagOption;
Copy link
Contributor

Choose a reason for hiding this comment

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

Since tagOptionIdentifier is also the identifier of the CfnTagOption resource, is there actually any reason for this map? I think we can just have an array of CfnTagOption, and then retrieve its identifier with cfnTagOption.node.id (or possibly Node.of(cfnTagOption).id if it uses the new construct API).

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 can just make this a list, the map part was a relic of an earlier point where we considered adding lookup access to an underlying tagOption(s), and a map felt more efficient than searching list.


Template.fromStack(stack).resourceCountIs('AWS::ServiceCatalog::TagOption', 4); //Generates a resource for each unique key-value pair
Template.fromStack(stack).resourceCountIs('AWS::ServiceCatalog::TagOptionAssociation', 6);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you instead keep key1 as having 2 values, and this way, we won't have to touch the assertions in this test?

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.

key1: ['value1'.repeat(1000), 'value2'],
key2: ['value1'],
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add an empty line after this line.

@arcrank arcrank requested a review from skinny85 January 24, 2022 17:37
Copy link
Contributor

@skinny85 skinny85 left a comment

Choose a reason for hiding this comment

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

Looks beautiful @arcrank, awesome job!

@mergify
Copy link
Contributor

mergify bot commented Jan 24, 2022

Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject89A8053A-LhjRyN9kxr8o
  • Commit ID: 5f1f4a6
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify mergify bot merged commit 903c4b6 into aws:master Jan 25, 2022
@mergify
Copy link
Contributor

mergify bot commented Jan 25, 2022

Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

LukvonStrom pushed a commit to LukvonStrom/aws-cdk that referenced this pull request Jan 26, 2022
Fixes:  [aws#17753](aws#17753)


Previously TagOptions were defined via an interface and we only created the underlying
resources upon an association.  This broke CX if tagoptions were mangaged centrally.  We move to
make the TagOptions class a wrapper around aggregate individual TagOptions.

BREAKING CHANGE: `TagOptions` now have `scope` and `props` argument in constructor, and data is now passed via a `allowedValueForTags` field in props

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
TikiTDO pushed a commit to TikiTDO/aws-cdk that referenced this pull request Feb 21, 2022
Fixes:  [aws#17753](aws#17753)


Previously TagOptions were defined via an interface and we only created the underlying
resources upon an association.  This broke CX if tagoptions were mangaged centrally.  We move to
make the TagOptions class a wrapper around aggregate individual TagOptions.

BREAKING CHANGE: `TagOptions` now have `scope` and `props` argument in constructor, and data is now passed via a `allowedValueForTags` field in props

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-servicecatalog Related to AWS Service Catalog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

(servicecatalog): TagOptions cannot be shared among portfolios in the same account
3 participants