Skip to content

Commit

Permalink
feat: use stringified tokens for resource attributes instead of stron…
Browse files Browse the repository at this point in the history
…g types (#712)

This change removes all strong-types generated to represent 
CloudFormation resource attributes that return a string  (such 
as `QueueArn`, `DeploymentGroupId`) and replaces them 
with `string` that contains a stringified token (via #518).

This allows working with these attributes as if they were regular
strings, and dramatically simplifies the type-system and unneeded
wrapping when assigning values to such attributes.

The ".ref" property of each resource has been replaced with
a property named according to the actual semantic meaning
of the intrinsic reference (such as `queueArn`), and 
also returns a `string`.

Users can test if a string contains stringified tokens using the function 
`unresolved(o)`, which can be applied to either strings or objects 
(previously was `isToken(o)`).

Fixes #695 (opened #744 to follow up on non-string attributes)
  • Loading branch information
Elad Ben-Israel authored Sep 19, 2018
1 parent 680f0df commit 6508f78
Showing 134 changed files with 1,106 additions and 1,079 deletions.
9 changes: 7 additions & 2 deletions build.sh
Original file line number Diff line number Diff line change
@@ -5,6 +5,11 @@ if [ ! -d node_modules ]; then
/bin/bash ./install.sh
fi

fail() {
echo "❌ Last command failed. Scroll up to see errors in log."
exit 1
}

/bin/bash ./git-secrets-scan.sh

BUILD_INDICATOR=".BUILD_COMPLETED"
@@ -19,10 +24,10 @@ trap "rm -rf $MERKLE_BUILD_CACHE" EXIT

echo "============================================================================================="
echo "building..."
time lerna exec --stream "npm run build"
time lerna run --no-bail --stream build || fail

echo "============================================================================================="
echo "testing..."
lerna run --stream test
lerna run --no-bail --stream test || fail

touch $BUILD_INDICATOR
4 changes: 2 additions & 2 deletions docs/src/examples.rst
Original file line number Diff line number Diff line change
@@ -168,8 +168,8 @@ The following example creates the Aurora database **MyAuroraDatabase**.
new rds.DatabaseCluster(this, 'MyRdsDb', {
defaultDatabaseName: 'MyAuroraDatabase',
masterUser: {
username: new cdk.Token('admin'),
password: new cdk.Token('123456')
username: 'admin',
password: '123456'
},
engine: rds.DatabaseClusterEngine.Aurora,
instanceProps: {
2 changes: 1 addition & 1 deletion examples/cdk-examples-typescript/chat-app/index.ts
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ class MyStack extends cdk.Stack {
new CognitoChatRoomPool(this, 'UserPool');

const bucket = s3.BucketRef.import(this, 'DougsBucket', {
bucketName: new s3.BucketName('dougs-chat-app')
bucketName: 'dougs-chat-app'
});

new ChatAppFunction(this, 'StartAddBucket', {
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CustomResource } from '@aws-cdk/aws-cloudformation';
import lambda = require('@aws-cdk/aws-lambda');
import { cloudformation as s3 } from '@aws-cdk/aws-s3';
import cdk = require('@aws-cdk/cdk');
import fs = require('fs');
import { CustomResource, SingletonLambda } from '../lib';

interface DemoResourceProps {
/**
@@ -18,24 +18,24 @@ interface DemoResourceProps {

class DemoResource extends cdk.Construct implements cdk.IDependable {
public readonly dependencyElements: cdk.IDependable[];
public readonly response: cdk.Token;
public readonly response: string;

constructor(parent: cdk.Construct, name: string, props: DemoResourceProps) {
super(parent, name);

const resource = new CustomResource(this, 'Resource', {
lambdaProvider: new SingletonLambda(this, 'Singleton', {
lambdaProvider: new lambda.SingletonFunction(this, 'Singleton', {
uuid: 'f7d4f730-4ee1-11e8-9c2d-fa7ae01bbebc',
// This makes the demo only work as top-level TypeScript program, but that's fine for now
code: new lambda.LambdaInlineCode(fs.readFileSync('integ.trivial-lambda-provider.py', { encoding: 'utf-8' })),
code: lambda.Code.inline(fs.readFileSync('provider.py', { encoding: 'utf-8' })),
handler: 'index.main',
timeout: 300,
runtime: lambda.LambdaRuntime.Python27,
runtime: lambda.Runtime.Python27,
}),
properties: props
});

this.response = resource.getAtt('Response');
this.response = resource.getAtt('Response').toString();
this.dependencyElements = [resource];
}
}
@@ -101,4 +101,4 @@ new SucceedingStack(app, 'SucceedingStack');
new FailCreationStack(app, 'FailCreationStack');
new FailAfterCreatingStack(app, 'FailAfterCreatingStack');

process.stdout.write(app.run());
process.stdout.write(app.run());
Original file line number Diff line number Diff line change
@@ -23,4 +23,4 @@ def main(event, context):
except Exception as e:
log.exception(e)
# cfnresponse's error message is always "see CloudWatch"
cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_id)
cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_id)
5 changes: 2 additions & 3 deletions examples/cdk-examples-typescript/neptune-demo/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ec2 = require('@aws-cdk/aws-ec2');
import neptune = require('@aws-cdk/aws-neptune');
import rds = require('@aws-cdk/aws-rds');
import cdk = require('@aws-cdk/cdk');

class NeptuneDemoStack extends cdk.Stack {
@@ -19,8 +18,8 @@ class NeptuneDemoStack extends cdk.Stack {
masterUser: {
// This would normally be imported from SSM parmeter store encrypted string,
// but don't want to overcomplicate the example
username: new rds.Username('admin'),
password: new rds.Password('eRSDwst7lpzu'),
username: 'admin',
password: 'eRSDwst7lpzu',
}
});
database.connections.allowDefaultPortFrom(new ec2.AnyIPv4(), 'Allow the world to connect');
16 changes: 8 additions & 8 deletions packages/@aws-cdk/assets/lib/asset.ts
Original file line number Diff line number Diff line change
@@ -47,18 +47,18 @@ export class Asset extends cdk.Construct {
/**
* Attribute that represents the name of the bucket this asset exists in.
*/
public readonly s3BucketName: s3.BucketName;
public readonly s3BucketName: string;

/**
* Attribute which represents the S3 object key of this asset.
*/
public readonly s3ObjectKey: s3.ObjectKey;
public readonly s3ObjectKey: string;

/**
* Attribute which represents the S3 URL of this asset.
* @example https://s3.us-west-1.amazonaws.com/bucket/key
*/
public readonly s3Url: s3.S3Url;
public readonly s3Url: string;

/**
* Resolved full-path location of this asset.
@@ -70,7 +70,7 @@ export class Asset extends cdk.Construct {
/**
* The S3 prefix where all different versions of this asset are stored
*/
private readonly s3Prefix: cdk.Token;
private readonly s3Prefix: string;

constructor(parent: cdk.Construct, id: string, props: GenericAssetProps) {
super(parent, id);
@@ -94,10 +94,10 @@ export class Asset extends cdk.Construct {
description: `S3 key for asset version "${this.path}"`
});

this.s3BucketName = new s3.BucketName(bucketParam.value);
this.s3Prefix = new cdk.FnSelect(0, new cdk.FnSplit(cxapi.ASSET_PREFIX_SEPARATOR, keyParam.value));
const s3Filename = new cdk.FnSelect(1, new cdk.FnSplit(cxapi.ASSET_PREFIX_SEPARATOR, keyParam.value));
this.s3ObjectKey = new s3.ObjectKey(new cdk.FnConcat(this.s3Prefix, s3Filename));
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.s3ObjectKey = `${this.s3Prefix}${s3Filename}`;

this.bucket = s3.BucketRef.import(this, 'AssetBucket', {
bucketName: this.s3BucketName
10 changes: 5 additions & 5 deletions packages/@aws-cdk/aws-apigateway/lib/deployment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import cdk = require('@aws-cdk/cdk');
import crypto = require('crypto');
import { cloudformation, DeploymentId } from './apigateway.generated';
import { cloudformation } from './apigateway.generated';
import { RestApiRef } from './restapi-ref';

export interface DeploymentProps {
@@ -55,7 +55,7 @@ export interface DeploymentProps {
* automatically for the `restApi.latestDeployment` deployment.
*/
export class Deployment extends cdk.Construct implements cdk.IDependable {
public readonly deploymentId: DeploymentId;
public readonly deploymentId: string;
public readonly api: RestApiRef;

/**
@@ -78,7 +78,7 @@ export class Deployment extends cdk.Construct implements cdk.IDependable {
}

this.api = props.api;
this.deploymentId = new DeploymentId(() => this.resource.ref);
this.deploymentId = new cdk.Token(() => this.resource.deploymentId).toString();
this.dependencyElements.push(this.resource);
}

@@ -142,13 +142,13 @@ class LatestDeploymentResource extends cloudformation.DeploymentResource {
* Returns a lazy reference to this resource (evaluated only upon synthesis).
*/
public get ref() {
return new DeploymentId(() => ({ Ref: this.lazyLogicalId }));
return new cdk.Token(() => ({ Ref: this.lazyLogicalId })).toString();
}

/**
* Does nothing.
*/
public set ref(_v: DeploymentId) {
public set ref(_v: string) {
return;
}

2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts
Original file line number Diff line number Diff line change
@@ -68,7 +68,7 @@ export class AwsIntegration extends Integration {
super({
type,
integrationHttpMethod: 'POST',
uri: cdk.Arn.fromComponents({
uri: cdk.ArnUtils.fromComponents({
service: 'apigateway',
account: backend,
resource: apiType,
12 changes: 6 additions & 6 deletions packages/@aws-cdk/aws-apigateway/lib/method.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cdk = require('@aws-cdk/cdk');
import { AuthorizerId, cloudformation, MethodId } from './apigateway.generated';
import { cloudformation } from './apigateway.generated';
import { Integration } from './integration';
import { MockIntegration } from './integrations/mock';
import { IRestApiResource } from './resource';
@@ -26,7 +26,7 @@ export interface MethodOptions {
* NOTE: in the future this will be replaced with an `AuthorizerRef`
* construct.
*/
authorizerId?: AuthorizerId;
authorizerId?: string;

/**
* Indicates whether the method requires clients to submit a valid API key.
@@ -65,7 +65,7 @@ export interface MethodProps {
}

export class Method extends cdk.Construct {
public readonly methodId: MethodId;
public readonly methodId: string;
public readonly httpMethod: string;
public readonly resource: IRestApiResource;
public readonly restApi: RestApi;
@@ -115,7 +115,7 @@ export class Method extends cdk.Construct {
* NOTE: {stage} will refer to the `restApi.deploymentStage`, which will
* automatically set if auto-deploy is enabled.
*/
public get methodArn(): cdk.Arn {
public get methodArn(): string {
if (!this.restApi.deploymentStage) {
throw new Error('There is no stage associated with this restApi. Either use `autoDeploy` or explicitly assign `deploymentStage`');
}
@@ -128,7 +128,7 @@ export class Method extends cdk.Construct {
* Returns an execute-api ARN for this method's "test-invoke-stage" stage.
* This stage is used by the AWS Console UI when testing the method.
*/
public get testMethodArn(): cdk.Arn {
public get testMethodArn(): string {
return this.restApi.executeApiArn(this.httpMethod, this.resource.resourcePath, 'test-invoke-stage');
}

@@ -156,7 +156,7 @@ export class Method extends cdk.Construct {
credentials = options.credentialsRole.roleArn;
} else if (options.credentialsPassthrough) {
// arn:aws:iam::*:user/*
credentials = cdk.Arn.fromComponents({ service: 'iam', region: '', account: '*', resource: 'user', sep: '/', resourceName: '*' });
credentials = cdk.ArnUtils.fromComponents({ service: 'iam', region: '', account: '*', resource: 'user', sep: '/', resourceName: '*' });
}

return {
8 changes: 4 additions & 4 deletions packages/@aws-cdk/aws-apigateway/lib/resource.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cdk = require('@aws-cdk/cdk');
import { cloudformation, ResourceId } from './apigateway.generated';
import { cloudformation } from './apigateway.generated';
import { Integration } from './integration';
import { Method, MethodOptions } from './method';
import { RestApi } from './restapi';
@@ -18,7 +18,7 @@ export interface IRestApiResource {
/**
* The ID of the resource.
*/
readonly resourceId: ResourceId;
readonly resourceId: string;

/**
* The full path of this resuorce.
@@ -85,7 +85,7 @@ export interface ResourceProps extends ResourceOptions {

export class Resource extends cdk.Construct implements IRestApiResource {
public readonly resourceApi: RestApi;
public readonly resourceId: ResourceId;
public readonly resourceId: string;
public readonly resourcePath: string;
public readonly defaultIntegration?: Integration;
public readonly defaultMethodOptions?: MethodOptions;
@@ -102,7 +102,7 @@ export class Resource extends cdk.Construct implements IRestApiResource {
};
const resource = new cloudformation.Resource(this, 'Resource', resourceProps);

this.resourceId = resource.ref;
this.resourceId = resource.resourceId;
this.resourceApi = props.parent.resourceApi;

// render resource path (special case for root)
9 changes: 4 additions & 5 deletions packages/@aws-cdk/aws-apigateway/lib/restapi-ref.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import cdk = require('@aws-cdk/cdk');
import { RestApiId } from './apigateway.generated';

export interface RestApiRefProps {
/**
* The REST API ID of an existing REST API resource.
*/
restApiId: RestApiId;
restApiId: string;
}

export abstract class RestApiRef extends cdk.Construct {
@@ -23,21 +22,21 @@ export abstract class RestApiRef extends cdk.Construct {
/**
* The ID of this API Gateway RestApi.
*/
public readonly abstract restApiId: RestApiId;
public readonly abstract restApiId: string;

/**
* Exports a REST API resource from this stack.
* @returns REST API props that can be imported to another stack.
*/
public export(): RestApiRefProps {
return {
restApiId: new RestApiId(new cdk.Output(this, 'RestApiId', { value: this.restApiId }).makeImportValue()),
restApiId: new cdk.Output(this, 'RestApiId', { value: this.restApiId }).makeImportValue().toString()
};
}
}

class ImportedRestApi extends RestApiRef {
public restApiId: RestApiId;
public restApiId: string;

constructor(parent: cdk.Construct, id: string, props: RestApiRefProps) {
super(parent, id);
10 changes: 5 additions & 5 deletions packages/@aws-cdk/aws-apigateway/lib/restapi.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import iam = require('@aws-cdk/aws-iam');
import cdk = require('@aws-cdk/cdk');
import { cloudformation, ResourceId, RestApiId } from './apigateway.generated';
import { cloudformation } from './apigateway.generated';
import { Deployment } from './deployment';
import { Integration } from './integration';
import { Method, MethodOptions } from './method';
@@ -139,7 +139,7 @@ export class RestApi extends RestApiRef implements cdk.IDependable {
/**
* The ID of this API Gateway RestApi.
*/
public readonly restApiId: RestApiId;
public readonly restApiId: string;

/**
* API Gateway deployment that represents the latest changes of the API.
@@ -216,7 +216,7 @@ export class RestApi extends RestApiRef implements cdk.IDependable {
defaultIntegration: props.defaultIntegration,
defaultMethodOptions: props.defaultMethodOptions,
resourceApi: this,
resourceId: new ResourceId(resource.restApiRootResourceId),
resourceId: resource.restApiRootResourceId,
resourcePath: '/'
};
}
@@ -258,7 +258,7 @@ export class RestApi extends RestApiRef implements cdk.IDependable {
method = '*';
}

return cdk.Arn.fromComponents({
return cdk.ArnUtils.fromComponents({
service: 'execute-api',
resource: this.restApiId,
sep: '/',
@@ -315,7 +315,7 @@ export class RestApi extends RestApiRef implements cdk.IDependable {
private configureCloudWatchRole(apiResource: cloudformation.RestApiResource) {
const role = new iam.Role(this, 'CloudWatchRole', {
assumedBy: new cdk.ServicePrincipal('apigateway.amazonaws.com'),
managedPolicyArns: [ cdk.Arn.fromComponents({
managedPolicyArns: [ cdk.ArnUtils.fromComponents({
service: 'iam',
region: '',
account: 'aws',
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-apigateway/lib/stage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cdk = require('@aws-cdk/cdk');
import { cloudformation, StageName } from './apigateway.generated';
import { cloudformation } from './apigateway.generated';
import { Deployment } from './deployment';
import { RestApiRef } from './restapi-ref';
import { parseMethodOptionsPath } from './util';
@@ -127,7 +127,7 @@ export interface MethodDeploymentOptions {
}

export class Stage extends cdk.Construct implements cdk.IDependable {
public readonly stageName: StageName;
public readonly stageName: string;
public readonly dependencyElements = new Array<cdk.IDependable>();

private readonly restApi: RestApiRef;
Loading

0 comments on commit 6508f78

Please sign in to comment.