Skip to content

Commit

Permalink
Phase 2 - remaining implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
skinny85 committed Dec 4, 2020
1 parent a43000b commit 1c98223
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,44 @@
"AccelerateConfiguration": {
"AccelerationStatus": "Enabled",
"PropertyNotInCfnSchema": false
},
"CorsConfiguration": {
"CorsRules": [
{
"AllowedMethods": [
"GET"
],
"AllowedOrigins": [
"origin1"
],
"MaxAge": 5,
"PropertyNotInCfnSchema": "unmodeled property in array"
}
]
}
}
},
"Function": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Handler": "index.handler",
"Runtime": "nodejs12.x",
"CodeUri": {
"Bucket": "bucket",
"Key": "key"
},
"Events": {
"Api": {
"Properties": {
"Method": "GET",
"Path": "/"
},
"Type": "Api",
"PropertyNotInCfnSchema": "unmodeled property in map"
}
}
}
}
}
},
"Transform": "AWS::Serverless-2016-10-31"
}
112 changes: 67 additions & 45 deletions packages/@aws-cdk/core/lib/cfn-parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,37 @@ import { CfnTag } from './cfn-tag';
import { Lazy } from './lazy';
import { CfnReference, ReferenceRendering } from './private/cfn-reference';
import { IResolvable } from './resolvable';
import { Mapper, Validator } from './runtime';
import { Validator } from './runtime';
import { isResolvableObject, Token } from './token';
import { undefinedIfAllValuesAreEmpty } from './util';

/**
* The class used as the intermediate result from the generated L1 methods
* that convert from CloudFormation's UpperCase to CDK's lowerCase property names.
* Saves any extra properties that were present in the argument object,
* but that were not found in the CFN schema,
* so that they're not lost from the final CDK-rendered template.
*/
export class FromCloudFormationResult<T> {
public readonly value: T;
public readonly extraProperties: { [key: string]: any };

public constructor(value: T) {
this.value = value;
this.extraProperties = {};
}

public appendExtraProperty(key: string, val: any): void {
this.extraProperties[key] = val;
}

public appendExtraProperties(prefix: string, properties: { [key: string]: any } | undefined): void {
for (const [key, val] of Object.entries(properties ?? {})) {
this.extraProperties[`${prefix}.${key}`] = val;
}
}
}

/**
* This class contains static methods called when going from
* translated values received from {@link CfnParser.parseValue}
Expand Down Expand Up @@ -145,7 +172,14 @@ export class FromCloudFormation {
return new FromCloudFormationResult(value);
}

return new FromCloudFormationResult(value.map(arg => mapper(arg).value));
const values = new Array<any>();
const ret = new FromCloudFormationResult(values);
for (let i = 0; i < value.length; i++) {
const result = mapper(value[i]);
values.push(result.value);
ret.appendExtraProperties(`${i}`, result.extraProperties);
}
return ret;
};
}

Expand All @@ -159,11 +193,14 @@ export class FromCloudFormation {
return new FromCloudFormationResult(value);
}

const ret: { [key: string]: T } = {};
const values: { [key: string]: T } = {};
const ret = new FromCloudFormationResult(values);
for (const [key, val] of Object.entries(value)) {
ret[key] = mapper(val).value;
const result = mapper(val);
values[key] = result.value;
ret.appendExtraProperties(key, result.extraProperties);
}
return new FromCloudFormationResult(ret);
return ret;
};
}

Expand All @@ -179,19 +216,19 @@ export class FromCloudFormation {
/**
* Return a function that, when applied to a value, will return the first validly deserialized one
*/
public static getTypeUnion(_validators: Validator[], _mappers: Mapper[]): (x: any) => any {
// return (value: any): any => {
// for (let i = 0; i < validators.length; i++) {
// const candidate = mappers[i](value);
// if (validators[i](candidate).isSuccess) {
// return candidate;
// }
// }
//
// // if nothing matches, just return the input unchanged, and let validators catch it
// return value;
// };
throw new Error('I will figure out getTypeUnion() later');
public static getTypeUnion(validators: Validator[], mappers: Array<(x: any) => FromCloudFormationResult<any>>):
(x: any) => FromCloudFormationResult<any> {
return (value: any) => {
for (let i = 0; i < validators.length; i++) {
const candidate = mappers[i](value);
if (validators[i](candidate.value).isSuccess) {
return candidate;
}
}

// if nothing matches, just return the input unchanged, and let validators catch it
return new FromCloudFormationResult(value);
};
}
}

Expand Down Expand Up @@ -240,21 +277,6 @@ export interface FromCloudFormationOptions {
readonly parser: CfnParser;
}

/**
* The class used as the intermediate result from the generated L1 methods
* that convert from CloudFormation's UpperCase to CDK's lowerCase property names.
* Saves any extra properties that were present in the argument object,
* but that were not found in the CFN schema,
* so that they're not lost from the final CDK-rendered template.
*/
export class FromCloudFormationResult<T> {
public readonly value: T;

public constructor(value: T) {
this.value = value;
}
}

/**
* The context in which the parsing is taking place.
*
Expand Down Expand Up @@ -362,16 +384,16 @@ export class CfnParser {
if (typeof p !== 'object') { return undefined; }

return undefinedIfAllValuesAreEmpty({
minSuccessfulInstancesPercent: FromCloudFormation.getNumber(p.MinSuccessfulInstancesPercent),
minSuccessfulInstancesPercent: FromCloudFormation.getNumber(p.MinSuccessfulInstancesPercent).value,
});
}

function parseResourceSignal(p: any): CfnResourceSignal | undefined {
if (typeof p !== 'object') { return undefined; }

return undefinedIfAllValuesAreEmpty({
count: FromCloudFormation.getNumber(p.Count),
timeout: FromCloudFormation.getString(p.Timeout),
count: FromCloudFormation.getNumber(p.Count).value,
timeout: FromCloudFormation.getString(p.Timeout).value,
});
}
}
Expand All @@ -387,8 +409,8 @@ export class CfnParser {
autoScalingRollingUpdate: parseAutoScalingRollingUpdate(policy.AutoScalingRollingUpdate),
autoScalingScheduledAction: parseAutoScalingScheduledAction(policy.AutoScalingScheduledAction),
codeDeployLambdaAliasUpdate: parseCodeDeployLambdaAliasUpdate(policy.CodeDeployLambdaAliasUpdate),
enableVersionUpgrade: FromCloudFormation.getBoolean(policy.EnableVersionUpgrade),
useOnlineResharding: FromCloudFormation.getBoolean(policy.UseOnlineResharding),
enableVersionUpgrade: FromCloudFormation.getBoolean(policy.EnableVersionUpgrade).value,
useOnlineResharding: FromCloudFormation.getBoolean(policy.UseOnlineResharding).value,
});

function parseAutoScalingReplacingUpdate(p: any): CfnAutoScalingReplacingUpdate | undefined {
Expand All @@ -403,12 +425,12 @@ export class CfnParser {
if (typeof p !== 'object') { return undefined; }

return undefinedIfAllValuesAreEmpty({
maxBatchSize: FromCloudFormation.getNumber(p.MaxBatchSize),
minInstancesInService: FromCloudFormation.getNumber(p.MinInstancesInService),
minSuccessfulInstancesPercent: FromCloudFormation.getNumber(p.MinSuccessfulInstancesPercent),
pauseTime: FromCloudFormation.getString(p.PauseTime),
suspendProcesses: FromCloudFormation.getStringArray(p.SuspendProcesses),
waitOnResourceSignals: FromCloudFormation.getBoolean(p.WaitOnResourceSignals),
maxBatchSize: FromCloudFormation.getNumber(p.MaxBatchSize).value,
minInstancesInService: FromCloudFormation.getNumber(p.MinInstancesInService).value,
minSuccessfulInstancesPercent: FromCloudFormation.getNumber(p.MinSuccessfulInstancesPercent).value,
pauseTime: FromCloudFormation.getString(p.PauseTime).value,
suspendProcesses: FromCloudFormation.getStringArray(p.SuspendProcesses).value,
waitOnResourceSignals: FromCloudFormation.getBoolean(p.WaitOnResourceSignals).value,
});
}

Expand All @@ -427,7 +449,7 @@ export class CfnParser {
if (typeof p !== 'object') { return undefined; }

return undefinedIfAllValuesAreEmpty({
ignoreUnmodifiedGroupSizeProperties: p.IgnoreUnmodifiedGroupSizeProperties,
ignoreUnmodifiedGroupSizeProperties: FromCloudFormation.getBoolean(p.IgnoreUnmodifiedGroupSizeProperties).value,
});
}
}
Expand Down
49 changes: 33 additions & 16 deletions tools/cfn2ts/lib/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,11 @@ export default class CodeGenerator {
// translate the template properties to CDK objects
this.code.line('const resourceProperties = options.parser.parseValue(resourceAttributes.Properties);');
// translate to props, using a (module-private) factory function
this.code.line(`const props = ${genspec.fromCfnFactoryName(propsType).fqn}(resourceProperties).value;`);
this.code.line(`const propsResult = ${genspec.fromCfnFactoryName(propsType).fqn}(resourceProperties);`);
// finally, instantiate the resource class
this.code.line(`const ret = new ${resourceName.className}(scope, id, props);`);
// save all keys from the resourceProperties that are not in the current CFN schema in the resource using overrides
const resourceSchemaPropertyNames = Object.keys(spec.Properties || {}).map(cfnType => `'${cfnType}'`);
this.code.line(`const resourcePropsOutsideCfnSchema = ${CFN_PARSE}.FromCloudFormation.omit(resourceProperties ?? {}, ${resourceSchemaPropertyNames.join(', ')});`);
this.code.openBlock('for (const [propKey, propVal] of Object.entries(resourcePropsOutsideCfnSchema)) ');
this.code.line(`const ret = new ${resourceName.className}(scope, id, propsResult.value);`);
// save all keys from extraProperties in the resource using property overrides
this.code.openBlock('for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) ');
this.code.line('ret.addPropertyOverride(propKey, propVal);');
this.code.closeBlock();
} else {
Expand Down Expand Up @@ -549,10 +547,8 @@ export default class CodeGenerator {
}

this.code.line('properties = properties || {};');
// Generate the return object
this.code.indent(`return new ${CFN_PARSE}.FromCloudFormationResult({`);
const self = this;

const self = this;
// class used for the visitor
class FromCloudFormationFactoryVisitor implements genspec.PropertyVisitor<string> {
public visitAtom(type: genspec.CodeName): string {
Expand Down Expand Up @@ -627,16 +623,16 @@ export default class CodeGenerator {
}
}

Object.keys(nameConversionTable).forEach(cfnName => {
const propSpec = propSpecs[cfnName];
const simpleCfnPropAccessExpr = `properties.${cfnName}`;
for (const [cfnPropName, cdkPropName] of Object.entries(nameConversionTable)) {
const propSpec = propSpecs[cfnPropName];
const simpleCfnPropAccessExpr = `properties.${cfnPropName}`;
const deserializedExpression = genspec.typeDispatch<string>(resource, propSpec, new FromCloudFormationFactoryVisitor()) +
`(${simpleCfnPropAccessExpr}).value`;
`(${simpleCfnPropAccessExpr})`;

let valueExpression = propSpec.Required
? deserializedExpression
: `${simpleCfnPropAccessExpr} != null ? ${deserializedExpression} : undefined`;
if (schema.isTagPropertyName(cfnName)) {
if (schema.isTagPropertyName(cfnPropName)) {
// Properties that have names considered to denote tags
// have their type generated without a union with IResolvable.
// However, we can't possibly know that when generating the factory
Expand All @@ -648,11 +644,32 @@ export default class CodeGenerator {
valueExpression += ' as any';
}

self.code.line(`${nameConversionTable[cfnName]}: ${valueExpression},`);
});
self.code.line(`const ${cdkPropName}Result = ${valueExpression};`);
}

// Generate the return object
this.code.indent(`const ret = new ${CFN_PARSE}.FromCloudFormationResult({`);
for (const cdkPropName of Object.values(nameConversionTable)) {
this.code.line(`${cdkPropName}: ${cdkPropName}Result?.value,`);
}
// close the return object brace
this.code.unindent('});');

// append all extra properties to the return object
for (const [cfnPropName, cdkPropName] of Object.entries(nameConversionTable)) {
this.code.line(`ret.appendExtraProperties('${cfnPropName}', ${cdkPropName}Result?.extraProperties);`);
}
// save any extra properties we find on this level
const omittedProperties = Object.keys(nameConversionTable)
.map(cfnPropName => `'${cfnPropName}'`)
.join(', ');
this.code.openBlock(`for (const [key, val] of Object.entries(${CFN_PARSE}.FromCloudFormation.omit(properties, ${omittedProperties}))) `);
this.code.line('ret.appendExtraProperty(key, val);');
this.code.closeBlock();

// return the result object
this.code.line('return ret;');

// close the function brace
this.code.closeBlock();
}
Expand Down

0 comments on commit 1c98223

Please sign in to comment.