diff --git a/docs/src/cloudformation.rst b/docs/src/cloudformation.rst index 70556fd1a0ad4..688adc4e0ed64 100644 --- a/docs/src/cloudformation.rst +++ b/docs/src/cloudformation.rst @@ -149,18 +149,29 @@ Outputs Conditions ---------- -.. NEEDS SOME INTRO TEXT +`cdk.Condition` can be used to define CloudFormation "Condition" elements in the template. +The `cdk.Fn.conditionXx()` static methods can be used to produce "condition expressions". .. code-block:: js import sqs = require('@aws-cdk/aws-sqs'); import cdk = require('@aws-cdk/cdk'); + + const param = new cdk.Parameter(this, 'Param1', { type: 'String' }); + const cond1 = new cdk.Condition(this, 'Condition1', { expression: cdk.Fn.conditionEquals("a", "b") }); + const cond2 = new cdk.Condition(this, 'Condition2', { expression: cdk.Fn.conditionContains([ "a", "b", "c" ], "c") }); + const cond3 = new cdk.Condition(this, 'Condition3', { expression: cdk.Fn.conditionEquals(param, "hello") }); + + const cond4 = new cdk.Condition(this, 'Condition4', { + expression: cdk.Fn.conditionOr(cond1, cond2, cdk.Fn.conditionNot(cond3)) + }); + const cond = new cdk.Condition(this, 'MyCondition', { expression: new cdk.FnIf(...) }); const queue = new sqs.CfnQueue(this, 'MyQueue'); - queue.options.condition = cond; + queue.options.condition = cond4; .. _intrinsic_functions: diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/condition.ts b/packages/@aws-cdk/cdk/lib/cloudformation/condition.ts index 706b352c90d86..465836420c200 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/condition.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/condition.ts @@ -1,20 +1,19 @@ import { Construct } from '../core/construct'; -import { CloudFormationToken } from './cloudformation-token'; import { Referenceable } from './stack'; export interface ConditionProps { - expression?: FnCondition; + expression?: IConditionExpression; } /** * Represents a CloudFormation condition, for resources which must be conditionally created and * the determination must be made at deploy time. */ -export class Condition extends Referenceable { +export class Condition extends Referenceable implements IConditionExpression { /** * The condition statement. */ - public expression?: FnCondition; + public expression?: IConditionExpression; /** * Build a new condition. The condition must be constructed with a condition token, @@ -26,35 +25,52 @@ export class Condition extends Referenceable { } public toCloudFormation(): object { + if (!this.expression) { + return { }; + } + return { Conditions: { [this.logicalId]: this.expression } }; } + + /** + * Synthesizes the condition. + */ + public resolve(): any { + return { Condition: this.logicalId }; + } } /** - * You can use intrinsic functions, such as ``Fn::If``, ``Fn::Equals``, and ``Fn::Not``, to conditionally - * create stack resources. These conditions are evaluated based on input parameters that you - * declare when you create or update a stack. After you define all your conditions, you can - * associate them with resources or resource properties in the Resources and Outputs sections - * of a template. + * Represents a CloudFormation element that can be used within a Condition. * - * You define all conditions in the Conditions section of a template except for ``Fn::If`` conditions. - * You can use the ``Fn::If`` condition in the metadata attribute, update policy attribute, and property - * values in the Resources section and Outputs sections of a template. + * You can use intrinsic functions, such as ``Fn.conditionIf``, + * ``Fn.conditionEquals``, and ``Fn.conditionNot``, to conditionally create + * stack resources. These conditions are evaluated based on input parameters + * that you declare when you create or update a stack. After you define all your + * conditions, you can associate them with resources or resource properties in + * the Resources and Outputs sections of a template. * - * You might use conditions when you want to reuse a template that can create resources in different - * contexts, such as a test environment versus a production environment. In your template, you can - * add an EnvironmentType input parameter, which accepts either prod or test as inputs. For the - * production environment, you might include Amazon EC2 instances with certain capabilities; - * however, for the test environment, you want to use less capabilities to save costs. With - * conditions, you can define which resources are created and how they're configured for each - * environment type. + * You define all conditions in the Conditions section of a template except for + * ``Fn.conditionIf`` conditions. You can use the ``Fn.conditionIf`` condition + * in the metadata attribute, update policy attribute, and property values in + * the Resources section and Outputs sections of a template. + * + * You might use conditions when you want to reuse a template that can create + * resources in different contexts, such as a test environment versus a + * production environment. In your template, you can add an EnvironmentType + * input parameter, which accepts either prod or test as inputs. For the + * production environment, you might include Amazon EC2 instances with certain + * capabilities; however, for the test environment, you want to use less + * capabilities to save costs. With conditions, you can define which resources + * are created and how they're configured for each environment type. */ -export class FnCondition extends CloudFormationToken { - constructor(type: string, value: any) { - super({ [type]: value }); - } -} +export interface IConditionExpression { + /** + * Returns a JSON node that represents this condition expression + */ + resolve(): any; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts index 44b0f440da057..fd0f38d6f217a 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/fn.ts @@ -1,5 +1,5 @@ import { CloudFormationToken, FnJoin } from './cloudformation-token'; -import { FnCondition } from './condition'; +import { IConditionExpression } from './condition'; // tslint:disable:max-line-length @@ -148,7 +148,7 @@ export class Fn { * @param conditions conditions to AND * @returns an FnCondition token */ - public static conditionAnd(...conditions: FnCondition[]): FnCondition { + public static conditionAnd(...conditions: IConditionExpression[]): IConditionExpression { return new FnAnd(...conditions); } @@ -159,7 +159,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): FnCondition { + public static conditionEquals(lhs: any, rhs: any): IConditionExpression { return new FnEquals(lhs, rhs); } @@ -178,7 +178,7 @@ export class Fn { * evaluates to false. * @returns an FnCondition token */ - public static conditionIf(conditionId: string, valueIfTrue: any, valueIfFalse: any): FnCondition { + public static conditionIf(conditionId: string, valueIfTrue: any, valueIfFalse: any): IConditionExpression { return new FnIf(conditionId, valueIfTrue, valueIfFalse); } @@ -189,7 +189,7 @@ export class Fn { * or false. * @returns an FnCondition token */ - public static conditionNot(condition: FnCondition): FnCondition { + public static conditionNot(condition: IConditionExpression): IConditionExpression { return new FnNot(condition); } @@ -201,7 +201,7 @@ export class Fn { * @param conditions conditions that evaluates to true or false. * @returns an FnCondition token */ - public static conditionOr(...conditions: FnCondition[]): FnCondition { + public static conditionOr(...conditions: IConditionExpression[]): IConditionExpression { return new FnOr(...conditions); } @@ -212,7 +212,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): FnCondition { + public static conditionContains(listOfStrings: string[], value: string): IConditionExpression { return new FnContains(listOfStrings, value); } @@ -223,7 +223,7 @@ export class Fn { * of strings. * @returns an FnCondition token */ - public conditionEachMemberEquals(listOfStrings: string[], value: string): FnCondition { + public conditionEachMemberEquals(listOfStrings: string[], value: string): IConditionExpression { return new FnEachMemberEquals(listOfStrings, value); } @@ -238,7 +238,7 @@ export class Fn { * strings_to_check parameter. * @returns an FnCondition token */ - public conditionEachMemberIn(stringsToCheck: string[], stringsToMatch: string): FnCondition { + public conditionEachMemberIn(stringsToCheck: string[], stringsToMatch: string): IConditionExpression { return new FnEachMemberIn(stringsToCheck, stringsToMatch); } @@ -442,13 +442,23 @@ class FnCidr extends FnBase { } } +class FnConditionBase extends CloudFormationToken implements IConditionExpression { + constructor(type: string, value: any) { + super({ [type]: value }); + } + + public toConditionJson() { + return this.resolve(); + } +} + /** * Returns true if all the specified conditions evaluate to true, or returns false if any one * of the conditions evaluates to false. ``Fn::And`` acts as an AND operator. The minimum number of * conditions that you can include is 2, and the maximum is 10. */ -class FnAnd extends FnCondition { - constructor(...condition: FnCondition[]) { +class FnAnd extends FnConditionBase { + constructor(...condition: IConditionExpression[]) { super('Fn::And', condition); } } @@ -457,7 +467,7 @@ class FnAnd extends FnCondition { * Compares if two values are equal. Returns true if the two values are equal or false * if they aren't. */ -class FnEquals extends FnCondition { +class FnEquals extends FnConditionBase { /** * Creates an ``Fn::Equals`` condition function. * @param lhs A value of any type that you want to compare. @@ -475,7 +485,7 @@ class FnEquals extends FnCondition { * in the Resources section and Outputs sections of a template. You can use the AWS::NoValue * pseudo parameter as a return value to remove the corresponding property. */ -class FnIf extends FnCondition { +class FnIf extends FnConditionBase { /** * Creates an ``Fn::If`` condition function. * @param condition A reference to a condition in the Conditions section. Use the condition's name to reference it. @@ -491,12 +501,12 @@ class FnIf extends FnCondition { * Returns true for a condition that evaluates to false or returns false for a condition that evaluates to true. * ``Fn::Not`` acts as a NOT operator. */ -class FnNot extends FnCondition { +class FnNot extends FnConditionBase { /** * Creates an ``Fn::Not`` condition function. * @param condition A condition such as ``Fn::Equals`` that evaluates to true or false. */ - constructor(condition: FnCondition) { + constructor(condition: IConditionExpression) { super('Fn::Not', [ condition ]); } } @@ -506,12 +516,12 @@ class FnNot extends FnCondition { * all of the conditions evaluates to false. ``Fn::Or`` acts as an OR operator. The minimum number * of conditions that you can include is 2, and the maximum is 10. */ -class FnOr extends FnCondition { +class FnOr extends FnConditionBase { /** * Creates an ``Fn::Or`` condition function. * @param condition A condition that evaluates to true or false. */ - constructor(...condition: FnCondition[]) { + constructor(...condition: IConditionExpression[]) { super('Fn::Or', condition); } } @@ -519,7 +529,7 @@ class FnOr extends FnCondition { /** * Returns true if a specified string matches at least one value in a list of strings. */ -class FnContains extends FnCondition { +class FnContains extends FnConditionBase { /** * Creates an ``Fn::Contains`` function. * @param listOfStrings A list of strings, such as "A", "B", "C". @@ -533,7 +543,7 @@ class FnContains extends FnCondition { /** * Returns true if a specified string matches all values in a list. */ -class FnEachMemberEquals extends FnCondition { +class FnEachMemberEquals extends FnConditionBase { /** * Creates an ``Fn::EachMemberEquals`` function. * @param listOfStrings A list of strings, such as "A", "B", "C". @@ -548,7 +558,7 @@ class FnEachMemberEquals extends FnCondition { * Returns true if each member in a list of strings matches at least one value in a second * list of strings. */ -class FnEachMemberIn extends FnCondition { +class FnEachMemberIn extends FnConditionBase { /** * Creates an ``Fn::EachMemberIn`` function. * @param stringsToCheck A list of strings, such as "A", "B", "C". AWS CloudFormation checks whether each member in the strings_to_check parameter is in the strings_to_match parameter. diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts b/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts index 85ff566366897..ecfbbbf200867 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/rule.ts @@ -1,6 +1,6 @@ import { Construct } from '../core/construct'; import { capitalizePropertyNames } from '../core/util'; -import { FnCondition } from './condition'; +import { IConditionExpression } from './condition'; import { Referenceable } from './stack'; /** @@ -29,7 +29,7 @@ export interface RuleProps { * If the rule condition evaluates to false, the rule doesn't take effect. * If the function in the rule condition evaluates to true, expressions in each assert are evaluated and applied. */ - ruleCondition?: FnCondition; + ruleCondition?: IConditionExpression; /** * Assertions which define the rule. @@ -57,7 +57,7 @@ export class Rule extends Referenceable { * If the rule condition evaluates to false, the rule doesn't take effect. * If the function in the rule condition evaluates to true, expressions in each assert are evaluated and applied. */ - public ruleCondition?: FnCondition; + public ruleCondition?: IConditionExpression; /** * Assertions which define the rule. @@ -81,7 +81,7 @@ export class Rule extends Referenceable { * @param condition The expression to evaluation. * @param description The description of the assertion. */ - public addAssertion(condition: FnCondition, description: string) { + public addAssertion(condition: IConditionExpression, description: string) { if (!this.assertions) { this.assertions = []; } @@ -111,7 +111,7 @@ export interface RuleAssertion { /** * The assertion. */ - assert: FnCondition; + assert: IConditionExpression; /** * The assertion description. diff --git a/packages/@aws-cdk/cdk/test/cloudformation/test.condition.ts b/packages/@aws-cdk/cdk/test/cloudformation/test.condition.ts new file mode 100644 index 0000000000000..4ca8300bf25e0 --- /dev/null +++ b/packages/@aws-cdk/cdk/test/cloudformation/test.condition.ts @@ -0,0 +1,32 @@ +import { Test } from 'nodeunit'; +import cdk = require('../../lib'); + +export = { + 'chain conditions'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const param = new cdk.Parameter(stack, 'Param1', { type: 'String' }); + const cond1 = new cdk.Condition(stack, 'Condition1', { expression: cdk.Fn.conditionEquals("a", "b") }); + const cond2 = new cdk.Condition(stack, 'Condition2', { expression: cdk.Fn.conditionContains([ "a", "b", "c" ], "c") }); + const cond3 = new cdk.Condition(stack, 'Condition3', { expression: cdk.Fn.conditionEquals(param, "hello") }); + + // WHEN + new cdk.Condition(stack, 'Condition4', { + expression: cdk.Fn.conditionOr(cond1, cond2, cdk.Fn.conditionNot(cond3)) + }); + + // THEN + test.deepEqual(stack.toCloudFormation(), { + Parameters: { Param1: { Type: 'String' } }, + Conditions: { + Condition1: { 'Fn::Equals': [ 'a', 'b' ] }, + Condition2: { 'Fn::Contains': [ [ 'a', 'b', 'c' ], 'c' ] }, + Condition3: { 'Fn::Equals': [ { Ref: 'Param1' }, 'hello' ] }, + Condition4: { 'Fn::Or': [ + { Condition: 'Condition1' }, + { Condition: 'Condition2' }, + { 'Fn::Not': [ { Condition: 'Condition3' } ] } ] } } }); + + test.done(); + } +};