Skip to content

Commit

Permalink
Merge pull request #1 from cloudsoft/typeconfig
Browse files Browse the repository at this point in the history
Typeconfig
  • Loading branch information
duncangrant authored Jul 1, 2022
2 parents ddcfb54 + 13040bd commit a57ff23
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 52 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ repos:
- repo: https://github.com/ambv/black
rev: stable
hooks:
- id: black
# language_version: python3.6
- id: black
# language_version: python3.6
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.0.0
hooks:
Expand Down
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ If you are using this package to build resource providers for CloudFormation, in

**Prerequisites**

- Python version 3.6 or above
- [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)
- Your choice of TypeScript IDE
- Python version 3.6 or above
- [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)
- Your choice of TypeScript IDE

**Installation**

Expand Down Expand Up @@ -75,6 +75,19 @@ pip3 install \

That ensures neither is accidentally installed from PyPI.

For changes to the typescript library "@amazon-web-services-cloudformation/cloudformation-cli-typescript-lib" pack up the compiled javascript:

```shell
npm run build
npm pack
```

You can then install this in a cfn resource project using:

```shell
npm install ../path/to/cloudformation-cli-typescript-plugin/amazon-web-services-cloudformation-cloudformation-cli-typescript-lib-1.0.1.tgz
```

Linting and running unit tests is done via [pre-commit](https://pre-commit.com/), and so is performed automatically on commit after being installed (`pre-commit install`). The continuous integration also runs these checks. Manual options are available so you don't have to commit:

```shell
Expand Down
10 changes: 10 additions & 0 deletions python/rpdk/typescript/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,23 @@ def generate(self, project):

models = resolve_models(project.schema)

if project.configuration_schema:
configuration_models = resolve_models(
project.configuration_schema, "TypeConfigurationModel"
)
else:
configuration_models = {"TypeConfigurationModel": {}}

models.update(configuration_models)

path = self.package_root / "models.ts"
LOG.debug("Writing file: %s", path)
template = self.env.get_template("models.ts")
contents = template.render(
lib_name=SUPPORT_LIB_NAME,
type_name=project.type_name,
models=models,
contains_type_configuration=project.configuration_schema,
primaryIdentifier=project.schema.get("primaryIdentifier", []),
additionalIdentifiers=project.schema.get("additionalIdentifiers", []),
)
Expand Down
4 changes: 2 additions & 2 deletions python/rpdk/typescript/templates/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ResourceHandlerRequest,
SessionProxy,
} from '{{lib_name}}';
import { ResourceModel } from './models';
import { ResourceModel, TypeConfigurationModel } from './models';

interface CallbackContext extends Record<string, any> {}

Expand Down Expand Up @@ -154,7 +154,7 @@ class Resource extends BaseResource<ResourceModel> {
}
}

export const resource = new Resource(ResourceModel.TYPE_NAME, ResourceModel);
export const resource = new Resource(ResourceModel.TYPE_NAME, ResourceModel, TypeConfigurationModel);

// Entrypoint for production usage after registered in CloudFormation
export const entrypoint = resource.entrypoint;
Expand Down
9 changes: 9 additions & 0 deletions src/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ export class NotUpdatable extends BaseHandlerException {}

export class InvalidRequest extends BaseHandlerException {}

export class InvalidTypeConfiguration extends BaseHandlerException {
constructor(typeName: string, reason: string) {
super(
`Invalid TypeConfiguration provided for type '${typeName}'. Reason: ${reason}`,
HandlerErrorCode.InvalidTypeConfiguration
);
}
}

export class AccessDenied extends BaseHandlerException {}

export class InvalidCredentials extends BaseHandlerException {}
Expand Down
2 changes: 2 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export enum HandlerErrorCode {
ServiceInternalError = 'ServiceInternalError',
NetworkFailure = 'NetworkFailure',
InternalFailure = 'InternalFailure',
InvalidTypeConfiguration = 'InvalidTypeConfiguration',
}

export interface Credentials {
Expand Down Expand Up @@ -261,6 +262,7 @@ export class RequestData<T = Dict> extends BaseDto {
@Expose() providerCredentials?: Credentials;
@Expose() previousResourceProperties?: T;
@Expose() previousStackTags?: Dict<string>;
@Expose() typeConfiguration?: Dict<string>;
}

export class HandlerRequest<ResourceT = Dict, CallbackT = Dict> extends BaseDto {
Expand Down
53 changes: 40 additions & 13 deletions src/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import 'reflect-metadata';
import { boundMethod } from 'autobind-decorator';

import { AwsTaskWorkerPool, ProgressEvent, SessionProxy } from './proxy';
import { BaseHandlerException, InternalFailure, InvalidRequest } from './exceptions';
import {
BaseHandlerException,
InternalFailure,
InvalidRequest,
InvalidTypeConfiguration,
} from './exceptions';
import {
Action,
BaseModel,
Expand Down Expand Up @@ -40,13 +45,13 @@ const MUTATING_ACTIONS: [Action, Action, Action] = [
Action.Delete,
];

export type HandlerSignature<T extends BaseModel> = Callable<
[Optional<SessionProxy>, any, Dict, LoggerProxy],
export type HandlerSignature<T extends BaseModel, TC extends BaseModel> = Callable<
[Optional<SessionProxy>, any, Dict, TC, LoggerProxy],
Promise<ProgressEvent<T>>
>;
export class HandlerSignatures<T extends BaseModel> extends Map<
export class HandlerSignatures<T extends BaseModel, TC extends BaseModel> extends Map<
Action,
HandlerSignature<T>
HandlerSignature<T, TC>
> {}
class HandlerEvents extends Map<Action, string | symbol> {}

Expand Down Expand Up @@ -88,7 +93,10 @@ function ensureSerialize<T extends BaseModel>(toResponse = false): MethodDecorat
};
}

export abstract class BaseResource<T extends BaseModel = BaseModel> {
export abstract class BaseResource<
T extends BaseModel = BaseModel,
TC extends BaseModel = BaseModel
> {
protected loggerProxy: LoggerProxy;
protected metricsPublisherProxy: MetricsPublisherProxy;

Expand All @@ -111,11 +119,14 @@ export abstract class BaseResource<T extends BaseModel = BaseModel> {
constructor(
public readonly typeName: string,
public readonly modelTypeReference: Constructor<T>,
public readonly typeConfigurationTypeReference: Constructor<TC> & {
deserialize: Function;
},
protected readonly workerPool?: AwsTaskWorkerPool,
private handlers?: HandlerSignatures<T>
private handlers?: HandlerSignatures<T, TC>
) {
this.typeName = typeName || '';
this.handlers = handlers || new HandlerSignatures<T>();
this.handlers = handlers || new HandlerSignatures<T, TC>();

this.lambdaLogger = console;
this.platformLoggerProxy = new LoggerProxy();
Expand Down Expand Up @@ -294,8 +305,8 @@ export abstract class BaseResource<T extends BaseModel = BaseModel> {

public addHandler = (
action: Action,
f: HandlerSignature<T>
): HandlerSignature<T> => {
f: HandlerSignature<T, TC>
): HandlerSignature<T, TC> => {
this.handlers.set(action, f);
return f;
};
Expand All @@ -304,13 +315,14 @@ export abstract class BaseResource<T extends BaseModel = BaseModel> {
session: Optional<SessionProxy>,
request: BaseResourceHandlerRequest<T>,
action: Action,
callbackContext: Dict
callbackContext: Dict,
typeConfiguration?: TC
): Promise<ProgressEvent<T>> => {
const actionName = action == null ? '<null>' : action.toString();
if (!this.handlers.has(action)) {
throw new Error(`Unknown action ${actionName}`);
}
const handleRequest: HandlerSignature<T> = this.handlers.get(action);
const handleRequest: HandlerSignature<T, TC> = this.handlers.get(action);
// We will make the callback context and resource states readonly
// to avoid modification at a later time
deepFreeze(callbackContext);
Expand All @@ -320,6 +332,7 @@ export abstract class BaseResource<T extends BaseModel = BaseModel> {
session,
request,
callbackContext,
typeConfiguration,
this.loggerProxy || this.platformLoggerProxy
);
this.log(`[${action}] handler invoked`);
Expand Down Expand Up @@ -473,6 +486,17 @@ export abstract class BaseResource<T extends BaseModel = BaseModel> {
}
};

private castTypeConfigurationRequest = (request: HandlerRequest): TC => {
try {
return this.typeConfigurationTypeReference.deserialize(
request.requestData.typeConfiguration
);
} catch (err) {
this.log('Invalid Type Configuration');
throw new InvalidTypeConfiguration(this.typeName, `${err} (${err.name}`);
}
};

// @ts-ignore
public async entrypoint(
eventData: any | Dict,
Expand Down Expand Up @@ -500,6 +524,8 @@ export abstract class BaseResource<T extends BaseModel = BaseModel> {
const [callerCredentials, providerCredentials] = credentials;
const request = this.castResourceRequest(event);

const typeConfiguration = this.castTypeConfigurationRequest(event);

let streamName = `${event.awsAccountId}-${event.region}`;
if (event.stackId && request.logicalResourceIdentifier) {
streamName = `${event.stackId}/${request.logicalResourceIdentifier}`;
Expand Down Expand Up @@ -550,7 +576,8 @@ export abstract class BaseResource<T extends BaseModel = BaseModel> {
this.callerSession,
request,
action,
callback
callback,
typeConfiguration
);
} catch (err) {
error = err;
Expand Down
Loading

0 comments on commit a57ff23

Please sign in to comment.