diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index b90748a2125ad..4bb6885c601eb 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) + - [CloudFormation parameters constraint](#cloudformation-parameters-constraint) - [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,32 @@ portfolio.notifyOnStackEvents(product, topic2, { }); ``` +### CloudFormation parameters constraint + +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). + +```ts fixture=portfolio-product +import * as cdk from '@aws-cdk/core'; + +portfolio.constrainCloudFormationParameters(product, { + rule: { + 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 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..f3e2c32297003 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,51 @@ export interface TagUpdateConstraintOptions extends CommonConstraintOptions { * @default true */ readonly allow?: boolean; +} + +/** + * An assertion within a template rule, defined by intrinsic functions. + */ +export interface TemplateRuleAssertion { + /** + * The assertion condition. + */ + readonly assert: cdk.ICfnRuleConditionExpression; + + /** + * The description for the asssertion. + * @default - no description provided for the assertion. + */ + readonly description?: 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 condition?: cdk.ICfnRuleConditionExpression; + + /** + * A list of assertions that make up the rule. + */ + readonly assertions: TemplateRuleAssertion[]; +} + +/** + * Properties for provisoning rule constraint. + */ +export interface CloudFormationRuleConstraintOptions extends CommonConstraintOptions { + /** + * The rule with condition and assertions to apply to template. + */ + readonly rule: 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 73d4452ce348b..3056a48e19777 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, StackSetsConstraintOptions, TagUpdateConstraintOptions } from './constraints'; +import { + CloudFormationRuleConstraintOptions, CommonConstraintOptions, + StackSetsConstraintOptions, TagUpdateConstraintOptions, +} from './constraints'; import { AssociationManager } from './private/association-manager'; import { hashValues } from './private/util'; import { InputValidator } from './private/validation'; @@ -100,6 +103,13 @@ 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 options options for the constraint. + */ + constrainCloudFormationParameters(product:IProduct, options: CloudFormationRuleConstraintOptions): void; + /** * Force users to assume a certain role when launching a product. * @@ -136,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 { @@ -161,6 +171,10 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { AssociationManager.notifyOnStackEvents(this, product, topic, options); } + public constrainCloudFormationParameters(product: IProduct, options: CloudFormationRuleConstraintOptions): void { + AssociationManager.constrainCloudFormationParameters(this, product, 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..bf5a68a8e70d3 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,14 @@ 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 { + CloudFormationRuleConstraintOptions, CommonConstraintOptions, StackSetsConstraintOptions, + TagUpdateConstraintOptions, TemplateRule, TemplateRuleAssertion, +} 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'; @@ -14,8 +17,9 @@ import { InputValidator } from './validation'; export class AssociationManager { public static associateProductWithPortfolio( - portfolio: IPortfolio, product: IProduct, + 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); const constructId = `PortfolioProductAssociation${associationKey}`; const existingAssociation = portfolio.node.tryFindChild(constructId); @@ -33,8 +37,7 @@ 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 association = this.associateProductWithPortfolio(portfolio, product, options); const constructId = `ResourceUpdateConstraint${association.associationKey}`; if (!portfolio.node.tryFindChild(constructId)) { @@ -54,8 +57,7 @@ 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 association = this.associateProductWithPortfolio(portfolio, product, options); const constructId = `LaunchNotificationConstraint${hashValues(topic.node.addr, topic.stack.node.addr, association.associationKey)}`; if (!portfolio.node.tryFindChild(constructId)) { @@ -74,9 +76,31 @@ export class AssociationManager { } } + public static constrainCloudFormationParameters( + portfolio: IPortfolio, product: IProduct, + options: CloudFormationRuleConstraintOptions, + ): void { + const association = this.associateProductWithPortfolio(portfolio, product, options); + 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, { + acceptLanguage: options.messageLanguage, + description: options.description, + portfolioId: portfolio.portfolioId, + productId: product.productId, + 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.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); + 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)}`); @@ -100,8 +124,7 @@ export class AssociationManager { } public static deployWithStackSets(portfolio: IPortfolio, product: IProduct, options: StackSetsConstraintOptions) { - this.validateCommonConstraintOptions(portfolio, product, options); - 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)}`); @@ -168,7 +191,25 @@ export class AssociationManager { return `- Portfolio: ${portfolio.node.path} | Product: ${product.node.path}`; } - private static validateCommonConstraintOptions(portfolio: IPortfolio, product: IProduct, options: CommonConstraintOptions): void { - InputValidator.validateLength(this.prettyPrintAssociation(portfolio, product), 'description', 0, 2000, options.description); + private static formatTemplateRule(stack: cdk.Stack, rule: TemplateRule): string { + return JSON.stringify({ + [rule.ruleName]: { + Assertions: this.formatAssertions(stack, rule.assertions), + RuleCondition: rule.condition ? stack.resolve(rule.condition) : undefined, + }, + }); } + + 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 formattedAssertions; + }, new Array<{ Assert: string, AssertDescription: string | undefined }>()); + }; } + 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..5407c293f09b5 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" ] }, + "TestPortfolioLaunchTemplateConstraintfac7b49c426e599F9FFF": { + "Type": "AWS::ServiceCatalog::LaunchTemplateConstraint", + "Properties": { + "PortfolioId": { + "Ref": "TestPortfolio4AC794EB" + }, + "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\"}]}}" + }, + "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..a96c11a45ba3f 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,16 @@ secondPortfolio.deployWithStackSets(product, { allowStackSetInstanceOperations: true, }); +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')), + description: 'test description', + }], + }, +}); + 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..bf6ef2c09b0ed 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts @@ -457,7 +457,104 @@ describe('portfolio associations and product constraints', () => { expect(() => { portfolio.notifyOnStackEvents(product, topic); }).toThrowError(`Topic ${topic} is already subscribed to association`); - }); + }), + + test('creates a CloudFormation parameters constraint', () => { + portfolio.addProduct(product); + portfolio.constrainCloudFormationParameters(product, { + rule: { + ruleName: 'Rule', + assertions: [ + { + assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), + description: '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('CloudFormation parameters constraint still creates without explicit association', () => { + portfolio.constrainCloudFormationParameters(product, { + rule: { + ruleName: 'Rule', + condition: cdk.Fn.conditionContains(['a', 'b'], 'text'), + assertions: [ + { + assert: cdk.Fn.conditionContains(['t2.micro', 't2.small'], cdk.Fn.ref('InstanceType')), + description: 'assert description', + }, + ], + }, + description: 'test description', + messageLanguage: servicecatalog.MessageLanguage.EN, + }); + + expect(stack).toHaveResourceLike('AWS::ServiceCatalog::LaunchTemplateConstraint'); + }), + + test('set multiple CloudFormation parameters constraints', () => { + portfolio.constrainCloudFormationParameters(product, { + rule: { + ruleName: 'Rule01', + assertions: [{ + assert: cdk.Fn.conditionContains(['BucketOwnerRead'], cdk.Fn.ref('AccessControl')), + description: 'assert description', + }], + }, + }); + + portfolio.constrainCloudFormationParameters(product, { + rule: { + ruleName: 'Rule02', + assertions: [{ + assert: cdk.Fn.conditionContains(['BucketOwnerWrite'], cdk.Fn.ref('AccessControl')), + description: 'assert description', + }], + }, + }); + + expect(stack).toCountResources('AWS::ServiceCatalog::LaunchTemplateConstraint', 2); + }), + + test('fails to set a duplicate CloudFormation parameters constraint', () => { + portfolio.constrainCloudFormationParameters(product, { + rule: { + ruleName: 'Rule01', + assertions: [{ + assert: cdk.Fn.conditionContains(['BucketOwnerRead'], cdk.Fn.ref('AccessControl')), + description: 'assert description', + }], + }, + }); + + expect(() => { + portfolio.constrainCloudFormationParameters(product, { + rule: { + ruleName: 'Rule01', + assertions: [{ + assert: cdk.Fn.conditionContains(['BucketOwnerWrite'], cdk.Fn.ref('AccessControl')), + description: 'assert description', + }], + }, + }); + }).toThrowError(/Provisioning rule Rule01 already configured on association/); + }), describe('portfolio constraints that have roles', () => { let launchRole: iam.IRole, adminRole: iam.IRole; diff --git a/packages/@aws-cdk/core/lib/cfn-condition.ts b/packages/@aws-cdk/core/lib/cfn-condition.ts index db3fe5915e512..51a1e071de6a3 100644 --- a/packages/@aws-cdk/core/lib/cfn-condition.ts +++ b/packages/@aws-cdk/core/lib/cfn-condition.ts @@ -86,4 +86,16 @@ 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. + * These functions can only be used in ``Rules`` section of template. + */ +export interface ICfnRuleConditionExpression extends ICfnConditionExpression { + /** + * 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 d8a871ca478e1..3ef1c265654bd 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,12 +267,12 @@ 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'); } if (conditions.length === 1) { - return conditions[0]; + return conditions[0] as ICfnRuleConditionExpression; } return Fn.conditionAnd(..._inGroupsOf(conditions, 10).map(group => new FnAnd(...group))); } @@ -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,12 +326,12 @@ 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'); } if (conditions.length === 1) { - return conditions[0]; + return conditions[0] as ICfnRuleConditionExpression; } return Fn.conditionOr(..._inGroupsOf(conditions, 10).map(group => new FnOr(...group))); } @@ -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); } @@ -604,7 +604,8 @@ class FnCidr extends FnBase { } } -class FnConditionBase extends Intrinsic implements ICfnConditionExpression { +class FnConditionBase extends Intrinsic implements ICfnRuleConditionExpression { + readonly disambiguator = true; constructor(type: string, value: any) { super({ [type]: value }); }