-
Notifications
You must be signed in to change notification settings - Fork 4k
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(cfn-include): preserve properties of resources that are not in the current CFN schema #11822
Conversation
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Money question I have after all of this:
Why not use CFN overrides to remember the properties and make sure they're rendered back out?
* @returns a new object with all the entries in `object`, | ||
* except those whose keys are one of the names provided in `props` | ||
*/ | ||
public static omit(object: object, ...props: string[]): object { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The location of this function is a tough un' eh.
It strictly speaking has nothing to do with FromCloudFormation
, so it should be a generic Util
function. But on the other hand, it is only used there and it's nice and convenient to put it here. Oh well, I guess?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it was put here more for convenience than anything else.
The only other place I can think of that makes sense for it is in runtime.ts
in @aws-cdk/core
.
Let me know what you think!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think runtime.ts
might make for a good location, yeah.
1326cb8
to
2e26abc
Compare
Excellent question. There is a reason, and it's nested properties. While adding the property overrides would work for top-level properties, like this (present in the test of the initial version of this PR): {
"Resources": {
"Bucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"PropertyNotInCfnSchema": 1
}
}
}
} It would not work for unknown properties in nested complex types, like this: {
"Resources": {
"Bucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"AccelerateConfiguration": {
"AccelerationStatus": "Enabled",
"PropertyNotInCfnSchema": false
}
}
}
}
} For that second case, we need to save the unrecognized property in the converters generated for each of the complex property types of the resource. I don't think that can be reasonably achieved with property overrides without huge changes to the current code generator. (BTW, I've added this second case to the tests in this PR) |
@@ -264,7 +264,7 @@ export class Distribution extends Resource implements IDistribution { | |||
} | |||
|
|||
const originId = this.addOrigin(props.defaultBehavior.origin); | |||
this.defaultBehavior = new CacheBehavior(originId, { pathPattern: '*', ...props.defaultBehavior }); | |||
this.defaultBehavior = new CacheBehavior(originId, props.defaultBehavior); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would not work for unknown properties in nested complex types, like this:
But it would, as far as I'm aware (or at least it should). Properties can be dot-separated to indicate deep properties, like so:
cfnResource.addPropertyOverride('AccelerateConfiguration.PropertyNotInCfnSchema', false);
Am I missing something?
* @returns a new object with all the entries in `object`, | ||
* except those whose keys are one of the names provided in `props` | ||
*/ | ||
public static omit(object: object, ...props: string[]): object { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think runtime.ts
might make for a good location, yeah.
…he current CFN schema Currently, if cloudformation-include encounters a property in one of the resources the CDK has an L1 for that is not in the version of the CloudFormation resource schema the module uses, the property will be silently dropped. Of course, that is not ideal. Make changes to our L1 code generation to preserve all properties that are not in the CFN schema. Fixes aws#9717
…Result<PropType>.
6a9da5e
to
1c98223
Compare
@rix0rrr this is ready for another review. I've changed to using property overrides, like we discussed. I kind of hate how the code looks because of that, especially the changes done in The |
I agree that it currently doesn't look great. On the one hand, it's generated code so I really don't care what it looks like, only that it does the right thing. But I see what you mean. We have to do 2 things (instead of 1) to the return value of every inner parser, which requires that we declare a variable to hold the intermediary result. But what if we defined an additional function which does the 2 things that we need to a parameter? Then it becomes one line per property again, which is rather clean. I added in one more convenience class which is intended to be used as an accumulator. It cleans this: function CfnAccessPointPropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult<CfnAccessPointProps> {
properties = properties || {};
const bucketResult = cfn_parse.FromCloudFormation.getString(properties.Bucket);
const creationDateResult = properties.CreationDate != null ? cfn_parse.FromCloudFormation.getString(properties.CreationDate) : undefined;
const nameResult = properties.Name != null ? cfn_parse.FromCloudFormation.getString(properties.Name) : undefined;
const networkOriginResult = properties.NetworkOrigin != null ? cfn_parse.FromCloudFormation.getString(properties.NetworkOrigin) : undefined;
const policyResult = properties.Policy != null ? cfn_parse.FromCloudFormation.getAny(properties.Policy) : undefined;
const policyStatusResult = properties.PolicyStatus != null ? cfn_parse.FromCloudFormation.getAny(properties.PolicyStatus) : undefined;
const publicAccessBlockConfigurationResult = properties.PublicAccessBlockConfiguration != null ? CfnAccessPointPublicAccessBlockConfigurationPropertyFromCloudFormation(properties.PublicAccessBlockConfiguration) : undefined;
const vpcConfigurationResult = properties.VpcConfiguration != null ? CfnAccessPointVpcConfigurationPropertyFromCloudFormation(properties.VpcConfiguration) : undefined;
const ret = new cfn_parse.FromCloudFormationResult({
bucket: bucketResult?.value,
creationDate: creationDateResult?.value,
name: nameResult?.value,
networkOrigin: networkOriginResult?.value,
policy: policyResult?.value,
policyStatus: policyStatusResult?.value,
publicAccessBlockConfiguration: publicAccessBlockConfigurationResult?.value,
vpcConfiguration: vpcConfigurationResult?.value,
});
ret.appendExtraProperties('Bucket', bucketResult?.extraProperties);
ret.appendExtraProperties('CreationDate', creationDateResult?.extraProperties);
ret.appendExtraProperties('Name', nameResult?.extraProperties);
ret.appendExtraProperties('NetworkOrigin', networkOriginResult?.extraProperties);
ret.appendExtraProperties('Policy', policyResult?.extraProperties);
ret.appendExtraProperties('PolicyStatus', policyStatusResult?.extraProperties);
ret.appendExtraProperties('PublicAccessBlockConfiguration', publicAccessBlockConfigurationResult?.extraProperties);
ret.appendExtraProperties('VpcConfiguration', vpcConfigurationResult?.extraProperties);
for (const [key, val] of Object.entries(cfn_parse.FromCloudFormation.omit(properties, 'Bucket', 'CreationDate', 'Name', 'NetworkOrigin', 'Policy', 'PolicyStatus', 'PublicAccessBlockConfiguration', 'VpcConfiguration'))) {
ret.appendExtraProperty(key, val);
}
return ret;
} Up to: function CfnAccessPointPropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult<CfnAccessPointProps> {
properties = properties || {};
const ret = new cfn_parse.FromCloudFormationPropertyObject<CfnAccessPointProps>();
ret.addResult('bucket', 'Bucket', cfn_parse.FromCloudFormation.getString(properties.Bucket));
ret.addResult('creationDate', 'CreationDate', properties.CreationDate != null ? cfn_parse.FromCloudFormation.getString(properties.CreationDate) : undefined);
ret.addResult('name', 'Name', properties.Name != null ? cfn_parse.FromCloudFormation.getString(properties.Name) : undefined);
ret.addResult('networkOrigin', 'NetworkOrigin', properties.NetworkOrigin != null ? cfn_parse.FromCloudFormation.getString(properties.NetworkOrigin) : undefined);
ret.addResult('policy', 'Policy', properties.Policy != null ? cfn_parse.FromCloudFormation.getAny(properties.Policy) : undefined);
ret.addResult('policyStatus', 'PolicyStatus', properties.PolicyStatus != null ? cfn_parse.FromCloudFormation.getAny(properties.PolicyStatus) : undefined);
ret.addResult('publicAccessBlockConfiguration', 'PublicAccessBlockConfiguration', properties.PublicAccessBlockConfiguration != null ? CfnAccessPointPublicAccessBlockConfigurationPropertyFromCloudFormation(properties.PublicAccessBlockConfiguration) : undefined);
ret.addResult('vpcConfiguration', 'VpcConfiguration', properties.VpcConfiguration != null ? CfnAccessPointVpcConfigurationPropertyFromCloudFormation(properties.VpcConfiguration) : undefined);
for (const [key, val] of Object.entries(cfn_parse.FromCloudFormation.omit(properties, 'Bucket', 'CreationDate', 'Name', 'NetworkOrigin', 'Policy', 'PolicyStatus', 'PublicAccessBlockConfiguration', 'VpcConfiguration'))) {
ret.appendExtraProperty(key, val);
}
return ret;
} That's a lot better already, no? And it occurs to me that the for-loop at the end contains a duplicate list of properties that have already been set on the accumulation object. So in fact we could even simplify to this: function CfnAccessPointPropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult<CfnAccessPointProps> {
properties = properties || {};
const ret = new cfn_parse.FromCloudFormationPropertyObject<CfnAccessPointProps>();
ret.addResult('bucket', 'Bucket', cfn_parse.FromCloudFormation.getString(properties.Bucket));
ret.addResult('creationDate', 'CreationDate', properties.CreationDate != null ? cfn_parse.FromCloudFormation.getString(properties.CreationDate) : undefined);
ret.addResult('name', 'Name', properties.Name != null ? cfn_parse.FromCloudFormation.getString(properties.Name) : undefined);
ret.addResult('networkOrigin', 'NetworkOrigin', properties.NetworkOrigin != null ? cfn_parse.FromCloudFormation.getString(properties.NetworkOrigin) : undefined);
ret.addResult('policy', 'Policy', properties.Policy != null ? cfn_parse.FromCloudFormation.getAny(properties.Policy) : undefined);
ret.addResult('policyStatus', 'PolicyStatus', properties.PolicyStatus != null ? cfn_parse.FromCloudFormation.getAny(properties.PolicyStatus) : undefined);
ret.addResult('publicAccessBlockConfiguration', 'PublicAccessBlockConfiguration', properties.PublicAccessBlockConfiguration != null ? CfnAccessPointPublicAccessBlockConfigurationPropertyFromCloudFormation(properties.PublicAccessBlockConfiguration) : undefined);
ret.addResult('vpcConfiguration', 'VpcConfiguration', properties.VpcConfiguration != null ? CfnAccessPointVpcConfigurationPropertyFromCloudFormation(properties.VpcConfiguration) : undefined);
ret.addUnknownPropertiesAsExtra(properties);
return ret;
} That also means we can get rid of the What do you think? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving whether or not to clean up the final omit
loop up to you. Also if you want to rename any classes, or simplify some more, that's up to you.
I'm happy with this approach. Provisional approval.
Thanks Rico. I've incorporated your comment about getting rid of Merging this in now! |
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
…he current CFN schema (aws#11822) Currently, if cloudformation-include encounters a property in one of the resources the CDK has an L1 for that is not in the version of the CloudFormation resource schema the module uses, the property will be silently dropped. Of course, that is not ideal. Make changes to our L1 code generation to preserve all properties that are not in the CFN schema. Fixes aws#9717 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…he current CFN schema (aws#11822) Currently, if cloudformation-include encounters a property in one of the resources the CDK has an L1 for that is not in the version of the CloudFormation resource schema the module uses, the property will be silently dropped. Of course, that is not ideal. Make changes to our L1 code generation to preserve all properties that are not in the CFN schema. Fixes aws#9717 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Currently, if cloudformation-include encounters a property in one of the resources the CDK has an L1 for
that is not in the version of the CloudFormation resource schema the module uses,
the property will be silently dropped. Of course, that is not ideal.
Make changes to our L1 code generation to preserve all properties that are not in the CFN schema.
Fixes #9717
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license