Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aws-cloudformation): allow specifying custom resource type #943

Merged
merged 2 commits into from
Oct 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions packages/@aws-cdk/aws-cloudformation/lib/custom-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@ export interface CustomResourceProps {
* Properties to pass to the Lambda
*/
properties?: Properties;

/**
* For custom resources, you can specify AWS::CloudFormation::CustomResource
* (the default) as the resource type, or you can specify your own resource
* type name. For example, you can use "Custom::MyCustomResourceTypeName".
*
* Custom resource type names must begin with "Custom::" and can include
* alphanumeric characters and the following characters: _@-. You can specify
* a custom resource type name up to a maximum length of 60 characters. You
* cannot change the type during an update.
*
* Using your own resource type names helps you quickly differentiate the
* types of custom resources in your stack. For example, if you had two custom
* resources that conduct two different ping tests, you could name their type
* as Custom::PingTester to make them easily identifiable as ping testers
* (instead of using AWS::CloudFormation::CustomResource).
*
* @see
* https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html#aws-cfn-resource-type-name
*/
resourceType?: string;
}

/**
Expand All @@ -57,6 +78,10 @@ export class CustomResource extends cloudformation.CustomResource {
});

this.userProperties = props.properties;

if (props.resourceType) {
this.useCustomResourceType(props.resourceType);
}
}

/**
Expand All @@ -67,6 +92,23 @@ export class CustomResource extends cloudformation.CustomResource {
return Object.assign(props, uppercaseProperties(this.userProperties || {}));
}

private useCustomResourceType(resourceType: string) {
if (!resourceType.startsWith('Custom::')) {
throw new Error(`Custom resource type must begin with "Custom::" (${resourceType})`);
}

const typeName = resourceType.substr(resourceType.indexOf('::') + 2);
if (typeName.length > 60) {
throw new Error(`Custom resource type length > 60 (${resourceType})`);
}

if (!/^[a-z0-9_@-]+$/i.test(typeName)) {
throw new Error(`Custom resource type name can only include alphanumeric characters and _@- (${typeName})`);
}

this.addOverride('Type', resourceType);
}

}

/**
Expand Down
57 changes: 55 additions & 2 deletions packages/@aws-cdk/aws-cloudformation/test/test.resource.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect } from '@aws-cdk/assert';
import { expect, haveResource } from '@aws-cdk/assert';
import lambda = require('@aws-cdk/aws-lambda');
import sns = require('@aws-cdk/aws-sns');
import cdk = require('@aws-cdk/cdk');
import { Test } from 'nodeunit';
import { CustomResource } from '../lib';
Expand Down Expand Up @@ -85,7 +86,59 @@ export = {
}
});
test.done();
}
},

'custom resources can specify a resource type that starts with Custom::'(test: Test) {
const stack = new cdk.Stack();
new CustomResource(stack, 'MyCustomResource', {
resourceType: 'Custom::MyCustomResourceType',
topicProvider: new sns.Topic(stack, 'Provider')
});
expect(stack).to(haveResource('Custom::MyCustomResourceType'));
test.done();
},

'fails if custom resource type is invalid': {
'does not start with "Custom::"'(test: Test) {
const stack = new cdk.Stack();

test.throws(() => {
new CustomResource(stack, 'MyCustomResource', {
resourceType: 'NoCustom::MyCustomResourceType',
topicProvider: new sns.Topic(stack, 'Provider')
});
}, /Custom resource type must begin with "Custom::"/);

test.done();
},

'has invalid characters'(test: Test) {
const stack = new cdk.Stack();

test.throws(() => {
new CustomResource(stack, 'MyCustomResource', {
resourceType: 'Custom::My Custom?ResourceType',
topicProvider: new sns.Topic(stack, 'Provider')
});
}, /Custom resource type name can only include alphanumeric characters and/);

test.done();
},

'is longer than 60 characters'(test: Test) {
const stack = new cdk.Stack();

test.throws(() => {
new CustomResource(stack, 'MyCustomResource', {
resourceType: 'Custom::0123456789012345678901234567890123456789012345678901234567891',
topicProvider: new sns.Topic(stack, 'Provider')
});
}, /Custom resource type length > 60/);

test.done();
},

},
};

class TestCustomResource extends cdk.Construct {
Expand Down