Skip to content

Commit

Permalink
feat(servicecatalogappregistry): initial L2 construct for Application (
Browse files Browse the repository at this point in the history
…aws#15140)

Service Catalog AppRegistry application construct initial base version.
Please note the ARNS for this construct have two '/' in them hence the slightly different ARN parsing.

Testing done
------------------
* `yarn build && yarn test`
* `yarn integ`

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*



Co-authored-by: Dillon Ponzo <dponzo18@gmail.com>
  • Loading branch information
2 people authored and hollanddd committed Aug 26, 2021
1 parent 0c16aad commit ae109d4
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 8 deletions.
36 changes: 33 additions & 3 deletions packages/@aws-cdk/aws-servicecatalogappregistry/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# AWS::ServiceCatalogAppRegistry Construct Library
# AWS ServiceCatalogAppRegistry Construct Library
<!--BEGIN STABILITY BANNER-->

---
Expand All @@ -9,12 +9,42 @@
>
> [CFN Resources]: https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib
![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge)

> The APIs of higher level constructs in this module are experimental and under active development.
> They are subject to non-backward compatible changes or removal in any future version. These are
> not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be
> announced in the release notes. This means that while you may use them, you may need to update
> your source code when upgrading to a newer version of this package.
---

<!--END STABILITY BANNER-->

This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project.
[AWS Service Catalog App Registry](https://docs.aws.amazon.com/servicecatalog/latest/adminguide/appregistry.html)
enables organizations to create and manage repositores of applications and associated resources.


```ts
import * as appreg from '@aws-cdk/aws-servicecatalogappregistry';
```

## Application

An AppRegistry application enables you to define your applications and associated resources.
The application name must be unique at the account level, but is mutable.

```ts
const application = new appreg.Application(this, 'MyFirstApplication', {
applicationName: 'MyFirstApplicationName',
description: 'description for my application', // the description is optional
});
```

An application that has been created outside of the stack can be imported into your CDK app.
Applications can be imported by their ARN via the `Application.fromApplicationArn()` API:

```ts
import servicecatalogappregistry = require('@aws-cdk/aws-servicecatalogappregistry');
const importedApplication = appreg.Application.fromApplicationArn(this, 'MyImportedApplication',
'arn:aws:servicecatalog:us-east-1:012345678910:/applications/0aqmvxvgmry0ecc4mjhwypun6i');
```
98 changes: 98 additions & 0 deletions packages/@aws-cdk/aws-servicecatalogappregistry/lib/application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as cdk from '@aws-cdk/core';
import { InputValidator } from './private/validation';
import { CfnApplication } from './servicecatalogappregistry.generated';

// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
// eslint-disable-next-line no-duplicate-imports, import/order
import { Construct } from 'constructs';

/**
* A Service Catalog AppRegistry Application.
*/
export interface IApplication extends cdk.IResource {
/**
* The ARN of the application.
* @attribute
*/
readonly applicationArn: string;

/**
* The ID of the application.
* @attribute
*/
readonly applicationId: string;
}

/**
* Properties for a Service Catalog AppRegistry Application
*/
export interface ApplicationProps {
/**
* Enforces a particular physical application name.
*/
readonly applicationName: string;

/**
* Description for application.
* @default - No description provided
*/
readonly description?: string;
}

abstract class ApplicationBase extends cdk.Resource implements IApplication {
public abstract readonly applicationArn: string;
public abstract readonly applicationId: string;
}

/**
* A Service Catalog AppRegistry Application.
*/
export class Application extends ApplicationBase {
/**
* Imports an Application construct that represents an external application.
*
* @param scope The parent creating construct (usually `this`).
* @param id The construct's name.
* @param applicationArn the Amazon Resource Name of the existing AppRegistry Application
*/
public static fromApplicationArn(scope: Construct, id: string, applicationArn: string): IApplication {
const arn = cdk.Stack.of(scope).splitArn(applicationArn, cdk.ArnFormat.SLASH_RESOURCE_SLASH_RESOURCE_NAME);
const applicationId = arn.resourceName;

if (!applicationId) {
throw new Error('Missing required Application ID from Application ARN: ' + applicationArn);
}

class Import extends ApplicationBase {
public readonly applicationArn = applicationArn;
public readonly applicationId = applicationId!;
}

return new Import(scope, id, {
environmentFromArn: applicationArn,
});
}

public readonly applicationArn: string;
public readonly applicationId: string;

constructor(scope: Construct, id: string, props: ApplicationProps) {
super(scope, id);

this.validateApplicationProps(props);

const application = new CfnApplication(this, 'Resource', {
name: props.applicationName,
description: props.description,
});

this.applicationArn = application.attrArn;
this.applicationId = application.attrId;
}

private validateApplicationProps(props: ApplicationProps) {
InputValidator.validateLength(this.node.path, 'application name', 1, 256, props.applicationName);
InputValidator.validateRegex(this.node.path, 'application name', /^[a-zA-Z0-9-_]+$/, props.applicationName);
InputValidator.validateLength(this.node.path, 'application description', 0, 1024, props.description);
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-servicecatalogappregistry/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './application';

// AWS::ServiceCatalogAppRegistry CloudFormation Resources:
export * from './servicecatalogappregistry.generated';
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as cdk from '@aws-cdk/core';

/**
* Class to validate that inputs match requirements.
*/
export class InputValidator {
/**
* Validates length is between allowed min and max lengths.
*/
public static validateLength(resourceName: string, inputName: string, minLength: number, maxLength: number, inputString?: string): void {
if (!cdk.Token.isUnresolved(inputString) && inputString !== undefined && (inputString.length < minLength || inputString.length > maxLength)) {
throw new Error(`Invalid ${inputName} for resource ${resourceName}, must have length between ${minLength} and ${maxLength}, got: '${this.truncateString(inputString, 100)}'`);
}
}

/**
* Validates a regex.
*/
public static validateRegex(resourceName: string, inputName: string, regex: RegExp, inputString?: string): void {
if (!cdk.Token.isUnresolved(inputString) && inputString !== undefined && !regex.test(inputString)) {
throw new Error(`Invalid ${inputName} for resource ${resourceName}, must match regex pattern ${regex}, got: '${this.truncateString(inputString, 100)}'`);
}
}

private static truncateString(string: string, maxLength: number): string {
if (string.length > maxLength) {
return string.substring(0, maxLength) + '[truncated]';
}
return string;
}
}
13 changes: 8 additions & 5 deletions packages/@aws-cdk/aws-servicecatalogappregistry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,26 @@
},
"license": "Apache-2.0",
"devDependencies": {
"@aws-cdk/assert-internal": "0.0.0",
"@types/jest": "^26.0.23",
"cdk-build-tools": "0.0.0",
"cdk-integ-tools": "0.0.0",
"cfn2ts": "0.0.0",
"pkglint": "0.0.0",
"@aws-cdk/assert-internal": "0.0.0"
"pkglint": "0.0.0"
},
"dependencies": {
"@aws-cdk/core": "0.0.0"
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69"
},
"peerDependencies": {
"@aws-cdk/core": "0.0.0"
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69"
},
"engines": {
"node": ">= 10.13.0 <13 || >=13.7.0"
},
"stability": "experimental",
"maturity": "cfn-only",
"maturity": "experimental",
"awscdkio": {
"announce": false
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import '@aws-cdk/assert-internal/jest';
import * as cdk from '@aws-cdk/core';
import * as appreg from '../lib';

describe('Application', () => {
let stack: cdk.Stack;

beforeEach(() => {
stack = new cdk.Stack();
});

test('default application creation', () => {
new appreg.Application(stack, 'MyApplication', {
applicationName: 'testApplication',
});

expect(stack).toMatchTemplate({
Resources: {
MyApplication5C63EC1D: {
Type: 'AWS::ServiceCatalogAppRegistry::Application',
Properties: {
Name: 'testApplication',
},
},
},
});
}),

test('application with explicit description', () => {
const description = 'my test application description';
new appreg.Application(stack, 'MyApplication', {
applicationName: 'testApplication',
description: description,
});

expect(stack).toHaveResourceLike('AWS::ServiceCatalogAppRegistry::Application', {
Description: description,
});
}),

test('application with application tags', () => {
const application = new appreg.Application(stack, 'MyApplication', {
applicationName: 'testApplication',
});

cdk.Tags.of(application).add('key1', 'value1');
cdk.Tags.of(application).add('key2', 'value2');

expect(stack).toHaveResourceLike('AWS::ServiceCatalogAppRegistry::Application', {
Tags: {
key1: 'value1',
key2: 'value2',
},
});
}),

test('for an application imported by ARN', () => {
const application = appreg.Application.fromApplicationArn(stack, 'MyApplication',
'arn:aws:servicecatalog:us-east-1:123456789012:/applications/0aqmvxvgmry0ecc4mjhwypun6i');
expect(application.applicationId).toEqual('0aqmvxvgmry0ecc4mjhwypun6i');
}),

test('fails for application imported by ARN missing applicationId', () => {
expect(() => {
appreg.Application.fromApplicationArn(stack, 'MyApplication',
'arn:aws:servicecatalog:us-east-1:123456789012:/applications/');
}).toThrow(/Missing required Application ID from Application ARN:/);
}),

test('application created with a token description does not throw validation error and creates', () => {
const tokenDescription = new cdk.CfnParameter(stack, 'Description');

new appreg.Application(stack, 'MyApplication', {
applicationName: 'myApplication',
description: tokenDescription.valueAsString,
});

expect(stack).toHaveResourceLike('AWS::ServiceCatalogAppRegistry::Application', {
Description: {
Ref: 'Description',
},
});
}),

test('application created with a token application name does not throw validation error', () => {
const tokenApplicationName= new cdk.CfnParameter(stack, 'ApplicationName');

new appreg.Application(stack, 'MyApplication', {
applicationName: tokenApplicationName.valueAsString,
});

expect(stack).toHaveResourceLike('AWS::ServiceCatalogAppRegistry::Application', {
Name: {
Ref: 'ApplicationName',
},
});
}),

test('fails for application with description length longer than allowed', () => {
expect(() => {
new appreg.Application(stack, 'MyApplication', {
applicationName: 'testApplication',
description: 'too long description'.repeat(1000),
});
}).toThrow(/Invalid application description for resource/);
}),

test('fails for application creation with name too short', () => {
expect(() => {
new appreg.Application(stack, 'MyApplication', {
applicationName: '',
});
}).toThrow(/Invalid application name for resource/);
}),

test('fails for application with name too long', () => {
expect(() => {
new appreg.Application(stack, 'MyApplication', {
applicationName: 'testApplication'.repeat(50),
});
}).toThrow(/Invalid application name for resource/);
}),

test('fails for application with name of invalid characters', () => {
expect(() => {
new appreg.Application(stack, 'MyApplication', {
applicationName: 'My@ppl!iC@ #',
});
}).toThrow(/Invalid application name for resource/);
});
});

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Resources": {
"TestApplication2FBC585F": {
"Type": "AWS::ServiceCatalogAppRegistry::Application",
"Properties": {
"Name": "myApplicationtest",
"Description": "my application description"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as cdk from '@aws-cdk/core';
import * as appreg from '../lib';

const app = new cdk.App();
const stack = new cdk.Stack(app, 'integ-servicecatalogappregistry-application');

new appreg.Application(stack, 'TestApplication', {
applicationName: 'myApplicationtest',
description: 'my application description',
});

app.synth();

0 comments on commit ae109d4

Please sign in to comment.