Skip to content

Commit

Permalink
fix(core): refactor use of Tokens
Browse files Browse the repository at this point in the history
Tokens are now represented by the interface `IToken`. The class `Token`
still exists, but it is used to hold static routines for token encoding
(`Token.encodeAsString()`), and as a potential base class for `IToken`
implementations so some default functionality is inherited (this
inheritance is an implementation optimization, it is not a
requirement!).

Actual token use is split into 2 categories:

- *Intrinsics*, represented by `Intrinsic` class: these are to represent values
  that will be understood by the deployment language formalism (e.g.
  CloudFormation), and can be used to escape language type checking.
- *Lazy values*, represented by a `Lazy` class with static factories:
  these will be used to represent type-checked but lazily-produced
  values (evaluated at synthesis time).

In order to be JSII-compatible (which does not currently support
lambdas), `Lazy.stringValue()` et. al. take an interface with a single
method instead of a function.

Also changed in this commit: shoring up property names for encoded
tokens in classes like `CfnParameter` and `CfnElement`.

* `.stringValue` => `.valueAsString`, etc so `.value`, `.valueAsString`,
  `.valueAsList` etc. group together in autocomplete.
* `.ref` now returns a Token, `.refAsString` returns the stringified
  version that token.

To match the other standard the latter should actually be `.refAsString`
but `.ref` is used in so many places that this might be uncomfortable?

Revert `Token.isToken()` to check if the passed object is literally an
`IToken` (to match `Array.isArray()` and other related methods), and
`Token.unresolved()` to check if the passed object might also be an
encoded Token.

Fixes #1933.

BREAKING CHANGES:

* `Token` can no longer be instantiated. Instead, instantiate
  `Intrinsic` or use `Lazy.stringValue` and others.
* `Token.isToken()` will only return true if the object is a Token,
  for detecting arbitrary encoded tokens, use `Token.unresolved()`.
* `CfnOutput`: remove `.ref`, since they can't be referenced anyway.
* `CfnParameter`: rename `.stringValue` => `.valueAsString` (and
  similar for lists and numbers).
* `.ref` now returns an `IToken`, use `.refAsString` to get a
  string-encoded Token.
  • Loading branch information
rix0rrr committed Jun 5, 2019
1 parent f7611ee commit 1bef5fc
Show file tree
Hide file tree
Showing 69 changed files with 187 additions and 175 deletions.
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-apigateway/lib/api-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class ApiKey extends Resource implements IApiKey {
stageKeys: this.renderStageKeys(props.resources)
});

this.keyId = resource.ref;
this.keyId = resource.refAsString;
}

private renderStageKeys(resources: RestApi[] | undefined): CfnApiKey.StageKeyProperty[] | undefined {
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-apigateway/lib/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class Method extends Resource {

const resource = new CfnMethod(this, 'Resource', methodProps);

this.methodId = resource.ref;
this.methodId = resource.refAsString;

props.resource.restApi._attachMethod(this);

Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-apigateway/lib/restapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export class RestApi extends Resource implements IRestApi {
parameters: props.parameters,
});

this.restApiId = resource.ref;
this.restApiId = resource.refAsString;

this.configureDeployment(props);

Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-apigateway/lib/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export class Stage extends Resource {
methodSettings,
});

this.stageName = resource.ref;
this.stageName = resource.refAsString;
this.restApi = props.deployment.api;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export class UsagePlan extends Resource {

this.apiStages.push(...(props.apiStages || []));

this.usagePlanId = resource.ref;
this.usagePlanId = resource.refAsString;

// Add ApiKey when
if (props.apiKey) {
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
keyName: props.keyName,
instanceType: props.instanceType.toString(),
securityGroups: securityGroupsToken,
iamInstanceProfile: iamProfile.ref,
iamInstanceProfile: iamProfile.refAsString,
userData: userDataToken,
associatePublicIpAddress: props.associatePublicIpAddress,
spotPrice: props.spotPrice,
Expand All @@ -408,7 +408,7 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
minSize: minCapacity.toString(),
maxSize: maxCapacity.toString(),
desiredCapacity: desiredCapacity.toString(),
launchConfigurationName: launchConfig.ref,
launchConfigurationName: launchConfig.refAsString,
loadBalancerNames: Lazy.listValue({ produce: () => this.loadBalancerNames }, { omitEmpty: true }),
targetGroupArns: Lazy.listValue({ produce: () => this.targetGroupArns }, { omitEmpty: true }),
notificationConfigurations: !props.notificationsTopic ? undefined : [
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class LambdaApplication extends Resource implements ILambdaApplication {
computePlatform: 'Lambda'
});

this.applicationName = resource.ref;
this.applicationName = resource.refAsString;
this.applicationArn = arnForApplication(this.applicationName);
}
}
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-codedeploy/lib/server/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class ServerApplication extends Resource implements IServerApplication {
computePlatform: 'Server',
});

this.applicationName = resource.ref;
this.applicationName = resource.refAsString;
this.applicationArn = arnForApplication(this.applicationName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export class ServerDeploymentConfig extends cdk.Resource implements IServerDeplo
minimumHealthyHosts: props.minimumHealthyHosts._json,
});

this.deploymentConfigName = resource.ref.toString();
this.deploymentConfigName = resource.refAsString;
this.deploymentConfigArn = arnForDeploymentConfig(this.deploymentConfigName);
}
}
Expand All @@ -125,4 +125,4 @@ function deploymentConfig(name: string): IServerDeploymentConfig {
};
}

function ignore(_x: any) { return; }
function ignore(_x: any) { return; }
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ export abstract class CloudFormationDeployAction extends CloudFormationAction {
...configuration,
// None evaluates to empty string which is falsey and results in undefined
Capabilities: (capabilities && capabilities.toString()) || undefined,
RoleArn: new cdk.Token(() => this.deploymentRole.roleArn),
ParameterOverrides: new cdk.Token(() => this.scope.node.stringifyJson(props.parameterOverrides)),
RoleArn: cdk.Lazy.stringValue({ produce: () => this.deploymentRole.roleArn }),
ParameterOverrides: cdk.Lazy.stringValue({ produce: () => this.scope.node.stringifyJson(props.parameterOverrides) }),
TemplateConfiguration: props.templateConfiguration ? props.templateConfiguration.location : undefined,
StackName: props.stackName,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class CodeBuildAction extends codepipeline.Action {
this.props = props;

if (this.inputs.length > 1) {
this.configuration.PrimarySource = new cdk.Token(() => this.inputs[0].artifactName);
this.configuration.PrimarySource = cdk.Lazy.stringValue({ produce: () => this.inputs[0].artifactName });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class ManualApprovalAction extends codepipeline.Action {
category: codepipeline.ActionCategory.Approval,
provider: 'Manual',
artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 0, maxOutputs: 0 },
configuration: new cdk.Token(() => this.actionConfiguration()),
configuration: cdk.Lazy.anyValue({ produce: () => this.actionConfiguration() }),
});

this.props = props;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect, haveResourceLike } from "@aws-cdk/assert";
import codepipeline = require('@aws-cdk/aws-codepipeline');
import lambda = require('@aws-cdk/aws-lambda');
import { Aws, SecretValue, Stack, Token } from "@aws-cdk/cdk";
import { Aws, Intrinsic, Lazy, SecretValue, Stack } from "@aws-cdk/cdk";
import { Test } from 'nodeunit';
import cpactions = require('../../lib');

Expand Down Expand Up @@ -34,7 +34,7 @@ export = {

'properly resolves any Tokens passed in userParameters'(test: Test) {
const stack = stackIncludingLambdaInvokeCodePipeline({
key: new Token(() => Aws.region),
key: Lazy.stringValue({ produce: () => Aws.region }),
});

expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', {
Expand Down Expand Up @@ -68,7 +68,7 @@ export = {

'properly resolves any stringified Tokens passed in userParameters'(test: Test) {
const stack = stackIncludingLambdaInvokeCodePipeline({
key: new Token(() => null).toString(),
key: new Intrinsic(null).toString(),
});

expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export = {
runOrder: 8,
output: new codepipeline.Artifact('A'),
branch: 'branch',
oauthToken: SecretValue.plainText(secret.stringValue),
oauthToken: SecretValue.plainText(secret.valueAsString),
owner: 'foo',
repo: 'bar',
trigger: cpactions.GitHubTrigger.Poll
Expand Down Expand Up @@ -141,7 +141,7 @@ export = {
runOrder: 8,
output: new codepipeline.Artifact('A'),
branch: 'branch',
oauthToken: SecretValue.plainText(secret.stringValue),
oauthToken: SecretValue.plainText(secret.valueAsString),
owner: 'foo',
repo: 'bar',
trigger: cpactions.GitHubTrigger.None
Expand Down Expand Up @@ -200,7 +200,7 @@ export = {
runOrder: 8,
output: new codepipeline.Artifact('A'),
branch: 'branch',
oauthToken: SecretValue.plainText(secret.stringValue),
oauthToken: SecretValue.plainText(secret.valueAsString),
owner: 'foo',
repo: 'bar'
}),
Expand Down
6 changes: 4 additions & 2 deletions packages/@aws-cdk/aws-codepipeline/lib/artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,11 @@ export class ArtifactPath {
}

function artifactAttribute(artifact: Artifact, attributeName: string) {
return new Intrinsic({ 'Fn::GetArtifactAtt': [artifact.artifactName, attributeName] }).toString();
const lazyArtifactName = Lazy.stringValue({ produce: () => artifact.artifactName });
return new Intrinsic({ 'Fn::GetArtifactAtt': [lazyArtifactName, attributeName] }).toString();
}

function artifactGetParam(artifact: Artifact, jsonFile: string, keyName: string) {
return new Intrinsic({ 'Fn::GetParam': [artifact.artifactName, jsonFile, keyName] }).toString();
const lazyArtifactName = Lazy.stringValue({ produce: () => artifact.artifactName });
return new Intrinsic({ 'Fn::GetParam': [lazyArtifactName, jsonFile, keyName] }).toString();
}
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export class Pipeline extends PipelineBase {

this.artifactBucket.grantReadWrite(this.role);

this.pipelineName = codePipeline.ref;
this.pipelineName = codePipeline.refAsString;
this.pipelineVersion = codePipeline.pipelineVersion;
this.crossRegionReplicationBuckets = props.crossRegionReplicationBuckets || {};
this.artifactStores = {};
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-codepipeline/lib/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function validateArtifactName(artifactName: string | undefined): void {

function validateAgainstRegex(regex: RegExp, thing: string, name: string | undefined) {
// name could be a Token - in that case, skip validation altogether
if (cdk.Token.isToken(name)) {
if (cdk.Token.unresolved(name)) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-config/lib/managed-rules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import iam = require('@aws-cdk/aws-iam');
import sns = require('@aws-cdk/aws-sns');
import { Construct, Token } from '@aws-cdk/cdk';
import { Construct, Lazy } from '@aws-cdk/cdk';
import { ManagedRule, RuleProps } from './rule';

/**
Expand Down Expand Up @@ -77,7 +77,7 @@ export class CloudFormationStackDriftDetectionCheck extends ManagedRule {
...props,
identifier: 'CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK',
inputParameters: {
cloudformationRoleArn: new Token(() => this.role.roleArn)
cloudformationRoleArn: Lazy.stringValue({ produce: () => this.role.roleArn })
}
});

Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/aws-config/lib/rule.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import events = require('@aws-cdk/aws-events');
import iam = require('@aws-cdk/aws-iam');
import lambda = require('@aws-cdk/aws-lambda');
import { Construct, IResource, Resource, Token } from '@aws-cdk/cdk';
import { Construct, IResource, Lazy, Resource } from '@aws-cdk/cdk';
import { CfnConfigRule } from './config.generated';

/**
Expand Down Expand Up @@ -255,7 +255,7 @@ export class ManagedRule extends RuleNew {
description: props.description,
inputParameters: props.inputParameters,
maximumExecutionFrequency: props.maximumExecutionFrequency,
scope: new Token(() => this.scope),
scope: Lazy.anyValue({ produce: () => this.scope }),
source: {
owner: 'AWS',
sourceIdentifier: props.identifier
Expand Down Expand Up @@ -358,7 +358,7 @@ export class CustomRule extends RuleNew {
description: props.description,
inputParameters: props.inputParameters,
maximumExecutionFrequency: props.maximumExecutionFrequency,
scope: new Token(() => this.scope),
scope: Lazy.anyValue({ produce: () => this.scope }),
source: {
owner: 'CUSTOM_LAMBDA',
sourceDetails,
Expand Down
18 changes: 9 additions & 9 deletions packages/@aws-cdk/aws-ec2/lib/security-group-rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export enum Protocol {
* A single TCP port
*/
export class TcpPort implements IPortRange {
public readonly canInlineRule = !Token.isToken(this.port);
public readonly canInlineRule = !Token.unresolved(this.port);

constructor(private readonly port: number) {
}
Expand All @@ -167,15 +167,15 @@ export class TcpPort implements IPortRange {
}

public toString() {
return Token.isToken(this.port) ? `{IndirectPort}` : this.port.toString();
return Token.unresolved(this.port) ? `{IndirectPort}` : this.port.toString();
}
}

/**
* A TCP port range
*/
export class TcpPortRange implements IPortRange {
public readonly canInlineRule = !Token.isToken(this.startPort) && !Token.isToken(this.endPort);
public readonly canInlineRule = !Token.unresolved(this.startPort) && !Token.unresolved(this.endPort);

constructor(private readonly startPort: number, private readonly endPort: number) {
}
Expand Down Expand Up @@ -216,7 +216,7 @@ export class TcpAllPorts implements IPortRange {
* A single UDP port
*/
export class UdpPort implements IPortRange {
public readonly canInlineRule = !Token.isToken(this.port);
public readonly canInlineRule = !Token.unresolved(this.port);

constructor(private readonly port: number) {
}
Expand All @@ -230,7 +230,7 @@ export class UdpPort implements IPortRange {
}

public toString() {
const port = Token.isToken(this.port) ? '{IndirectPort}' : this.port;
const port = Token.unresolved(this.port) ? '{IndirectPort}' : this.port;
return `UDP ${port}`;
}
}
Expand All @@ -239,7 +239,7 @@ export class UdpPort implements IPortRange {
* A UDP port range
*/
export class UdpPortRange implements IPortRange {
public readonly canInlineRule = !Token.isToken(this.startPort) && !Token.isToken(this.endPort);
public readonly canInlineRule = !Token.unresolved(this.startPort) && !Token.unresolved(this.endPort);

constructor(private readonly startPort: number, private readonly endPort: number) {
}
Expand Down Expand Up @@ -280,7 +280,7 @@ export class UdpAllPorts implements IPortRange {
* A set of matching ICMP Type & Code
*/
export class IcmpTypeAndCode implements IPortRange {
public readonly canInlineRule = !Token.isToken(this.type) && !Token.isToken(this.code);
public readonly canInlineRule = !Token.unresolved(this.type) && !Token.unresolved(this.code);

constructor(private readonly type: number, private readonly code: number) {
}
Expand Down Expand Up @@ -321,7 +321,7 @@ export class IcmpPing implements IPortRange {
* All ICMP Codes for a given ICMP Type
*/
export class IcmpAllTypeCodes implements IPortRange {
public readonly canInlineRule = !Token.isToken(this.type);
public readonly canInlineRule = !Token.unresolved(this.type);

constructor(private readonly type: number) {
}
Expand Down Expand Up @@ -373,4 +373,4 @@ export class AllTraffic implements IPortRange {
public toString() {
return 'ALL TRAFFIC';
}
}
}
10 changes: 5 additions & 5 deletions packages/@aws-cdk/aws-ec2/lib/vpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -808,12 +808,12 @@ export class Vpc extends VpcBase {
});
this.internetDependencies.push(igw);
const att = new CfnVPCGatewayAttachment(this, 'VPCGW', {
internetGatewayId: igw.ref,
vpcId: this.resource.ref
internetGatewayId: igw.refAsString,
vpcId: this.resource.refAsString
});

(this.publicSubnets as PublicSubnet[]).forEach(publicSubnet => {
publicSubnet.addDefaultInternetRoute(igw.ref, att);
publicSubnet.addDefaultInternetRoute(igw.refAsString, att);
});

// if gateways are needed create them
Expand Down Expand Up @@ -1129,12 +1129,12 @@ export class Subnet extends cdk.Resource implements ISubnet {
const table = new CfnRouteTable(this, 'RouteTable', {
vpcId: props.vpcId,
});
this.routeTableId = table.ref;
this.routeTableId = table.refAsString;

// Associate the public route table for this subnet, to this subnet
new CfnSubnetRouteTableAssociation(this, 'RouteTableAssociation', {
subnetId: this.subnetId,
routeTableId: table.ref
routeTableId: table.refAsString
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ecr/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ export class Repository extends RepositoryBase {
// if repositoryArn is a token, the repository name is also required. this is because
// repository names can include "/" (e.g. foo/bar/myrepo) and it is impossible to
// parse the name from an ARN using CloudFormation's split/select.
if (Token.isToken(repositoryArn)) {
if (Token.unresolved(repositoryArn)) {
throw new Error('"repositoryArn" is a late-bound value, and therefore "repositoryName" is required. Use `fromRepositoryAttributes` instead');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ export class LoadBalancer extends Resource implements IConnectable {
* @attribute
*/
public get loadBalancerName() {
return this.elb.ref;
return this.elb.refAsString;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export class ApplicationListenerRule extends cdk.Construct {
this.addFixedResponse(props.fixedResponse);
}

this.listenerRuleArn = resource.ref;
this.listenerRuleArn = resource.refAsString;
}

/**
Expand Down
Loading

0 comments on commit 1bef5fc

Please sign in to comment.