Skip to content

Commit

Permalink
feat(aws-cloudformation): allow specifying custom resource type (#943)
Browse files Browse the repository at this point in the history
Adds the `resourceType` property to `CustomResource`
which allows specifying the type name for the resource.

Custom resource type names must start with "Custom::" and
have some naming restrictions, which are validated.
  • Loading branch information
Elad Ben-Israel authored Oct 17, 2018
1 parent 5996442 commit 9de3a84
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 2 deletions.
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

0 comments on commit 9de3a84

Please sign in to comment.