Skip to content

Commit

Permalink
feat: idiomize cloudformation intrinsics functions (#1428)
Browse files Browse the repository at this point in the history
Refactor CloudFormation intrinsic functions so that they will have a more idiomatic shape. They have been converted from classes (e.g. `new FnJoin(...)`) to static methods (e.g. `Fn.join(...)`). Condition functions are mapped to `Fn.conditionXxx` and return an `FnCondition` object.

Furthermore, return type of these static methods are now tokens represented as the resolved type (i.e. `string` or `string[]`) instead of `CloudFormationToken` objects. This allows using these functions and pseudo parameters naturally (instead of requiring the use of `.toString()` or `.toList()`).

Fixes #202

BREAKING CHANGE: CloudFormation intrinsic functions are now represented as static methods under the `Fn` class (e.g. `Fn.join(...)` instead of `new FnJoin(...).toString()`).
  • Loading branch information
Elad Ben-Israel committed Dec 27, 2018
1 parent 095da14 commit 04217a5
Show file tree
Hide file tree
Showing 37 changed files with 541 additions and 307 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1700,7 +1700,7 @@ Fn.if_(Fn.equals(param.ref, 'True'), 'Encrypted', Pseudo.NO_VALUE)
After:
```javascript
new FnIf(new FnEquals(param.ref, 'True'), 'Encrypted', new AwsNoValue())
new FnIf(Fn.equals(param.ref, 'True'), 'Encrypted', new AwsNoValue())
```
- CloudFormation template options (`templateFormatVersion`, `description` and `transform`) are now grouped under `Stack.templateOptions` instead of directly under `Stack`.
Expand Down
4 changes: 2 additions & 2 deletions docs/src/cloudformation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ Intrinsic Functions
.. code-block:: js
import cdk = require('@aws-cdk/cdk');
new cdk.FnJoin(",", [...])
import { Fn } from'@aws-cdk/cdk';
Fn.join(",", [...])
.. _pseudo_parameters:
Expand Down
14 changes: 7 additions & 7 deletions examples/cdk-examples-typescript/advanced-usage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class CloudFormationExample extends cdk.Stack {
// outputs are constructs the synthesize into the template's "Outputs" section
new cdk.Output(this, 'Output', {
description: 'This is an output of the template',
value: new cdk.FnConcat(new cdk.AwsAccountId(), '/', param.ref)
value: `${new cdk.AwsAccountId()}/${param.ref}`
});

// stack.templateOptions can be used to specify template-level options
Expand All @@ -176,13 +176,13 @@ class CloudFormationExample extends cdk.Stack {
new cdk.AwsStackName(),
],

// all CloudFormation's intrinsic functions are supported via the `cdk.FnXxx` classes
// all CloudFormation's intrinsic functions are supported via the `cdk.Fn.xxx` static methods.
IntrinsicFunctions: [
new cdk.FnAnd(
new cdk.FnFindInMap('MyMap', 'K1', 'K2'),
new cdk.FnSub('hello ${world}', {
world: new cdk.FnBase64(param.ref) // resolves to { Ref: <param-id> }
}))
cdk.Fn.join('', [
cdk.Fn.findInMap('MyMap', 'K1', 'K2'),
cdk.Fn.sub('hello ${world}', {
world: cdk.Fn.base64(param.ref) // resolves to { Ref: <param-id> }
}) ])
],
};
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"pkglint": "tools/pkglint/bin/pkglint -f ."
},
"devDependencies": {
"@types/node": "^8.10.38",
"@types/node": "8.10.38",
"@types/nodeunit": "^0.0.30",
"conventional-changelog-cli": "^2.0.5",
"lerna": "^3.3.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/assets-docker/lib/image-asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ export class DockerImageAsset extends cdk.Construct {
this.addMetadata(cxapi.ASSET_METADATA, asset);

// parse repository name and tag from the parameter (<REPO_NAME>:<TAG>)
const components = new cdk.FnSplit(':', imageNameParameter.value);
const repositoryName = new cdk.FnSelect(0, components).toString();
const imageTag = new cdk.FnSelect(1, components).toString();
const components = cdk.Fn.split(':', imageNameParameter.valueAsString);
const repositoryName = cdk.Fn.select(0, components).toString();
const imageTag = cdk.Fn.select(1, components).toString();

// Require that repository adoption happens first, so we route the
// input ARN into the Custom Resource and then get the URI which we use to
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/assets/lib/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ export class Asset extends cdk.Construct {
});

this.s3BucketName = bucketParam.value.toString();
this.s3Prefix = new cdk.FnSelect(0, new cdk.FnSplit(cxapi.ASSET_PREFIX_SEPARATOR, keyParam.value)).toString();
const s3Filename = new cdk.FnSelect(1, new cdk.FnSplit(cxapi.ASSET_PREFIX_SEPARATOR, keyParam.value)).toString();
this.s3Prefix = cdk.Fn.select(0, cdk.Fn.split(cxapi.ASSET_PREFIX_SEPARATOR, keyParam.valueAsString)).toString();
const s3Filename = cdk.Fn.select(1, cdk.Fn.split(cxapi.ASSET_PREFIX_SEPARATOR, keyParam.valueAsString)).toString();
this.s3ObjectKey = `${this.s3Prefix}${s3Filename}`;

this.bucket = s3.BucketRef.import(this, 'AssetBucket', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup

// use delayed evaluation
const machineImage = props.machineImage.getImage(this);
const userDataToken = new cdk.Token(() => new cdk.FnBase64((machineImage.os.createUserData(this.userDataLines))));
const userDataToken = new cdk.Token(() => cdk.Fn.base64((machineImage.os.createUserData(this.userDataLines))));
const securityGroupsToken = new cdk.Token(() => this.securityGroups.map(sg => sg.securityGroupId));

const launchConfig = new CfnLaunchConfiguration(this, 'LaunchConfig', {
Expand Down
4 changes: 1 addition & 3 deletions packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,9 +574,7 @@ export class CloudFrontWebDistribution extends cdk.Construct implements route53.

if (originConfig.s3OriginSource && originConfig.s3OriginSource.originAccessIdentity) {
originProperty.s3OriginConfig = {
originAccessIdentity: new cdk.FnConcat(
"origin-access-identity/cloudfront/", originConfig.s3OriginSource.originAccessIdentity.ref
),
originAccessIdentity: `origin-access-identity/cloudfront/${originConfig.s3OriginSource.originAccessIdentity.ref}`
};
} else if (originConfig.s3OriginSource) {
originProperty.s3OriginConfig = {};
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-cloudtrail/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export class CloudTrail extends cdk.Construct {
.addServicePrincipal(cloudTrailPrincipal));

s3bucket.addToResourcePolicy(new iam.PolicyStatement()
.addResource(s3bucket.arnForObjects(new cdk.FnConcat('AWSLogs/', new cdk.AwsAccountId(), "/*")))
.addResource(s3bucket.arnForObjects(`AWSLogs/${new cdk.AwsAccountId()}/*`))
.addActions("s3:PutObject")
.addServicePrincipal(cloudTrailPrincipal)
.setCondition("StringEquals", {'s3:x-amz-acl': "bucket-owner-full-control"}));
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-codebuild/lib/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,10 +495,10 @@ export class Project extends ProjectRef {

let cache: CfnProject.ProjectCacheProperty | undefined;
if (props.cacheBucket) {
const cacheDir = props.cacheDir != null ? props.cacheDir : new cdk.AwsNoValue();
const cacheDir = props.cacheDir != null ? props.cacheDir : new cdk.AwsNoValue().toString();
cache = {
type: 'S3',
location: new cdk.FnJoin('/', [props.cacheBucket.bucketName, cacheDir]),
location: cdk.Fn.join('/', [props.cacheBucket.bucketName, cacheDir]),
};

props.cacheBucket.grantReadWrite(this.role);
Expand Down
12 changes: 6 additions & 6 deletions packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -855,14 +855,14 @@ export = {
new codebuild.Project(stack, 'Project', {
source: new codebuild.CodePipelineSource(),
environment: {
environmentVariables: {
FOO: { value: '1234' },
BAR: { value: new cdk.FnConcat('111', { twotwotwo: '222' }), type: codebuild.BuildEnvironmentVariableType.ParameterStore }
}
environmentVariables: {
FOO: { value: '1234' },
BAR: { value: `111${new cdk.CloudFormationToken({ twotwotwo: '222' })}`, type: codebuild.BuildEnvironmentVariableType.ParameterStore }
}
},
environmentVariables: {
GOO: { value: 'ABC' },
FOO: { value: 'OVERRIDE!' }
GOO: { value: 'ABC' },
FOO: { value: 'OVERRIDE!' }
}
});

Expand Down
7 changes: 1 addition & 6 deletions packages/@aws-cdk/aws-codecommit/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,7 @@ class ImportedRepositoryRef extends RepositoryRef {
}

private repositoryCloneUrl(protocol: 'https' | 'ssh'): string {
return new cdk.FnConcat(`${protocol}://git-codecommit.`,
new cdk.AwsRegion(),
'.',
new cdk.AwsURLSuffix(),
'/v1/repos/',
this.repositoryName).toString();
return `${protocol}://git-codecommit.${new cdk.AwsRegion()}.${new cdk.AwsURLSuffix()}/v1/repos/${this.repositoryName}`;
}
}

Expand Down
8 changes: 4 additions & 4 deletions packages/@aws-cdk/aws-ecr/test/test.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export = {

// WHEN/THEN
test.throws(() => ecr.Repository.import(stack, 'Repo', {
repositoryArn: new cdk.FnGetAtt('Boom', 'Boom').toString()
repositoryArn: cdk.Fn.getAtt('Boom', 'Boom').toString()
}), /repositoryArn is a late-bound value, and therefore repositoryName is required/);

test.done();
Expand All @@ -204,8 +204,8 @@ export = {

// WHEN
const repo = ecr.Repository.import(stack, 'Repo', {
repositoryArn: new cdk.FnGetAtt('Boom', 'Arn').toString(),
repositoryName: new cdk.FnGetAtt('Boom', 'Name').toString()
repositoryArn: cdk.Fn.getAtt('Boom', 'Arn').toString(),
repositoryName: cdk.Fn.getAtt('Boom', 'Name').toString()
});

// THEN
Expand Down Expand Up @@ -242,7 +242,7 @@ export = {
'arnForLocalRepository can be used to render an ARN for a local repository'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const repoName = new cdk.FnGetAtt('Boom', 'Name').toString();
const repoName = cdk.Fn.getAtt('Boom', 'Name').toString();

// WHEN
const repo = ecr.Repository.import(stack, 'Repo', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,6 @@ export interface LoadBalancerTargetProps {
* app/my-load-balancer/50dc6c495c0c9188
*/
export function loadBalancerNameFromListenerArn(listenerArn: string) {
const arnParts = new cdk.FnSplit('/', listenerArn);
return `${new cdk.FnSelect(1, arnParts)}/${new cdk.FnSelect(2, arnParts)}/${new cdk.FnSelect(3, arnParts)}`;
const arnParts = cdk.Fn.split('/', listenerArn);
return `${cdk.Fn.select(1, arnParts)}/${cdk.Fn.select(2, arnParts)}/${cdk.Fn.select(3, arnParts)}`;
}
10 changes: 5 additions & 5 deletions packages/@aws-cdk/aws-events/lib/input-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export interface TargetInputTemplate {
* @example
*
* {
* textTemplate: 'Build <buildid> started',
* pathsMap: {
* buildid: '$.detail.id'
* }
* textTemplate: 'Build <buildid> started',
* pathsMap: {
* buildid: '$.detail.id'
* }
* }
*/
textTemplate?: any;
textTemplate?: string;

/**
* Input template where you can use the values of the keys from
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-events/lib/rule.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Construct, FnConcat, Token } from '@aws-cdk/cdk';
import { Construct, Token } from '@aws-cdk/cdk';
import { EventPattern } from './event-pattern';
import { CfnRule } from './events.generated';
import { TargetInputTemplate } from './input-options';
Expand Down Expand Up @@ -133,7 +133,7 @@ export class EventRule extends EventRuleRef {
} else if (typeof(inputOptions.textTemplate) === 'string') {
inputTemplate = JSON.stringify(inputOptions.textTemplate);
} else {
inputTemplate = new FnConcat('"', inputOptions.textTemplate, '"');
inputTemplate = `"${inputOptions.textTemplate}"`;
}

return {
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-events/test/test.rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export = {
// tokens are used here (FnConcat), but this is a text template so we
// expect it to be wrapped in double quotes automatically for us.
rule.addTarget(t1, {
textTemplate: new cdk.FnConcat('a', 'b')
textTemplate: cdk.Fn.join('', [ 'a', 'b' ]).toString()
});

// jsonTemplate can be used to format JSON documents with replacements
Expand All @@ -252,7 +252,7 @@ export = {
// tokens can also used for JSON templates, but that means escaping is
// the responsibility of the user.
rule.addTarget(t4, {
jsonTemplate: new cdk.FnJoin(' ', ['"', 'hello', '\"world\"', '"']),
jsonTemplate: cdk.Fn.join(' ', ['"', 'hello', '\"world\"', '"']),
});

expect(stack).toMatch({
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-iam/test/test.policy-document.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FnConcat, resolve } from '@aws-cdk/cdk';
import { resolve, Token } from '@aws-cdk/cdk';
import { Test } from 'nodeunit';
import { Anyone, AnyPrincipal, CanonicalUserPrincipal, PolicyDocument, PolicyPrincipal, PolicyStatement } from '../lib';
import { ArnPrincipal, CompositePrincipal, FederatedPrincipal, PrincipalPolicyFragment, ServicePrincipal } from '../lib';
Expand All @@ -12,7 +12,7 @@ export = {
p.addResource('yourQueue');

p.addAllResources();
p.addAwsAccountPrincipal(new FnConcat('my', { account: 'account' }, 'name').toString());
p.addAwsAccountPrincipal(`my${new Token({ account: 'account' })}name`);
p.limitToAccount('12221121221');

test.deepEqual(resolve(p), { Action:
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-kinesis/lib/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export abstract class StreamRef extends cdk.Construct implements logs.ILogSubscr
if (!this.cloudWatchLogsRole) {
// Create a role to be assumed by CWL that can write to this stream and pass itself.
this.cloudWatchLogsRole = new iam.Role(this, 'CloudWatchLogsCanPutRecords', {
assumedBy: new iam.ServicePrincipal(new cdk.FnConcat('logs.', new cdk.AwsRegion(), '.amazonaws.com').toString()),
assumedBy: new iam.ServicePrincipal(`logs.${new cdk.AwsRegion()}.amazonaws.com`)
});
this.cloudWatchLogsRole.addToPolicy(new iam.PolicyStatement().addAction('kinesis:PutRecord').addResource(this.streamArn));
this.cloudWatchLogsRole.addToPolicy(new iam.PolicyStatement().addAction('iam:PassRole').addResource(this.cloudWatchLogsRole.roleArn));
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export abstract class FunctionRef extends cdk.Construct
//
// (Wildcards in principals are unfortunately not supported.
this.addPermission('InvokedByCloudWatchLogs', {
principal: new iam.ServicePrincipal(new cdk.FnConcat('logs.', new cdk.AwsRegion(), '.amazonaws.com').toString()),
principal: new iam.ServicePrincipal(`logs.${new cdk.AwsRegion()}.amazonaws.com`),
sourceArn: arn
});
this.logSubscriptionDestinationPolicyAddedFor.push(arn);
Expand Down Expand Up @@ -451,6 +451,6 @@ class LambdaRefImport extends FunctionRef {
* @returns `FnSelect(6, FnSplit(':', arn))`
*/
private extractNameFromArn(arn: string) {
return new cdk.FnSelect(6, new cdk.FnSplit(':', arn)).toString();
return cdk.Fn.select(6, cdk.Fn.split(':', arn)).toString();
}
}
10 changes: 5 additions & 5 deletions packages/@aws-cdk/aws-s3/lib/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ export abstract class BucketRef extends cdk.Construct {
* bucket is returned.
* @returns an ObjectS3Url token
*/
public urlForObject(key?: any): string {
const components = [ 'https://', 's3.', new cdk.AwsRegion(), '.', new cdk.AwsURLSuffix(), '/', this.bucketName ];
public urlForObject(key?: string): string {
const components = [ `https://s3.${new cdk.AwsRegion()}.${new cdk.AwsURLSuffix()}/${this.bucketName}` ];
if (key) {
// trim prepending '/'
if (typeof key === 'string' && key.startsWith('/')) {
Expand All @@ -176,7 +176,7 @@ export abstract class BucketRef extends cdk.Construct {
components.push(key);
}

return new cdk.FnConcat(...components).toString();
return components.join('');
}

/**
Expand All @@ -188,8 +188,8 @@ export abstract class BucketRef extends cdk.Construct {
* arnForObjects('home/', team, '/', user, '/*')
*
*/
public arnForObjects(...keyPattern: any[]): string {
return new cdk.FnConcat(this.bucketArn, '/', ...keyPattern).toString();
public arnForObjects(...keyPattern: string[]): string {
return `${this.bucketArn}/${keyPattern.join('')}`;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-s3/test/test.util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import cdk = require('@aws-cdk/cdk');
import { CloudFormationToken } from '@aws-cdk/cdk';
import { Test } from 'nodeunit';
import { parseBucketArn, parseBucketName } from '../lib/util';

Expand Down Expand Up @@ -41,7 +42,7 @@ export = {
},

'undefined if cannot extract name from a non-string arn'(test: Test) {
const bucketArn = new cdk.FnConcat('arn:aws:s3:::', { Ref: 'my-bucket' }).toString();
const bucketArn = `arn:aws:s3:::${new CloudFormationToken({ Ref: 'my-bucket' })}`;
test.deepEqual(cdk.resolve(parseBucketName({ bucketArn })), undefined);
test.done();
},
Expand Down
Loading

0 comments on commit 04217a5

Please sign in to comment.