-
Notifications
You must be signed in to change notification settings - Fork 4k
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): allow associating TagOptions with a Portfolio #15612
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, but the API needs some polish before we can merge it in.
@@ -157,6 +158,21 @@ A product can be added to multiple portfolios depending on your resource and org | |||
portfolio.addProduct(product); | |||
``` | |||
|
|||
### TagOptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
### TagOptions | |
### Tag options |
TagOptions allow administrators to easily manage tags on provisioned products by creating a selection of tags for end users to choose from. | ||
For example, an end user can choose an `ec2` for the instance type size. | ||
TagOptions are created by specifying a key with a selection of values. | ||
At the moment, TagOptions can only be disabled on the console. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment, TagOptions can only be disabled on the console. | |
At the moment, TagOptions can only be disabled in the console. |
* A TagOption is a key-value pair managed in AWS Service Catalog. | ||
* It is not an AWS tag, but serves as a template for creating an AWS tag based on the TagOption. | ||
*/ | ||
addTagOptions(tagOptions: TagOptions): void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Let's also allow setting the
TagOptions
directly when creating thePortfolio
. So, add an optionalreadonly tagOptions?: TagOptions
property toPortfolioProps
. - Since you're using "associate" in the description already, how about we call this method
associateTagOptions()
? I'm worried we're overloading the term "add" in this class to mean very many different things.
@@ -48,6 +49,19 @@ export class AssociationManager { | |||
} | |||
} | |||
|
|||
public static associateTagOptions(portfolio: IPortfolio, resourceId: string, tagOptions: TagOptions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I don't think
resourceId
as a separate parameter makes too much sense. I think you should remove it, and useportfolio.portfolioId
in the expression below inCfnTagOptionAssociation
. - Specify the return type of this function explicitly please.
@@ -0,0 +1,35 @@ | |||
import * as cdk from '@aws-cdk/core'; | |||
import { CfnTagOption } from '.'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a weird notation - I wouldn't use it.
@@ -0,0 +1,35 @@ | |||
import * as cdk from '@aws-cdk/core'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file should be called tag-options.ts
.
/** | ||
* List of CfnTagOption | ||
*/ | ||
public readonly cfnTagOptions: CfnTagOption[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use properties from the L1 layer in publicly-available classes like this.
Rename the property to tagOptions
, and make it return a {[key: string]: string[]}
(make sure it's a defensive copy though, so none can mutate them in that way). Then, move all of the logic of creating CfnTagOption
s into the associateTagOptions()
method of AssociationManager
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason why the logic is here is because tagOptions are global. Moving it to associateTagOptions
would require us to create the L1 when an association is made and do an additional check each time to make sure no duplicate tagOptions are being created. This brings up the issue of what happens when we create one tagOption and apply it to two portfolio, it would create two tagOptions (unless we maintain a map of tagOptions as using tryFindChild
won't work due to the different hash/portfolio).
What do you recommend Adam?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can still make them global by retrieving the Stack a given Portfolio belongs to with the Stack.of(portfolio)
method.
*/ | ||
public readonly cfnTagOptions: CfnTagOption[]; | ||
|
||
constructor(stack : cdk.Stack, tagOptionsMap: {[key: string]: string[]}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not really an API we would consider adding to the CDK - it's very unnatural.
Get rid of the stack
parameter from here.
}), | ||
|
||
test('fails to add tag options with invalid value length', () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for this empty line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great @wanjacki! A few last polishes before we merge this in.
key1: ['value1', 'value2'], | ||
key2: ['value1'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we give a more realistic example here? Perhaps the one using the EC2 instance sizes mentioned above?
const stack = cdk.Stack.of(portfolio); | ||
const tagOptionKey = hashValues(key, value, stack.node.addr); | ||
const tagOptionConstructId = `TagOption${tagOptionKey}`; | ||
var cfnTagOption = stack.node.tryFindChild(tagOptionConstructId) as CfnTagOption; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var
-> let
/** | ||
* List of CfnTagOption | ||
*/ | ||
public readonly tagOptionsMap: {[key: string]: string[]}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public readonly tagOptionsMap: {[key: string]: string[]}; | |
public readonly tagOptionsMap: { [key: string]: string[] }; |
InputValidator.validateLength(portfolio.node.addr, 'TagOption key', 1, 128, key); | ||
tagOptions.tagOptionsMap[key].forEach((value: string) => { | ||
InputValidator.validateLength(portfolio.node.addr, 'TagOption value', 1, 256, value); | ||
const stack = cdk.Stack.of(portfolio); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's rename this variable to portfolioStack
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, let's move this out of the loop (it should be done once per associateTagOptions()
call, not on each loop iteration).
public readonly tagOptionsMap: {[key: string]: string[]}; | ||
|
||
constructor(tagOptionsMap: {[key: string]: string[]}) { | ||
this.tagOptionsMap = tagOptionsMap; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like I wrote before, I think this should save a defensive copy of the passed map:
this.tagOptionsMap = tagOptionsMap; | |
this.tagOptionsMap = { ...tagOptionsMap }; |
So that mutating the tagOptionsMap
after creating TagOptions
does not cause any changes in the instance.
@@ -48,6 +49,34 @@ export class AssociationManager { | |||
} | |||
} | |||
|
|||
public static associateTagOptions(portfolio: IPortfolio, tagOptions: TagOptions): void { | |||
Object.keys(tagOptions.tagOptionsMap).forEach(key => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use Object.entries
with for...of
instead, like this, which will save you accessing the value separately with tagOptions.tagOptionsMap[key]
below.
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great @wanjacki!
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). |
Allows users to add TagOptions to their portfolio. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Allows users to add TagOptions to their portfolio. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Allows users to add TagOptions to their portfolio.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license