From e3699e2236524ebe7bc1b0f68cb62c315c0fc493 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Mon, 26 Jul 2021 13:51:42 -0400 Subject: [PATCH 01/16] feat(servicecatalog): Add provisioning parameters constraint Add ability to configure parameter options for when launching a product. Co-authored-by: Dillon Ponzo --- .../@aws-cdk/aws-servicecatalog/README.md | 27 ++++- .../aws-servicecatalog/lib/constraints.ts | 38 +++++++ .../aws-servicecatalog/lib/portfolio.ts | 14 ++- .../lib/private/association-manager.ts | 45 +++++++- .../test/integ.portfolio.expected.json | 15 +++ .../test/integ.portfolio.ts | 15 ++- .../aws-servicecatalog/test/portfolio.test.ts | 100 +++++++++++++++++- 7 files changed, 246 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index b90748a2125ad..bedf69136be37 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -35,6 +35,7 @@ enables organizations to create and manage catalogs of products for their end us - [Constraints](#constraints) - [Tag update constraint](#tag-update-constraint) - [Notify on stack events](#notify-on-stack-events) + - [Constrain provisioning parameters](#constrain-provisioning-parameters) - [Set launch role](#set-launch-role) - [Deploy with StackSets](#deploy-with-stacksets) @@ -162,7 +163,7 @@ A product can be added to multiple portfolios depending on your resource and org portfolio.addProduct(product); ``` -### Tag Options +## 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. @@ -228,6 +229,30 @@ portfolio.notifyOnStackEvents(product, topic2, { }); ``` +### Constrain provisioning parameters + +Allows you to configure the options that are available to end users when they launch a product. +A provisioning rule consists of one or more assertions that narrow the allowable values for parameters in a product. +You can configure multiple rules to govern the different parameters and parameter options in your products. +For example, a parameter might define the various instance types that users can choose from when launching a stack that includes EC2 instances. +A provisoning rule has an optional `ruleCondition` field that allows ability to configure when rules are applied. +If a `ruleCondition` is specified, all the assertions will be applied if the condition evalutates to true. +For information on rule-specific intrinsic functions to define rule conditions and assertions, +see [AWS Rule Functions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-rules.html). + +```ts fixture=portfolio-product +import * as cdk from '@aws-cdk/core'; + +portfolio.constrainProvisioningParameters(product, { + ruleName: 'testInstanceType', + ruleCondition: cdk.Fn.conditionEquals(cdk.Fn.ref("Environment"), 'test'), + assertions: [ { + assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), + assertDescription: 'For test environment, the instance type should be small', + }] +}) +``` + ### Set launch role Allows you to configure a specific AWS `IAM` role that a user must assume when launching a product. diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts b/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts index 50cb619aa0889..3f004fb7a0608 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts @@ -1,4 +1,5 @@ import * as iam from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; import { MessageLanguage } from './common'; /** @@ -62,4 +63,41 @@ export interface TagUpdateConstraintOptions extends CommonConstraintOptions { * @default true */ readonly allow?: boolean; +} + +/** + * An assertion within a template rule, defined by intrinsic functions. + */ +export interface Assertion { + /** + * The assertion condition. + */ + readonly assert: cdk.ICfnConditionExpression; + + /** + * The description for the asssertion + * @default - no description provided for the assertion. + */ + readonly assertDescription?: string; +} + +/** + * Defines the provisioning template constraints. + */ +export interface TemplateRule { + /** + * Name of the rule. + */ + readonly ruleName: string; + + /** + * Specify when to apply rule with a rule-specific intrinsic function. + * @default - No rule condition provided. + */ + readonly ruleCondition?: cdk.ICfnConditionExpression; + + /** + * A list of assertions that make up the rule. + */ + readonly assertions: Assertion[]; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts index 73d4452ce348b..1d33b1d9fca9c 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts @@ -2,7 +2,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; import { MessageLanguage } from './common'; -import { CommonConstraintOptions, StackSetsConstraintOptions, TagUpdateConstraintOptions } from './constraints'; +import { CommonConstraintOptions, TemplateRule, StackSetsConstraintOptions, TagUpdateConstraintOptions } from './constraints'; import { AssociationManager } from './private/association-manager'; import { hashValues } from './private/util'; import { InputValidator } from './private/validation'; @@ -100,6 +100,14 @@ export interface IPortfolio extends cdk.IResource { */ notifyOnStackEvents(product: IProduct, topic: sns.ITopic, options?: CommonConstraintOptions): void; + /** + * Set provisioning rules for the product. + * @param product A service catalog product. + * @param assertion A list of valid rules to apply. + * @param options options for the constraint. + */ + constrainProvisioningParameters(product:IProduct, assertion: TemplateRule, options?: CommonConstraintOptions): void; + /** * Force users to assume a certain role when launching a product. * @@ -161,6 +169,10 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { AssociationManager.notifyOnStackEvents(this, product, topic, options); } + public constrainProvisioningParameters(product: IProduct, assertion: TemplateRule, options: CommonConstraintOptions = {}): void { + AssociationManager.constrainProvisioningParameters(this, product, assertion, options); + } + public setLaunchRole(product: IProduct, launchRole: iam.IRole, options: CommonConstraintOptions = {}): void { AssociationManager.setLaunchRole(this, product, launchRole, options); } diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts index e4e8326f2bb19..e80d0819a16b2 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts @@ -1,11 +1,11 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; -import { CommonConstraintOptions, StackSetsConstraintOptions, TagUpdateConstraintOptions } from '../constraints'; +import { Assertion, CommonConstraintOptions, StackSetsConstraintOptions, TagUpdateConstraintOptions, TemplateRule } from '../constraints'; import { IPortfolio } from '../portfolio'; import { IProduct } from '../product'; import { - CfnLaunchNotificationConstraint, CfnLaunchRoleConstraint, CfnPortfolioProductAssociation, + CfnLaunchNotificationConstraint, CfnLaunchRoleConstraint, CfnLaunchTemplateConstraint, CfnPortfolioProductAssociation, CfnResourceUpdateConstraint, CfnStackSetConstraint, CfnTagOption, CfnTagOptionAssociation, } from '../servicecatalog.generated'; import { TagOptions } from '../tag-options'; @@ -74,6 +74,28 @@ export class AssociationManager { } } + public static constrainProvisioningParameters(portfolio: IPortfolio, product: IProduct, + assertion: TemplateRule, options: CommonConstraintOptions): void { + this.validateCommonConstraintOptions(portfolio, product, options); + const association = this.associateProductWithPortfolio(portfolio, product); + const constructId = `LaunchTemplateConstraint${hashValues(association.associationKey, assertion.ruleName)}`; + + if (!portfolio.node.tryFindChild(constructId)) { + const constraint = new CfnLaunchTemplateConstraint(portfolio as unknown as cdk.Resource, constructId, { + acceptLanguage: options.messageLanguage, + description: options.description, + portfolioId: portfolio.portfolioId, + productId: product.productId, + rules: this.formatRule(portfolio as unknown as cdk.Resource, assertion), + }); + + // Add dependsOn to force proper order in deployment. + constraint.addDependsOn(association.cfnPortfolioProductAssociation); + } else { + throw new Error(`Provisioning rule ${assertion.ruleName} already configured on association ${this.prettyPrintAssociation(portfolio, product)}`); + } + } + public static setLaunchRole(portfolio: IPortfolio, product: IProduct, launchRole: iam.IRole, options: CommonConstraintOptions): void { this.validateCommonConstraintOptions(portfolio, product, options); const association = this.associateProductWithPortfolio(portfolio, product); @@ -168,7 +190,26 @@ export class AssociationManager { return `- Portfolio: ${portfolio.node.path} | Product: ${product.node.path}`; } + private static formatAssertions(scope: cdk.Resource, assertions : Assertion[]): {[key: string]: string}[] { + const formattedAssertions: {[key: string]: string}[] = []; + assertions.forEach(assertion => formattedAssertions.push({ + Assert: scope.stack.resolve(assertion.assert), + ...(assertion.assertDescription) && { AssertDescription: assertion.assertDescription }, + })); + return formattedAssertions; + } + + private static formatRule(scope: cdk.Resource, rule: TemplateRule): string { + return JSON.stringify({ + [rule.ruleName]: { + Assertions: this.formatAssertions(scope, rule.assertions), + ...(rule.ruleCondition) && { RuleCondition: scope.stack.resolve(rule.ruleCondition) }, + }, + }); + } + private static validateCommonConstraintOptions(portfolio: IPortfolio, product: IProduct, options: CommonConstraintOptions): void { InputValidator.validateLength(this.prettyPrintAssociation(portfolio, product), 'description', 0, 2000, options.description); } } + diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json index bc21e236fd3dd..9411dbe4af087 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json @@ -202,6 +202,21 @@ "TestPortfolioPortfolioProductAssociationa0185761d231B0D998A7" ] }, + "TestPortfolioLaunchTemplateConstraint13a8b80248da4E047268": { + "Type": "AWS::ServiceCatalog::LaunchTemplateConstraint", + "Properties": { + "PortfolioId": { + "Ref": "TestPortfolio4AC794EB" + }, + "ProductId": { + "Ref": "TestProduct7606930B" + }, + "Rules": "{\"testInstanceType\":{\"Assertions\":[{\"Assert\":{\"Fn::Contains\":[[\"t2.micro\",\"t2.small\"],{\"Ref\":\"InstanceType\"}]},\"AssertDescription\":\"For test environment, the instance type should be small\"}],\"RuleCondition\":{\"Fn::Equals\":[{\"Ref\":\"Environment\"},\"test\"]}}}" + }, + "DependsOn": [ + "TestPortfolioPortfolioProductAssociationa0185761d231B0D998A7" + ] + }, "TagOptionc0d88a3c4b8b": { "Type": "AWS::ServiceCatalog::TagOption", "Properties": { diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts index c9aa607880619..20debf14d72cf 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts @@ -1,10 +1,10 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; -import { App, Stack } from '@aws-cdk/core'; +import * as cdk from '@aws-cdk/core'; import * as servicecatalog from '../lib'; -const app = new App(); -const stack = new Stack(app, 'integ-servicecatalog-portfolio'); +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integ-servicecatalog-portfolio'); const role = new iam.Role(stack, 'TestRole', { assumedBy: new iam.AccountRootPrincipal(), @@ -79,4 +79,13 @@ secondPortfolio.deployWithStackSets(product, { allowStackSetInstanceOperations: true, }); +portfolio.constrainProvisioningParameters(product, { + ruleName: 'testInstanceType', + ruleCondition: cdk.Fn.conditionEquals(cdk.Fn.ref('Environment'), 'test'), + assertions: [{ + assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), + assertDescription: 'For test environment, the instance type should be small', + }], +}); + app.synth(); diff --git a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts index 3c76619c03a1f..64f49bb5b3213 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts @@ -457,7 +457,105 @@ describe('portfolio associations and product constraints', () => { expect(() => { portfolio.notifyOnStackEvents(product, topic); }).toThrowError(`Topic ${topic} is already subscribed to association`); - }); + }), + + test('set provisioning rule', () => { + portfolio.addProduct(product); + portfolio.constrainProvisioningParameters(product, + { + ruleName: 'Rule', + assertions: [ + { + assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), + assertDescription: 'assert description', + }, + ], + }, + ); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::LaunchTemplateConstraint', { + PortfolioId: { Ref: 'MyPortfolio59CCA9C9' }, + ProductId: { Ref: 'MyProduct49A3C587' }, + Rules: JSON.stringify( { + Rule: { + Assertions: [ + { + Assert: { 'Fn::Contains': [['t2.micro', 't2.small'], { Ref: 'InstanceType' }] }, + AssertDescription: 'assert description', + }, + ], + }, + }), + }); + }), + + test('set provisioning rule still creates without explicit association', () => { + portfolio.constrainProvisioningParameters(product, + { + ruleName: 'Rule', + ruleCondition: cdk.Fn.conditionContains(['a', 'b'], 'text'), + assertions: [ + { + assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), + assertDescription: 'assert description', + }, + { + assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('OtherInstanceType')), + assertDescription: 'other assert description', + }, + ], + }, { + description: 'test description', + messageLanguage: servicecatalog.MessageLanguage.EN, + }, + ); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::LaunchTemplateConstraint'); + }), + + test('set multiple provisioning rules', () => { + portfolio.constrainProvisioningParameters(product, + { + ruleName: 'Rule01', + assertions: [{ + assert: cdk.Fn.conditionContains(['BucketOwnerRead'], cdk.Fn.ref('AccessControl')), + assertDescription: 'assert description', + }], + }); + + portfolio.constrainProvisioningParameters(product, + { + ruleName: 'Rule02', + assertions: [{ + assert: cdk.Fn.conditionContains(['BucketOwnerWrite'], cdk.Fn.ref('AccessControl')), + assertDescription: 'assert description', + }], + }); + + expect(stack).toCountResources('AWS::ServiceCatalog::LaunchTemplateConstraint', 2); + }), + + test('fails to set a duplicate provisioning rule', () => { + portfolio.constrainProvisioningParameters(product, + { + ruleName: 'Rule01', + assertions: [{ + assert: cdk.Fn.conditionContains(['BucketOwnerRead'], cdk.Fn.ref('AccessControl')), + assertDescription: 'assert description', + }], + }); + + expect(() => { + portfolio.constrainProvisioningParameters(product, + { + ruleName: 'Rule01', + assertions: [{ + assert: cdk.Fn.conditionContains(['BucketOwnerWrite'], cdk.Fn.ref('AccessControl')), + assertDescription: 'assert description', + }], + }); + }).toThrowError(/Provisioning rule Rule01 already configured on association/); + }), describe('portfolio constraints that have roles', () => { let launchRole: iam.IRole, adminRole: iam.IRole; From 9249fd33feeaa9c55c516d819d3a4dcd8b821788 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Tue, 27 Jul 2021 12:42:51 -0400 Subject: [PATCH 02/16] addressing PR comments --- .../@aws-cdk/aws-servicecatalog/README.md | 6 +- .../aws-servicecatalog/lib/constraints.ts | 18 ++++-- .../aws-servicecatalog/lib/portfolio.ts | 12 ++-- .../lib/private/association-manager.ts | 43 ++++++++------ .../aws-servicecatalog/lib/private/util.ts | 2 +- .../test/integ.portfolio.ts | 16 ++--- .../aws-servicecatalog/test/portfolio.test.ts | 59 ++++++++++--------- 7 files changed, 92 insertions(+), 64 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index bedf69136be37..da20669621bf7 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -35,7 +35,7 @@ enables organizations to create and manage catalogs of products for their end us - [Constraints](#constraints) - [Tag update constraint](#tag-update-constraint) - [Notify on stack events](#notify-on-stack-events) - - [Constrain provisioning parameters](#constrain-provisioning-parameters) + - [Constrain Cloudformation parameters](#constrain-cloudformation-parameters) - [Set launch role](#set-launch-role) - [Deploy with StackSets](#deploy-with-stacksets) @@ -229,7 +229,7 @@ portfolio.notifyOnStackEvents(product, topic2, { }); ``` -### Constrain provisioning parameters +### Constrain Cloudformation parameters Allows you to configure the options that are available to end users when they launch a product. A provisioning rule consists of one or more assertions that narrow the allowable values for parameters in a product. @@ -243,7 +243,7 @@ see [AWS Rule Functions](https://docs.aws.amazon.com/AWSCloudFormation/latest/Us ```ts fixture=portfolio-product import * as cdk from '@aws-cdk/core'; -portfolio.constrainProvisioningParameters(product, { +portfolio.constrainCloudFormationParameters(product, { ruleName: 'testInstanceType', ruleCondition: cdk.Fn.conditionEquals(cdk.Fn.ref("Environment"), 'test'), assertions: [ { diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts b/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts index 3f004fb7a0608..8d1093817a0c3 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts @@ -68,7 +68,7 @@ export interface TagUpdateConstraintOptions extends CommonConstraintOptions { /** * An assertion within a template rule, defined by intrinsic functions. */ -export interface Assertion { +export interface TemplateRuleAssertion { /** * The assertion condition. */ @@ -78,7 +78,7 @@ export interface Assertion { * The description for the asssertion * @default - no description provided for the assertion. */ - readonly assertDescription?: string; + readonly description?: string; } /** @@ -94,10 +94,20 @@ export interface TemplateRule { * Specify when to apply rule with a rule-specific intrinsic function. * @default - No rule condition provided. */ - readonly ruleCondition?: cdk.ICfnConditionExpression; + readonly condition?: cdk.ICfnConditionExpression; /** * A list of assertions that make up the rule. */ - readonly assertions: Assertion[]; + readonly assertions: TemplateRuleAssertion[]; +} + +/** + * Properties for provisoning rule constraint. + */ +export interface ProvisioningRuleOptions extends CommonConstraintOptions { + /** + * The rule with condition and assertions to apply to template. + */ + readonly assertion: TemplateRule; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts index 1d33b1d9fca9c..9b80f69d37ee0 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts @@ -2,7 +2,10 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; import { MessageLanguage } from './common'; -import { CommonConstraintOptions, TemplateRule, StackSetsConstraintOptions, TagUpdateConstraintOptions } from './constraints'; +import { + CommonConstraintOptions, StackSetsConstraintOptions, + TagUpdateConstraintOptions, ProvisioningRuleOptions, +} from './constraints'; import { AssociationManager } from './private/association-manager'; import { hashValues } from './private/util'; import { InputValidator } from './private/validation'; @@ -103,10 +106,9 @@ export interface IPortfolio extends cdk.IResource { /** * Set provisioning rules for the product. * @param product A service catalog product. - * @param assertion A list of valid rules to apply. * @param options options for the constraint. */ - constrainProvisioningParameters(product:IProduct, assertion: TemplateRule, options?: CommonConstraintOptions): void; + constrainCloudFormationParameters(product:IProduct, options: ProvisioningRuleOptions): void; /** * Force users to assume a certain role when launching a product. @@ -169,8 +171,8 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { AssociationManager.notifyOnStackEvents(this, product, topic, options); } - public constrainProvisioningParameters(product: IProduct, assertion: TemplateRule, options: CommonConstraintOptions = {}): void { - AssociationManager.constrainProvisioningParameters(this, product, assertion, options); + public constrainCloudFormationParameters(product: IProduct, options: ProvisioningRuleOptions): void { + AssociationManager.constrainCloudFormationParameters(this, product, options); } public setLaunchRole(product: IProduct, launchRole: iam.IRole, options: CommonConstraintOptions = {}): void { diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts index e80d0819a16b2..27c928ef8e0bf 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts @@ -1,7 +1,10 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; -import { Assertion, CommonConstraintOptions, StackSetsConstraintOptions, TagUpdateConstraintOptions, TemplateRule } from '../constraints'; +import { + CommonConstraintOptions, ProvisioningRuleOptions, StackSetsConstraintOptions, + TagUpdateConstraintOptions, TemplateRule, TemplateRuleAssertion, +} from '../constraints'; import { IPortfolio } from '../portfolio'; import { IProduct } from '../product'; import { @@ -74,11 +77,13 @@ export class AssociationManager { } } - public static constrainProvisioningParameters(portfolio: IPortfolio, product: IProduct, - assertion: TemplateRule, options: CommonConstraintOptions): void { + public static constrainCloudFormationParameters( + portfolio: IPortfolio, product: IProduct, + options: ProvisioningRuleOptions, + ): void { this.validateCommonConstraintOptions(portfolio, product, options); const association = this.associateProductWithPortfolio(portfolio, product); - const constructId = `LaunchTemplateConstraint${hashValues(association.associationKey, assertion.ruleName)}`; + const constructId = `LaunchTemplateConstraint${hashValues(association.associationKey, options.assertion.ruleName)}`; if (!portfolio.node.tryFindChild(constructId)) { const constraint = new CfnLaunchTemplateConstraint(portfolio as unknown as cdk.Resource, constructId, { @@ -86,13 +91,13 @@ export class AssociationManager { description: options.description, portfolioId: portfolio.portfolioId, productId: product.productId, - rules: this.formatRule(portfolio as unknown as cdk.Resource, assertion), + rules: this.formatTemplateRule(portfolio.stack, options.assertion), }); // Add dependsOn to force proper order in deployment. constraint.addDependsOn(association.cfnPortfolioProductAssociation); } else { - throw new Error(`Provisioning rule ${assertion.ruleName} already configured on association ${this.prettyPrintAssociation(portfolio, product)}`); + throw new Error(`Provisioning rule ${options.assertion.ruleName} already configured on association ${this.prettyPrintAssociation(portfolio, product)}`); } } @@ -190,24 +195,28 @@ export class AssociationManager { return `- Portfolio: ${portfolio.node.path} | Product: ${product.node.path}`; } - private static formatAssertions(scope: cdk.Resource, assertions : Assertion[]): {[key: string]: string}[] { - const formattedAssertions: {[key: string]: string}[] = []; - assertions.forEach(assertion => formattedAssertions.push({ - Assert: scope.stack.resolve(assertion.assert), - ...(assertion.assertDescription) && { AssertDescription: assertion.assertDescription }, - })); - return formattedAssertions; - } - - private static formatRule(scope: cdk.Resource, rule: TemplateRule): string { + private static formatTemplateRule(scope: cdk.Stack, rule: TemplateRule): string { return JSON.stringify({ [rule.ruleName]: { Assertions: this.formatAssertions(scope, rule.assertions), - ...(rule.ruleCondition) && { RuleCondition: scope.stack.resolve(rule.ruleCondition) }, + RuleCondition: rule.condition ? scope.resolve(rule.condition) : undefined, }, }); } + private static formatAssertions(scope: cdk.Stack, assertions : TemplateRuleAssertion[]): {[key: string]: string | undefined }[] { + const formattedAssertions: {[key: string]: string | undefined }[] = []; + assertions.reduce((formattedAssertion, assertion) => { + formattedAssertion.push( { + Assert: scope.resolve(assertion.assert), + AssertDescription: assertion.description, + }); + return formattedAssertion; + }, formattedAssertions); + + return formattedAssertions; + }; + private static validateCommonConstraintOptions(portfolio: IPortfolio, product: IProduct, options: CommonConstraintOptions): void { InputValidator.validateLength(this.prettyPrintAssociation(portfolio, product), 'description', 0, 2000, options.description); } diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts index 7bec65be383a0..3b5d82b48107a 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts @@ -7,4 +7,4 @@ export function hashValues(...ids: string[]): string { const sha256 = crypto.createHash('sha256'); ids.forEach(val => sha256.update(val)); return sha256.digest('hex').slice(0, 12); -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts index 20debf14d72cf..6eae611e1c6be 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts @@ -79,13 +79,15 @@ secondPortfolio.deployWithStackSets(product, { allowStackSetInstanceOperations: true, }); -portfolio.constrainProvisioningParameters(product, { - ruleName: 'testInstanceType', - ruleCondition: cdk.Fn.conditionEquals(cdk.Fn.ref('Environment'), 'test'), - assertions: [{ - assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), - assertDescription: 'For test environment, the instance type should be small', - }], +portfolio.constrainCloudFormationParameters(product, { + assertion: { + ruleName: 'testInstanceType', + condition: cdk.Fn.conditionEquals(cdk.Fn.ref('Environment'), 'test'), + assertions: [{ + assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), + description: 'For test environment, the instance type should be small', + }], + }, }); app.synth(); diff --git a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts index 64f49bb5b3213..72962c2e5440d 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts @@ -461,17 +461,18 @@ describe('portfolio associations and product constraints', () => { test('set provisioning rule', () => { portfolio.addProduct(product); - portfolio.constrainProvisioningParameters(product, + portfolio.constrainCloudFormationParameters(product, { + assertion: { ruleName: 'Rule', assertions: [ { assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), - assertDescription: 'assert description', + description: 'assert description', }, ], }, - ); + }); expect(stack).toHaveResourceLike('AWS::ServiceCatalog::LaunchTemplateConstraint', { PortfolioId: { Ref: 'MyPortfolio59CCA9C9' }, @@ -490,70 +491,74 @@ describe('portfolio associations and product constraints', () => { }), test('set provisioning rule still creates without explicit association', () => { - portfolio.constrainProvisioningParameters(product, - { + portfolio.constrainCloudFormationParameters(product, { + assertion: { ruleName: 'Rule', - ruleCondition: cdk.Fn.conditionContains(['a', 'b'], 'text'), + condition: cdk.Fn.conditionContains(['a', 'b'], 'text'), assertions: [ { assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), - assertDescription: 'assert description', + description: 'assert description', }, { assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('OtherInstanceType')), - assertDescription: 'other assert description', + description: 'other assert description', }, ], - }, { - description: 'test description', - messageLanguage: servicecatalog.MessageLanguage.EN, }, + description: 'test description', + messageLanguage: servicecatalog.MessageLanguage.EN, + }, ); expect(stack).toHaveResourceLike('AWS::ServiceCatalog::LaunchTemplateConstraint'); }), test('set multiple provisioning rules', () => { - portfolio.constrainProvisioningParameters(product, - { + portfolio.constrainCloudFormationParameters(product, { + assertion: { ruleName: 'Rule01', assertions: [{ assert: cdk.Fn.conditionContains(['BucketOwnerRead'], cdk.Fn.ref('AccessControl')), - assertDescription: 'assert description', + description: 'assert description', }], - }); + }, + }); - portfolio.constrainProvisioningParameters(product, - { + portfolio.constrainCloudFormationParameters(product, { + assertion: { ruleName: 'Rule02', assertions: [{ assert: cdk.Fn.conditionContains(['BucketOwnerWrite'], cdk.Fn.ref('AccessControl')), - assertDescription: 'assert description', + description: 'assert description', }], - }); + }, + }); expect(stack).toCountResources('AWS::ServiceCatalog::LaunchTemplateConstraint', 2); }), test('fails to set a duplicate provisioning rule', () => { - portfolio.constrainProvisioningParameters(product, - { + portfolio.constrainCloudFormationParameters(product, { + assertion: { ruleName: 'Rule01', assertions: [{ assert: cdk.Fn.conditionContains(['BucketOwnerRead'], cdk.Fn.ref('AccessControl')), - assertDescription: 'assert description', + description: 'assert description', }], - }); + }, + }); expect(() => { - portfolio.constrainProvisioningParameters(product, - { + portfolio.constrainCloudFormationParameters(product, { + assertion: { ruleName: 'Rule01', assertions: [{ assert: cdk.Fn.conditionContains(['BucketOwnerWrite'], cdk.Fn.ref('AccessControl')), - assertDescription: 'assert description', + description: 'assert description', }], - }); + }, + }); }).toThrowError(/Provisioning rule Rule01 already configured on association/); }), From cd721bd2e282f75a6891b1c19f59d1cf98c79bd6 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Tue, 27 Jul 2021 12:46:02 -0400 Subject: [PATCH 03/16] fixing typo --- packages/@aws-cdk/aws-servicecatalog/README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index da20669621bf7..fcabbedeb0a86 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -244,13 +244,15 @@ see [AWS Rule Functions](https://docs.aws.amazon.com/AWSCloudFormation/latest/Us import * as cdk from '@aws-cdk/core'; portfolio.constrainCloudFormationParameters(product, { - ruleName: 'testInstanceType', - ruleCondition: cdk.Fn.conditionEquals(cdk.Fn.ref("Environment"), 'test'), - assertions: [ { - assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), - assertDescription: 'For test environment, the instance type should be small', - }] -}) + assertion: { + ruleName: 'testInstanceType', + condition: cdk.Fn.conditionEquals(cdk.Fn.ref('Environment'), 'test'), + assertions: [{ + assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), + description: 'For test environment, the instance type should be small', + }], + }, +}); ``` ### Set launch role From c927a1880d8b82738e4e90d96ad3b4fbe9e37894 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Tue, 27 Jul 2021 20:09:08 -0400 Subject: [PATCH 04/16] addressing PR comments --- .../@aws-cdk/aws-servicecatalog/README.md | 8 ++-- .../aws-servicecatalog/lib/constraints.ts | 6 +-- .../lib/private/association-manager.ts | 41 ++++++++----------- .../test/integ.portfolio.ts | 2 +- .../aws-servicecatalog/test/portfolio.test.ts | 20 ++++----- packages/@aws-cdk/core/lib/cfn-condition.ts | 11 +++++ packages/@aws-cdk/core/lib/cfn-fn.ts | 18 ++++---- 7 files changed, 51 insertions(+), 55 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index fcabbedeb0a86..854a197929f82 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -35,7 +35,7 @@ enables organizations to create and manage catalogs of products for their end us - [Constraints](#constraints) - [Tag update constraint](#tag-update-constraint) - [Notify on stack events](#notify-on-stack-events) - - [Constrain Cloudformation parameters](#constrain-cloudformation-parameters) + - [Cloudformation parameters constraint](#cloudformation-parameters-constraint) - [Set launch role](#set-launch-role) - [Deploy with StackSets](#deploy-with-stacksets) @@ -229,14 +229,14 @@ portfolio.notifyOnStackEvents(product, topic2, { }); ``` -### Constrain Cloudformation parameters +### Cloudformation parameters constraint Allows you to configure the options that are available to end users when they launch a product. A provisioning rule consists of one or more assertions that narrow the allowable values for parameters in a product. You can configure multiple rules to govern the different parameters and parameter options in your products. For example, a parameter might define the various instance types that users can choose from when launching a stack that includes EC2 instances. -A provisoning rule has an optional `ruleCondition` field that allows ability to configure when rules are applied. -If a `ruleCondition` is specified, all the assertions will be applied if the condition evalutates to true. +A provisioning rule has an optional `condition` field that allows ability to configure when rules are applied. +If a `condition` is specified, all the assertions will be applied if the condition evalutates to true. For information on rule-specific intrinsic functions to define rule conditions and assertions, see [AWS Rule Functions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-rules.html). diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts b/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts index 8d1093817a0c3..c1382c55ad708 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts @@ -72,7 +72,7 @@ export interface TemplateRuleAssertion { /** * The assertion condition. */ - readonly assert: cdk.ICfnConditionExpression; + readonly assert: cdk.ICfnRuleConditionExpression; /** * The description for the asssertion @@ -94,7 +94,7 @@ export interface TemplateRule { * Specify when to apply rule with a rule-specific intrinsic function. * @default - No rule condition provided. */ - readonly condition?: cdk.ICfnConditionExpression; + readonly condition?: cdk.ICfnRuleConditionExpression; /** * A list of assertions that make up the rule. @@ -109,5 +109,5 @@ export interface ProvisioningRuleOptions extends CommonConstraintOptions { /** * The rule with condition and assertions to apply to template. */ - readonly assertion: TemplateRule; + readonly rule: TemplateRule; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts index 27c928ef8e0bf..7323d4250bdf0 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts @@ -17,8 +17,9 @@ import { InputValidator } from './validation'; export class AssociationManager { public static associateProductWithPortfolio( - portfolio: IPortfolio, product: IProduct, + portfolio: IPortfolio, product: IProduct, options?: CommonConstraintOptions, ): { associationKey: string, cfnPortfolioProductAssociation: CfnPortfolioProductAssociation } { + InputValidator.validateLength(this.prettyPrintAssociation(portfolio, product), 'description', 0, 2000, options?.description); const associationKey = hashValues(portfolio.node.addr, product.node.addr, product.stack.node.addr); const constructId = `PortfolioProductAssociation${associationKey}`; const existingAssociation = portfolio.node.tryFindChild(constructId); @@ -36,7 +37,6 @@ export class AssociationManager { } public static constrainTagUpdates(portfolio: IPortfolio, product: IProduct, options: TagUpdateConstraintOptions): void { - this.validateCommonConstraintOptions(portfolio, product, options); const association = this.associateProductWithPortfolio(portfolio, product); const constructId = `ResourceUpdateConstraint${association.associationKey}`; @@ -57,7 +57,6 @@ export class AssociationManager { } public static notifyOnStackEvents(portfolio: IPortfolio, product: IProduct, topic: sns.ITopic, options: CommonConstraintOptions): void { - this.validateCommonConstraintOptions(portfolio, product, options); const association = this.associateProductWithPortfolio(portfolio, product); const constructId = `LaunchNotificationConstraint${hashValues(topic.node.addr, topic.stack.node.addr, association.associationKey)}`; @@ -81,9 +80,8 @@ export class AssociationManager { portfolio: IPortfolio, product: IProduct, options: ProvisioningRuleOptions, ): void { - this.validateCommonConstraintOptions(portfolio, product, options); const association = this.associateProductWithPortfolio(portfolio, product); - const constructId = `LaunchTemplateConstraint${hashValues(association.associationKey, options.assertion.ruleName)}`; + const constructId = `LaunchTemplateConstraint${hashValues(association.associationKey, options.rule.ruleName)}`; if (!portfolio.node.tryFindChild(constructId)) { const constraint = new CfnLaunchTemplateConstraint(portfolio as unknown as cdk.Resource, constructId, { @@ -91,18 +89,17 @@ export class AssociationManager { description: options.description, portfolioId: portfolio.portfolioId, productId: product.productId, - rules: this.formatTemplateRule(portfolio.stack, options.assertion), + rules: this.formatTemplateRule(portfolio.stack, options.rule), }); // Add dependsOn to force proper order in deployment. constraint.addDependsOn(association.cfnPortfolioProductAssociation); } else { - throw new Error(`Provisioning rule ${options.assertion.ruleName} already configured on association ${this.prettyPrintAssociation(portfolio, product)}`); + throw new Error(`Provisioning rule ${options.rule.ruleName} already configured on association ${this.prettyPrintAssociation(portfolio, product)}`); } } public static setLaunchRole(portfolio: IPortfolio, product: IProduct, launchRole: iam.IRole, options: CommonConstraintOptions): void { - this.validateCommonConstraintOptions(portfolio, product, options); const association = this.associateProductWithPortfolio(portfolio, product); // Check if a stackset deployment constraint has already been configured. if (portfolio.node.tryFindChild(this.stackSetConstraintLogicalId(association.associationKey))) { @@ -127,7 +124,6 @@ export class AssociationManager { } public static deployWithStackSets(portfolio: IPortfolio, product: IProduct, options: StackSetsConstraintOptions) { - this.validateCommonConstraintOptions(portfolio, product, options); const association = this.associateProductWithPortfolio(portfolio, product); // Check if a launch role has already been set. if (portfolio.node.tryFindChild(this.launchRoleConstraintLogicalId(association.associationKey))) { @@ -195,30 +191,25 @@ export class AssociationManager { return `- Portfolio: ${portfolio.node.path} | Product: ${product.node.path}`; } - private static formatTemplateRule(scope: cdk.Stack, rule: TemplateRule): string { + private static formatTemplateRule(stack: cdk.Stack, rule: TemplateRule): string { return JSON.stringify({ [rule.ruleName]: { - Assertions: this.formatAssertions(scope, rule.assertions), - RuleCondition: rule.condition ? scope.resolve(rule.condition) : undefined, + Assertions: this.formatAssertions(stack, rule.assertions), + RuleCondition: rule.condition ? stack.resolve(rule.condition) : undefined, }, }); } - private static formatAssertions(scope: cdk.Stack, assertions : TemplateRuleAssertion[]): {[key: string]: string | undefined }[] { - const formattedAssertions: {[key: string]: string | undefined }[] = []; - assertions.reduce((formattedAssertion, assertion) => { - formattedAssertion.push( { - Assert: scope.resolve(assertion.assert), + private static formatAssertions( + stack: cdk.Stack, assertions : TemplateRuleAssertion[], + ): { Assert: string, AssertDescription: string | undefined }[] { + return assertions.reduce((formattedAssertions, assertion) => { + formattedAssertions.push( { + Assert: stack.resolve(assertion.assert), AssertDescription: assertion.description, }); - return formattedAssertion; - }, formattedAssertions); - - return formattedAssertions; + return formattedAssertions; + }, Array<{ Assert: string, AssertDescription: string | undefined }>()); }; - - private static validateCommonConstraintOptions(portfolio: IPortfolio, product: IProduct, options: CommonConstraintOptions): void { - InputValidator.validateLength(this.prettyPrintAssociation(portfolio, product), 'description', 0, 2000, options.description); - } } diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts index 6eae611e1c6be..3bd2d2a535cac 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts @@ -80,7 +80,7 @@ secondPortfolio.deployWithStackSets(product, { }); portfolio.constrainCloudFormationParameters(product, { - assertion: { + rule: { ruleName: 'testInstanceType', condition: cdk.Fn.conditionEquals(cdk.Fn.ref('Environment'), 'test'), assertions: [{ diff --git a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts index 72962c2e5440d..7494ce0eb32be 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts @@ -462,8 +462,7 @@ describe('portfolio associations and product constraints', () => { test('set provisioning rule', () => { portfolio.addProduct(product); portfolio.constrainCloudFormationParameters(product, { - assertion: - { + rule: { ruleName: 'Rule', assertions: [ { @@ -492,7 +491,7 @@ describe('portfolio associations and product constraints', () => { test('set provisioning rule still creates without explicit association', () => { portfolio.constrainCloudFormationParameters(product, { - assertion: { + rule: { ruleName: 'Rule', condition: cdk.Fn.conditionContains(['a', 'b'], 'text'), assertions: [ @@ -500,23 +499,18 @@ describe('portfolio associations and product constraints', () => { assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), description: 'assert description', }, - { - assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('OtherInstanceType')), - description: 'other assert description', - }, ], }, description: 'test description', messageLanguage: servicecatalog.MessageLanguage.EN, - }, - ); + }); expect(stack).toHaveResourceLike('AWS::ServiceCatalog::LaunchTemplateConstraint'); }), test('set multiple provisioning rules', () => { portfolio.constrainCloudFormationParameters(product, { - assertion: { + rule: { ruleName: 'Rule01', assertions: [{ assert: cdk.Fn.conditionContains(['BucketOwnerRead'], cdk.Fn.ref('AccessControl')), @@ -526,7 +520,7 @@ describe('portfolio associations and product constraints', () => { }); portfolio.constrainCloudFormationParameters(product, { - assertion: { + rule: { ruleName: 'Rule02', assertions: [{ assert: cdk.Fn.conditionContains(['BucketOwnerWrite'], cdk.Fn.ref('AccessControl')), @@ -540,7 +534,7 @@ describe('portfolio associations and product constraints', () => { test('fails to set a duplicate provisioning rule', () => { portfolio.constrainCloudFormationParameters(product, { - assertion: { + rule: { ruleName: 'Rule01', assertions: [{ assert: cdk.Fn.conditionContains(['BucketOwnerRead'], cdk.Fn.ref('AccessControl')), @@ -551,7 +545,7 @@ describe('portfolio associations and product constraints', () => { expect(() => { portfolio.constrainCloudFormationParameters(product, { - assertion: { + rule: { ruleName: 'Rule01', assertions: [{ assert: cdk.Fn.conditionContains(['BucketOwnerWrite'], cdk.Fn.ref('AccessControl')), diff --git a/packages/@aws-cdk/core/lib/cfn-condition.ts b/packages/@aws-cdk/core/lib/cfn-condition.ts index db3fe5915e512..1d0a2ffca33db 100644 --- a/packages/@aws-cdk/core/lib/cfn-condition.ts +++ b/packages/@aws-cdk/core/lib/cfn-condition.ts @@ -87,3 +87,14 @@ export class CfnCondition extends CfnElement implements ICfnConditionExpression, * ``` */ export interface ICfnConditionExpression extends IResolvable { } + +/** + * Interface to specify certain functions as rule-specifc. + * These functions can only be used in ``Rules`` section of template. + */ + export interface ICfnRuleConditionExpression extends ICfnConditionExpression { + /** + * Optional field to determine if condition should be used. + */ + readonly disambiguator?: boolean; +} diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index d8a871ca478e1..2724f1f9964ad 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -1,4 +1,4 @@ -import { ICfnConditionExpression } from './cfn-condition'; +import { ICfnConditionExpression, ICfnRuleConditionExpression } from './cfn-condition'; import { minimalCloudFormationJoin } from './private/cloudformation-lang'; import { Intrinsic } from './private/intrinsic'; import { Reference } from './reference'; @@ -267,7 +267,7 @@ export class Fn { * @param conditions conditions to AND * @returns an FnCondition token */ - public static conditionAnd(...conditions: ICfnConditionExpression[]): ICfnConditionExpression { + public static conditionAnd(...conditions: ICfnConditionExpression[]): ICfnRuleConditionExpression { if (conditions.length === 0) { throw new Error('Fn.conditionAnd() needs at least one argument'); } @@ -284,7 +284,7 @@ export class Fn { * @param rhs A value of any type that you want to compare. * @returns an FnCondition token */ - public static conditionEquals(lhs: any, rhs: any): ICfnConditionExpression { + public static conditionEquals(lhs: any, rhs: any): ICfnRuleConditionExpression { return new FnEquals(lhs, rhs); } @@ -303,7 +303,7 @@ export class Fn { * evaluates to false. * @returns an FnCondition token */ - public static conditionIf(conditionId: string, valueIfTrue: any, valueIfFalse: any): ICfnConditionExpression { + public static conditionIf(conditionId: string, valueIfTrue: any, valueIfFalse: any): ICfnRuleConditionExpression { return new FnIf(conditionId, valueIfTrue, valueIfFalse); } @@ -314,7 +314,7 @@ export class Fn { * or false. * @returns an FnCondition token */ - public static conditionNot(condition: ICfnConditionExpression): ICfnConditionExpression { + public static conditionNot(condition: ICfnConditionExpression): ICfnRuleConditionExpression { return new FnNot(condition); } @@ -326,7 +326,7 @@ export class Fn { * @param conditions conditions that evaluates to true or false. * @returns an FnCondition token */ - public static conditionOr(...conditions: ICfnConditionExpression[]): ICfnConditionExpression { + public static conditionOr(...conditions: ICfnConditionExpression[]): ICfnRuleConditionExpression { if (conditions.length === 0) { throw new Error('Fn.conditionOr() needs at least one argument'); } @@ -343,7 +343,7 @@ export class Fn { * @param value A string, such as "A", that you want to compare against a list of strings. * @returns an FnCondition token */ - public static conditionContains(listOfStrings: string[], value: string): ICfnConditionExpression { + public static conditionContains(listOfStrings: string[], value: string): ICfnRuleConditionExpression { return new FnContains(listOfStrings, value); } @@ -354,7 +354,7 @@ export class Fn { * of strings. * @returns an FnCondition token */ - public static conditionEachMemberEquals(listOfStrings: string[], value: string): ICfnConditionExpression { + public static conditionEachMemberEquals(listOfStrings: string[], value: string): ICfnRuleConditionExpression { return new FnEachMemberEquals(listOfStrings, value); } @@ -369,7 +369,7 @@ export class Fn { * strings_to_check parameter. * @returns an FnCondition token */ - public static conditionEachMemberIn(stringsToCheck: string[], stringsToMatch: string[]): ICfnConditionExpression { + public static conditionEachMemberIn(stringsToCheck: string[], stringsToMatch: string[]): ICfnRuleConditionExpression { return new FnEachMemberIn(stringsToCheck, stringsToMatch); } From 5d4057b865d4b47cb9f7bf52d54f97229e6398f6 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Thu, 29 Jul 2021 17:00:23 -0400 Subject: [PATCH 05/16] addresing more PR comments, added a field to to enable structural typing --- .../test/integ.portfolio.expected.json | 4 ++-- .../aws-servicecatalog/test/integ.portfolio.ts | 7 +++---- packages/@aws-cdk/core/lib/cfn-condition.ts | 10 +++++----- packages/@aws-cdk/core/lib/cfn-fn.ts | 7 ++++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json index 9411dbe4af087..8e9b4e988a896 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json @@ -202,7 +202,7 @@ "TestPortfolioPortfolioProductAssociationa0185761d231B0D998A7" ] }, - "TestPortfolioLaunchTemplateConstraint13a8b80248da4E047268": { + "TestPortfolioLaunchTemplateConstraintfac7b49c426e599F9FFF": { "Type": "AWS::ServiceCatalog::LaunchTemplateConstraint", "Properties": { "PortfolioId": { @@ -211,7 +211,7 @@ "ProductId": { "Ref": "TestProduct7606930B" }, - "Rules": "{\"testInstanceType\":{\"Assertions\":[{\"Assert\":{\"Fn::Contains\":[[\"t2.micro\",\"t2.small\"],{\"Ref\":\"InstanceType\"}]},\"AssertDescription\":\"For test environment, the instance type should be small\"}],\"RuleCondition\":{\"Fn::Equals\":[{\"Ref\":\"Environment\"},\"test\"]}}}" + "Rules": "{\"SubnetsinVPC\":{\"Assertions\":[{\"Assert\":{\"Fn::EachMemberIn\":[{\"Fn::ValueOfAll\":[\"AWs::EC2::Subnet::Id\",\"VpcId\"]},{\"Fn::RefAll\":\"AWS::EC2::VPC::Id\"}]},\"AssertDescription\":\"for the kids\"}]}}" }, "DependsOn": [ "TestPortfolioPortfolioProductAssociationa0185761d231B0D998A7" diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts index 3bd2d2a535cac..5926bcca7a689 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts @@ -81,11 +81,10 @@ secondPortfolio.deployWithStackSets(product, { portfolio.constrainCloudFormationParameters(product, { rule: { - ruleName: 'testInstanceType', - condition: cdk.Fn.conditionEquals(cdk.Fn.ref('Environment'), 'test'), + ruleName: 'SubnetsinVPC', assertions: [{ - assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), - description: 'For test environment, the instance type should be small', + assert: cdk.Fn.conditionEachMemberIn(cdk.Fn.valueOfAll('AWs::EC2::Subnet::Id', 'VpcId'), cdk.Fn.refAll('AWS::EC2::VPC::Id')), + description: 'for the kids', }], }, }); diff --git a/packages/@aws-cdk/core/lib/cfn-condition.ts b/packages/@aws-cdk/core/lib/cfn-condition.ts index 1d0a2ffca33db..60be53e529005 100644 --- a/packages/@aws-cdk/core/lib/cfn-condition.ts +++ b/packages/@aws-cdk/core/lib/cfn-condition.ts @@ -86,15 +86,15 @@ export class CfnCondition extends CfnElement implements ICfnConditionExpression, * }); * ``` */ -export interface ICfnConditionExpression extends IResolvable { } +export interface ICfnConditionExpression extends IResolvable {} /** - * Interface to specify certain functions as rule-specifc. + * Interface to specify certain functions as Service Catalog rule-specifc. * These functions can only be used in ``Rules`` section of template. */ - export interface ICfnRuleConditionExpression extends ICfnConditionExpression { +export interface ICfnRuleConditionExpression extends ICfnConditionExpression { /** - * Optional field to determine if condition should be used. + * This field is here for typing service catalog rule specific intrinsic functions. */ - readonly disambiguator?: boolean; + readonly serviceCatalogdisambiguator: boolean; } diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index 2724f1f9964ad..5f37250a3eab5 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -272,7 +272,7 @@ export class Fn { throw new Error('Fn.conditionAnd() needs at least one argument'); } if (conditions.length === 1) { - return conditions[0]; + return conditions[0] as ICfnRuleConditionExpression; } return Fn.conditionAnd(..._inGroupsOf(conditions, 10).map(group => new FnAnd(...group))); } @@ -331,7 +331,7 @@ export class Fn { throw new Error('Fn.conditionOr() needs at least one argument'); } if (conditions.length === 1) { - return conditions[0]; + return conditions[0] as ICfnRuleConditionExpression; } return Fn.conditionOr(..._inGroupsOf(conditions, 10).map(group => new FnOr(...group))); } @@ -604,7 +604,8 @@ class FnCidr extends FnBase { } } -class FnConditionBase extends Intrinsic implements ICfnConditionExpression { +class FnConditionBase extends Intrinsic implements ICfnRuleConditionExpression { + readonly serviceCatalogdisambiguator: boolean = true; constructor(type: string, value: any) { super({ [type]: value }); } From 4335c873d8fcba8f9f7869c3927ec0bfbd73e91b Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Thu, 29 Jul 2021 17:07:32 -0400 Subject: [PATCH 06/16] fixing a description field --- .../aws-servicecatalog/test/integ.portfolio.expected.json | 2 +- packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json index 8e9b4e988a896..5407c293f09b5 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json @@ -211,7 +211,7 @@ "ProductId": { "Ref": "TestProduct7606930B" }, - "Rules": "{\"SubnetsinVPC\":{\"Assertions\":[{\"Assert\":{\"Fn::EachMemberIn\":[{\"Fn::ValueOfAll\":[\"AWs::EC2::Subnet::Id\",\"VpcId\"]},{\"Fn::RefAll\":\"AWS::EC2::VPC::Id\"}]},\"AssertDescription\":\"for the kids\"}]}}" + "Rules": "{\"SubnetsinVPC\":{\"Assertions\":[{\"Assert\":{\"Fn::EachMemberIn\":[{\"Fn::ValueOfAll\":[\"AWs::EC2::Subnet::Id\",\"VpcId\"]},{\"Fn::RefAll\":\"AWS::EC2::VPC::Id\"}]},\"AssertDescription\":\"test description\"}]}}" }, "DependsOn": [ "TestPortfolioPortfolioProductAssociationa0185761d231B0D998A7" diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts index 5926bcca7a689..b45c4662791c4 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts @@ -84,7 +84,7 @@ portfolio.constrainCloudFormationParameters(product, { ruleName: 'SubnetsinVPC', assertions: [{ assert: cdk.Fn.conditionEachMemberIn(cdk.Fn.valueOfAll('AWs::EC2::Subnet::Id', 'VpcId'), cdk.Fn.refAll('AWS::EC2::VPC::Id')), - description: 'for the kids', + description: 'test description', }], }, }); From ddbd6e160f74f15d6d8abd5235b971e504231c69 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Thu, 29 Jul 2021 17:11:25 -0400 Subject: [PATCH 07/16] fixing integ test expected with updated description --- .../aws-servicecatalog/test/integ.portfolio.expected.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json index 5407c293f09b5..8e9b4e988a896 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json @@ -211,7 +211,7 @@ "ProductId": { "Ref": "TestProduct7606930B" }, - "Rules": "{\"SubnetsinVPC\":{\"Assertions\":[{\"Assert\":{\"Fn::EachMemberIn\":[{\"Fn::ValueOfAll\":[\"AWs::EC2::Subnet::Id\",\"VpcId\"]},{\"Fn::RefAll\":\"AWS::EC2::VPC::Id\"}]},\"AssertDescription\":\"test description\"}]}}" + "Rules": "{\"SubnetsinVPC\":{\"Assertions\":[{\"Assert\":{\"Fn::EachMemberIn\":[{\"Fn::ValueOfAll\":[\"AWs::EC2::Subnet::Id\",\"VpcId\"]},{\"Fn::RefAll\":\"AWS::EC2::VPC::Id\"}]},\"AssertDescription\":\"for the kids\"}]}}" }, "DependsOn": [ "TestPortfolioPortfolioProductAssociationa0185761d231B0D998A7" From 20df32a2346a99eb813bc990e4ee3461a2635b60 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Thu, 29 Jul 2021 17:14:38 -0400 Subject: [PATCH 08/16] fixing camel case --- packages/@aws-cdk/core/lib/cfn-condition.ts | 2 +- packages/@aws-cdk/core/lib/cfn-rule.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/core/lib/cfn-condition.ts b/packages/@aws-cdk/core/lib/cfn-condition.ts index 60be53e529005..3b433d144f6da 100644 --- a/packages/@aws-cdk/core/lib/cfn-condition.ts +++ b/packages/@aws-cdk/core/lib/cfn-condition.ts @@ -96,5 +96,5 @@ export interface ICfnRuleConditionExpression extends ICfnConditionExpression { /** * This field is here for typing service catalog rule specific intrinsic functions. */ - readonly serviceCatalogdisambiguator: boolean; + readonly serviceCatalogDisambiguator: boolean; } diff --git a/packages/@aws-cdk/core/lib/cfn-rule.ts b/packages/@aws-cdk/core/lib/cfn-rule.ts index f3141ca636ce2..6d8e9c29b9303 100644 --- a/packages/@aws-cdk/core/lib/cfn-rule.ts +++ b/packages/@aws-cdk/core/lib/cfn-rule.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { ICfnConditionExpression } from './cfn-condition'; +import { ICfnRuleConditionExpression } from './cfn-condition'; import { CfnRefElement } from './cfn-element'; import { capitalizePropertyNames } from './util'; @@ -31,7 +31,7 @@ export interface CfnRuleProps { * * @default - Rule's assertions will always take effect. */ - readonly ruleCondition?: ICfnConditionExpression; + readonly ruleCondition?: ICfnRuleConditionExpression; /** * Assertions which define the rule. @@ -57,7 +57,7 @@ export interface CfnRuleProps { * @link https://docs.aws.amazon.com/servicecatalog/latest/adminguide/reference-template_constraint_rules.html */ export class CfnRule extends CfnRefElement { - private ruleCondition?: ICfnConditionExpression; + private ruleCondition?: ICfnRuleConditionExpression; private assertions?: CfnRuleAssertion[]; /** @@ -77,7 +77,7 @@ export class CfnRule extends CfnRefElement { * @param condition The expression to evaluation. * @param description The description of the assertion. */ - public addAssertion(condition: ICfnConditionExpression, description: string) { + public addAssertion(condition: ICfnRuleConditionExpression, description: string) { if (!this.assertions) { this.assertions = []; } @@ -110,7 +110,7 @@ export interface CfnRuleAssertion { /** * The assertion. */ - readonly assert: ICfnConditionExpression; + readonly assert: ICfnRuleConditionExpression; /** * The assertion description. From 28c63699b07bb439f7c12a05cb9f24edb6701d93 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Thu, 29 Jul 2021 17:26:28 -0400 Subject: [PATCH 09/16] changing name of field in rule expression back to disambiguator --- packages/@aws-cdk/core/lib/cfn-condition.ts | 2 +- packages/@aws-cdk/core/lib/cfn-fn.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/core/lib/cfn-condition.ts b/packages/@aws-cdk/core/lib/cfn-condition.ts index 3b433d144f6da..83c9c4baff715 100644 --- a/packages/@aws-cdk/core/lib/cfn-condition.ts +++ b/packages/@aws-cdk/core/lib/cfn-condition.ts @@ -96,5 +96,5 @@ export interface ICfnRuleConditionExpression extends ICfnConditionExpression { /** * This field is here for typing service catalog rule specific intrinsic functions. */ - readonly serviceCatalogDisambiguator: boolean; + readonly disambiguator: boolean; } diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index 5f37250a3eab5..dc343cc653d7b 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -605,7 +605,7 @@ class FnCidr extends FnBase { } class FnConditionBase extends Intrinsic implements ICfnRuleConditionExpression { - readonly serviceCatalogdisambiguator: boolean = true; + readonly disambiguator: boolean = true; constructor(type: string, value: any) { super({ [type]: value }); } From a586aa80ef2cc0f9ada180d3ce827d9a10cafdab Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Thu, 29 Jul 2021 18:18:24 -0400 Subject: [PATCH 10/16] integ expected file not uploaded as expected --- .../aws-servicecatalog/test/integ.portfolio.expected.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json index 8e9b4e988a896..5407c293f09b5 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.expected.json @@ -211,7 +211,7 @@ "ProductId": { "Ref": "TestProduct7606930B" }, - "Rules": "{\"SubnetsinVPC\":{\"Assertions\":[{\"Assert\":{\"Fn::EachMemberIn\":[{\"Fn::ValueOfAll\":[\"AWs::EC2::Subnet::Id\",\"VpcId\"]},{\"Fn::RefAll\":\"AWS::EC2::VPC::Id\"}]},\"AssertDescription\":\"for the kids\"}]}}" + "Rules": "{\"SubnetsinVPC\":{\"Assertions\":[{\"Assert\":{\"Fn::EachMemberIn\":[{\"Fn::ValueOfAll\":[\"AWs::EC2::Subnet::Id\",\"VpcId\"]},{\"Fn::RefAll\":\"AWS::EC2::VPC::Id\"}]},\"AssertDescription\":\"test description\"}]}}" }, "DependsOn": [ "TestPortfolioPortfolioProductAssociationa0185761d231B0D998A7" From 32a720031fb9bc81dc594fa1a66d8a7decd7a411 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Thu, 29 Jul 2021 21:06:49 -0400 Subject: [PATCH 11/16] fixing last commit that deleted file instead of unstaging --- packages/@aws-cdk/aws-servicecatalog/README.md | 6 +++--- .../aws-servicecatalog/lib/constraints.ts | 6 +++--- .../aws-servicecatalog/lib/portfolio.ts | 10 +++++----- .../lib/private/association-manager.ts | 18 +++++++++--------- .../aws-servicecatalog/test/integ.portfolio.ts | 4 +++- .../aws-servicecatalog/test/portfolio.test.ts | 8 ++++---- packages/@aws-cdk/core/lib/cfn-condition.ts | 3 ++- packages/@aws-cdk/core/lib/cfn-fn.ts | 2 +- 8 files changed, 30 insertions(+), 27 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index 854a197929f82..bd770945eaf49 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -35,7 +35,7 @@ enables organizations to create and manage catalogs of products for their end us - [Constraints](#constraints) - [Tag update constraint](#tag-update-constraint) - [Notify on stack events](#notify-on-stack-events) - - [Cloudformation parameters constraint](#cloudformation-parameters-constraint) + - [CloudFormation parameters constraint](#cloudformation-parameters-constraint) - [Set launch role](#set-launch-role) - [Deploy with StackSets](#deploy-with-stacksets) @@ -229,7 +229,7 @@ portfolio.notifyOnStackEvents(product, topic2, { }); ``` -### Cloudformation parameters constraint +### CloudFormation parameters constraint Allows you to configure the options that are available to end users when they launch a product. A provisioning rule consists of one or more assertions that narrow the allowable values for parameters in a product. @@ -244,7 +244,7 @@ see [AWS Rule Functions](https://docs.aws.amazon.com/AWSCloudFormation/latest/Us import * as cdk from '@aws-cdk/core'; portfolio.constrainCloudFormationParameters(product, { - assertion: { + rule: { ruleName: 'testInstanceType', condition: cdk.Fn.conditionEquals(cdk.Fn.ref('Environment'), 'test'), assertions: [{ diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts b/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts index c1382c55ad708..f3e2c32297003 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/constraints.ts @@ -75,7 +75,7 @@ export interface TemplateRuleAssertion { readonly assert: cdk.ICfnRuleConditionExpression; /** - * The description for the asssertion + * The description for the asssertion. * @default - no description provided for the assertion. */ readonly description?: string; @@ -92,7 +92,7 @@ export interface TemplateRule { /** * Specify when to apply rule with a rule-specific intrinsic function. - * @default - No rule condition provided. + * @default - no rule condition provided */ readonly condition?: cdk.ICfnRuleConditionExpression; @@ -105,7 +105,7 @@ export interface TemplateRule { /** * Properties for provisoning rule constraint. */ -export interface ProvisioningRuleOptions extends CommonConstraintOptions { +export interface CloudFormationRuleConstraintOptions extends CommonConstraintOptions { /** * The rule with condition and assertions to apply to template. */ diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts index 9b80f69d37ee0..3056a48e19777 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts @@ -3,8 +3,8 @@ import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; import { MessageLanguage } from './common'; import { - CommonConstraintOptions, StackSetsConstraintOptions, - TagUpdateConstraintOptions, ProvisioningRuleOptions, + CloudFormationRuleConstraintOptions, CommonConstraintOptions, + StackSetsConstraintOptions, TagUpdateConstraintOptions, } from './constraints'; import { AssociationManager } from './private/association-manager'; import { hashValues } from './private/util'; @@ -108,7 +108,7 @@ export interface IPortfolio extends cdk.IResource { * @param product A service catalog product. * @param options options for the constraint. */ - constrainCloudFormationParameters(product:IProduct, options: ProvisioningRuleOptions): void; + constrainCloudFormationParameters(product:IProduct, options: CloudFormationRuleConstraintOptions): void; /** * Force users to assume a certain role when launching a product. @@ -146,7 +146,7 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { } public addProduct(product: IProduct): void { - AssociationManager.associateProductWithPortfolio(this, product); + AssociationManager.associateProductWithPortfolio(this, product, undefined); } public shareWithAccount(accountId: string, options: PortfolioShareOptions = {}): void { @@ -171,7 +171,7 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { AssociationManager.notifyOnStackEvents(this, product, topic, options); } - public constrainCloudFormationParameters(product: IProduct, options: ProvisioningRuleOptions): void { + public constrainCloudFormationParameters(product: IProduct, options: CloudFormationRuleConstraintOptions): void { AssociationManager.constrainCloudFormationParameters(this, product, options); } diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts index 7323d4250bdf0..bf5a68a8e70d3 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/association-manager.ts @@ -2,7 +2,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; import { - CommonConstraintOptions, ProvisioningRuleOptions, StackSetsConstraintOptions, + CloudFormationRuleConstraintOptions, CommonConstraintOptions, StackSetsConstraintOptions, TagUpdateConstraintOptions, TemplateRule, TemplateRuleAssertion, } from '../constraints'; import { IPortfolio } from '../portfolio'; @@ -17,7 +17,7 @@ import { InputValidator } from './validation'; export class AssociationManager { public static associateProductWithPortfolio( - portfolio: IPortfolio, product: IProduct, options?: CommonConstraintOptions, + portfolio: IPortfolio, product: IProduct, options: CommonConstraintOptions | undefined, ): { associationKey: string, cfnPortfolioProductAssociation: CfnPortfolioProductAssociation } { InputValidator.validateLength(this.prettyPrintAssociation(portfolio, product), 'description', 0, 2000, options?.description); const associationKey = hashValues(portfolio.node.addr, product.node.addr, product.stack.node.addr); @@ -37,7 +37,7 @@ export class AssociationManager { } public static constrainTagUpdates(portfolio: IPortfolio, product: IProduct, options: TagUpdateConstraintOptions): void { - const association = this.associateProductWithPortfolio(portfolio, product); + const association = this.associateProductWithPortfolio(portfolio, product, options); const constructId = `ResourceUpdateConstraint${association.associationKey}`; if (!portfolio.node.tryFindChild(constructId)) { @@ -57,7 +57,7 @@ export class AssociationManager { } public static notifyOnStackEvents(portfolio: IPortfolio, product: IProduct, topic: sns.ITopic, options: CommonConstraintOptions): void { - const association = this.associateProductWithPortfolio(portfolio, product); + const association = this.associateProductWithPortfolio(portfolio, product, options); const constructId = `LaunchNotificationConstraint${hashValues(topic.node.addr, topic.stack.node.addr, association.associationKey)}`; if (!portfolio.node.tryFindChild(constructId)) { @@ -78,9 +78,9 @@ export class AssociationManager { public static constrainCloudFormationParameters( portfolio: IPortfolio, product: IProduct, - options: ProvisioningRuleOptions, + options: CloudFormationRuleConstraintOptions, ): void { - const association = this.associateProductWithPortfolio(portfolio, product); + const association = this.associateProductWithPortfolio(portfolio, product, options); const constructId = `LaunchTemplateConstraint${hashValues(association.associationKey, options.rule.ruleName)}`; if (!portfolio.node.tryFindChild(constructId)) { @@ -100,7 +100,7 @@ export class AssociationManager { } public static setLaunchRole(portfolio: IPortfolio, product: IProduct, launchRole: iam.IRole, options: CommonConstraintOptions): void { - const association = this.associateProductWithPortfolio(portfolio, product); + const association = this.associateProductWithPortfolio(portfolio, product, options); // Check if a stackset deployment constraint has already been configured. if (portfolio.node.tryFindChild(this.stackSetConstraintLogicalId(association.associationKey))) { throw new Error(`Cannot set launch role when a StackSet rule is already defined for association ${this.prettyPrintAssociation(portfolio, product)}`); @@ -124,7 +124,7 @@ export class AssociationManager { } public static deployWithStackSets(portfolio: IPortfolio, product: IProduct, options: StackSetsConstraintOptions) { - const association = this.associateProductWithPortfolio(portfolio, product); + const association = this.associateProductWithPortfolio(portfolio, product, options); // Check if a launch role has already been set. if (portfolio.node.tryFindChild(this.launchRoleConstraintLogicalId(association.associationKey))) { throw new Error(`Cannot configure StackSet deployment when a launch role is already defined for association ${this.prettyPrintAssociation(portfolio, product)}`); @@ -209,7 +209,7 @@ export class AssociationManager { AssertDescription: assertion.description, }); return formattedAssertions; - }, Array<{ Assert: string, AssertDescription: string | undefined }>()); + }, new Array<{ Assert: string, AssertDescription: string | undefined }>()); }; } diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts index b45c4662791c4..a96c11a45ba3f 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.ts @@ -83,7 +83,9 @@ portfolio.constrainCloudFormationParameters(product, { rule: { ruleName: 'SubnetsinVPC', assertions: [{ - assert: cdk.Fn.conditionEachMemberIn(cdk.Fn.valueOfAll('AWs::EC2::Subnet::Id', 'VpcId'), cdk.Fn.refAll('AWS::EC2::VPC::Id')), + assert: cdk.Fn.conditionEachMemberIn( + cdk.Fn.valueOfAll('AWs::EC2::Subnet::Id', 'VpcId'), + cdk.Fn.refAll('AWS::EC2::VPC::Id')), description: 'test description', }], }, diff --git a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts index 7494ce0eb32be..bf6ef2c09b0ed 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts @@ -459,7 +459,7 @@ describe('portfolio associations and product constraints', () => { }).toThrowError(`Topic ${topic} is already subscribed to association`); }), - test('set provisioning rule', () => { + test('creates a CloudFormation parameters constraint', () => { portfolio.addProduct(product); portfolio.constrainCloudFormationParameters(product, { rule: { @@ -489,7 +489,7 @@ describe('portfolio associations and product constraints', () => { }); }), - test('set provisioning rule still creates without explicit association', () => { + test('CloudFormation parameters constraint still creates without explicit association', () => { portfolio.constrainCloudFormationParameters(product, { rule: { ruleName: 'Rule', @@ -508,7 +508,7 @@ describe('portfolio associations and product constraints', () => { expect(stack).toHaveResourceLike('AWS::ServiceCatalog::LaunchTemplateConstraint'); }), - test('set multiple provisioning rules', () => { + test('set multiple CloudFormation parameters constraints', () => { portfolio.constrainCloudFormationParameters(product, { rule: { ruleName: 'Rule01', @@ -532,7 +532,7 @@ describe('portfolio associations and product constraints', () => { expect(stack).toCountResources('AWS::ServiceCatalog::LaunchTemplateConstraint', 2); }), - test('fails to set a duplicate provisioning rule', () => { + test('fails to set a duplicate CloudFormation parameters constraint', () => { portfolio.constrainCloudFormationParameters(product, { rule: { ruleName: 'Rule01', diff --git a/packages/@aws-cdk/core/lib/cfn-condition.ts b/packages/@aws-cdk/core/lib/cfn-condition.ts index 83c9c4baff715..51a1e071de6a3 100644 --- a/packages/@aws-cdk/core/lib/cfn-condition.ts +++ b/packages/@aws-cdk/core/lib/cfn-condition.ts @@ -94,7 +94,8 @@ export interface ICfnConditionExpression extends IResolvable {} */ export interface ICfnRuleConditionExpression extends ICfnConditionExpression { /** - * This field is here for typing service catalog rule specific intrinsic functions. + * This field is only needed to defeat TypeScript's structural typing. + * It is never used. */ readonly disambiguator: boolean; } diff --git a/packages/@aws-cdk/core/lib/cfn-fn.ts b/packages/@aws-cdk/core/lib/cfn-fn.ts index dc343cc653d7b..3ef1c265654bd 100644 --- a/packages/@aws-cdk/core/lib/cfn-fn.ts +++ b/packages/@aws-cdk/core/lib/cfn-fn.ts @@ -605,7 +605,7 @@ class FnCidr extends FnBase { } class FnConditionBase extends Intrinsic implements ICfnRuleConditionExpression { - readonly disambiguator: boolean = true; + readonly disambiguator = true; constructor(type: string, value: any) { super({ [type]: value }); } From c1d190e7031ac93e5e223e8f2f8ce695d2cd10b5 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Thu, 29 Jul 2021 21:13:27 -0400 Subject: [PATCH 12/16] trying to remove changes to util file --- packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts index 3b5d82b48107a..7bec65be383a0 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/util.ts @@ -7,4 +7,4 @@ export function hashValues(...ids: string[]): string { const sha256 = crypto.createHash('sha256'); ids.forEach(val => sha256.update(val)); return sha256.digest('hex').slice(0, 12); -} \ No newline at end of file +} From 8cc8d284d59e69858d0231ff4e2e8c20de62b4b9 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Thu, 29 Jul 2021 21:41:26 -0400 Subject: [PATCH 13/16] updating language --- packages/@aws-cdk/aws-servicecatalog/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index bd770945eaf49..4bb6885c601eb 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -231,11 +231,11 @@ portfolio.notifyOnStackEvents(product, topic2, { ### CloudFormation parameters constraint -Allows you to configure the options that are available to end users when they launch a product. -A provisioning rule consists of one or more assertions that narrow the allowable values for parameters in a product. -You can configure multiple rules to govern the different parameters and parameter options in your products. -For example, a parameter might define the various instance types that users can choose from when launching a stack that includes EC2 instances. -A provisioning rule has an optional `condition` field that allows ability to configure when rules are applied. +CloudFormation parameters constraints allow you to configure the that are available to end users when they launch a product via defined rules. +A rule consists of one or more assertions that narrow the allowable values for parameters in a product. +You can configure multiple parameter constraints to govern the different parameters and parameter options in your products. +For example, a rule might define the various instance types that users can choose from when launching a stack that includes EC2 instances. +A parameter rule has an optional `condition` field that allows ability to configure when rules are applied. If a `condition` is specified, all the assertions will be applied if the condition evalutates to true. For information on rule-specific intrinsic functions to define rule conditions and assertions, see [AWS Rule Functions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-rules.html). From f3887baf28c433c54b550920e888751f2d7391b7 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Thu, 29 Jul 2021 23:09:14 -0400 Subject: [PATCH 14/16] checking if build failure is related to rule condition change --- packages/@aws-cdk/core/lib/cfn-rule.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/core/lib/cfn-rule.ts b/packages/@aws-cdk/core/lib/cfn-rule.ts index 6d8e9c29b9303..f3141ca636ce2 100644 --- a/packages/@aws-cdk/core/lib/cfn-rule.ts +++ b/packages/@aws-cdk/core/lib/cfn-rule.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { ICfnRuleConditionExpression } from './cfn-condition'; +import { ICfnConditionExpression } from './cfn-condition'; import { CfnRefElement } from './cfn-element'; import { capitalizePropertyNames } from './util'; @@ -31,7 +31,7 @@ export interface CfnRuleProps { * * @default - Rule's assertions will always take effect. */ - readonly ruleCondition?: ICfnRuleConditionExpression; + readonly ruleCondition?: ICfnConditionExpression; /** * Assertions which define the rule. @@ -57,7 +57,7 @@ export interface CfnRuleProps { * @link https://docs.aws.amazon.com/servicecatalog/latest/adminguide/reference-template_constraint_rules.html */ export class CfnRule extends CfnRefElement { - private ruleCondition?: ICfnRuleConditionExpression; + private ruleCondition?: ICfnConditionExpression; private assertions?: CfnRuleAssertion[]; /** @@ -77,7 +77,7 @@ export class CfnRule extends CfnRefElement { * @param condition The expression to evaluation. * @param description The description of the assertion. */ - public addAssertion(condition: ICfnRuleConditionExpression, description: string) { + public addAssertion(condition: ICfnConditionExpression, description: string) { if (!this.assertions) { this.assertions = []; } @@ -110,7 +110,7 @@ export interface CfnRuleAssertion { /** * The assertion. */ - readonly assert: ICfnRuleConditionExpression; + readonly assert: ICfnConditionExpression; /** * The assertion description. From a905e09609ed7950a13054f404cbf72ed6d084af Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Fri, 30 Jul 2021 00:53:40 -0400 Subject: [PATCH 15/16] one final test of cfn-rule change --- packages/@aws-cdk/core/lib/cfn-condition.ts | 2 +- packages/@aws-cdk/core/lib/cfn-rule.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/core/lib/cfn-condition.ts b/packages/@aws-cdk/core/lib/cfn-condition.ts index 51a1e071de6a3..dd69bac083960 100644 --- a/packages/@aws-cdk/core/lib/cfn-condition.ts +++ b/packages/@aws-cdk/core/lib/cfn-condition.ts @@ -86,7 +86,7 @@ export class CfnCondition extends CfnElement implements ICfnConditionExpression, * }); * ``` */ -export interface ICfnConditionExpression extends IResolvable {} +export interface ICfnConditionExpression extends IResolvable { } /** * Interface to specify certain functions as Service Catalog rule-specifc. diff --git a/packages/@aws-cdk/core/lib/cfn-rule.ts b/packages/@aws-cdk/core/lib/cfn-rule.ts index f3141ca636ce2..6d8e9c29b9303 100644 --- a/packages/@aws-cdk/core/lib/cfn-rule.ts +++ b/packages/@aws-cdk/core/lib/cfn-rule.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { ICfnConditionExpression } from './cfn-condition'; +import { ICfnRuleConditionExpression } from './cfn-condition'; import { CfnRefElement } from './cfn-element'; import { capitalizePropertyNames } from './util'; @@ -31,7 +31,7 @@ export interface CfnRuleProps { * * @default - Rule's assertions will always take effect. */ - readonly ruleCondition?: ICfnConditionExpression; + readonly ruleCondition?: ICfnRuleConditionExpression; /** * Assertions which define the rule. @@ -57,7 +57,7 @@ export interface CfnRuleProps { * @link https://docs.aws.amazon.com/servicecatalog/latest/adminguide/reference-template_constraint_rules.html */ export class CfnRule extends CfnRefElement { - private ruleCondition?: ICfnConditionExpression; + private ruleCondition?: ICfnRuleConditionExpression; private assertions?: CfnRuleAssertion[]; /** @@ -77,7 +77,7 @@ export class CfnRule extends CfnRefElement { * @param condition The expression to evaluation. * @param description The description of the assertion. */ - public addAssertion(condition: ICfnConditionExpression, description: string) { + public addAssertion(condition: ICfnRuleConditionExpression, description: string) { if (!this.assertions) { this.assertions = []; } @@ -110,7 +110,7 @@ export interface CfnRuleAssertion { /** * The assertion. */ - readonly assert: ICfnConditionExpression; + readonly assert: ICfnRuleConditionExpression; /** * The assertion description. From 6453116c7505e2fc94837ad6d72826348d2017e0 Mon Sep 17 00:00:00 2001 From: Aidan Crank Date: Fri, 30 Jul 2021 13:34:23 -0400 Subject: [PATCH 16/16] reverting prev commit but keeping one formatting change in other file --- packages/@aws-cdk/core/lib/cfn-condition.ts | 2 +- packages/@aws-cdk/core/lib/cfn-rule.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/core/lib/cfn-condition.ts b/packages/@aws-cdk/core/lib/cfn-condition.ts index dd69bac083960..51a1e071de6a3 100644 --- a/packages/@aws-cdk/core/lib/cfn-condition.ts +++ b/packages/@aws-cdk/core/lib/cfn-condition.ts @@ -86,7 +86,7 @@ export class CfnCondition extends CfnElement implements ICfnConditionExpression, * }); * ``` */ -export interface ICfnConditionExpression extends IResolvable { } +export interface ICfnConditionExpression extends IResolvable {} /** * Interface to specify certain functions as Service Catalog rule-specifc. diff --git a/packages/@aws-cdk/core/lib/cfn-rule.ts b/packages/@aws-cdk/core/lib/cfn-rule.ts index 6d8e9c29b9303..f3141ca636ce2 100644 --- a/packages/@aws-cdk/core/lib/cfn-rule.ts +++ b/packages/@aws-cdk/core/lib/cfn-rule.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { ICfnRuleConditionExpression } from './cfn-condition'; +import { ICfnConditionExpression } from './cfn-condition'; import { CfnRefElement } from './cfn-element'; import { capitalizePropertyNames } from './util'; @@ -31,7 +31,7 @@ export interface CfnRuleProps { * * @default - Rule's assertions will always take effect. */ - readonly ruleCondition?: ICfnRuleConditionExpression; + readonly ruleCondition?: ICfnConditionExpression; /** * Assertions which define the rule. @@ -57,7 +57,7 @@ export interface CfnRuleProps { * @link https://docs.aws.amazon.com/servicecatalog/latest/adminguide/reference-template_constraint_rules.html */ export class CfnRule extends CfnRefElement { - private ruleCondition?: ICfnRuleConditionExpression; + private ruleCondition?: ICfnConditionExpression; private assertions?: CfnRuleAssertion[]; /** @@ -77,7 +77,7 @@ export class CfnRule extends CfnRefElement { * @param condition The expression to evaluation. * @param description The description of the assertion. */ - public addAssertion(condition: ICfnRuleConditionExpression, description: string) { + public addAssertion(condition: ICfnConditionExpression, description: string) { if (!this.assertions) { this.assertions = []; } @@ -110,7 +110,7 @@ export interface CfnRuleAssertion { /** * The assertion. */ - readonly assert: ICfnRuleConditionExpression; + readonly assert: ICfnConditionExpression; /** * The assertion description.