-
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(cdk): Expose props as read/write properties in CFN resources #2372
Conversation
f2b737c
to
af39691
Compare
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.
added questions about my current design, seeking some early feedback.
Class = 'CLASS', | ||
} | ||
|
||
interface EmitPropertyProps { |
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.
this is dying
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.
Would be useful (for the PR) to see an example of some generated code
Makes me concerned now that mixing up these changes is going to fail miserably and will potentially really confuse people. @rix0rrr @RomainMuller @skinny85 what do you guys think?
Here's my proposal: let's add a postfix Do you think we should maybe remove the bucket prefix ( |
To make sure I understand, specifically Also I suppose CFN updating the spec to include this information is off the table? |
Yes, you are correct.
Not at the moment. |
I like that.
I agree with that as well. |
So now that I get into this fix I'm starting to think this fix is like drawing the short straw ... this is going to break a lot of things requiring a lot name changes. I think I have a couple of ideas on some ways to shortcut that, but this one could be ugly. So @rix0rrr @skinny85 @eladb @RomainMuller let's get agreement on the proposal above
This is what role would generate like under that change. Everything referencing export class CfnRole extends cdk.CfnResource {
/**
* The CloudFormation resource type name for this resource class.
*/
public static readonly resourceTypeName = "AWS::IAM::Role";
/**
* @cloudformationAttribute Arn
*/
public readonly arnAttr: string;
/**
* @cloudformationAttribute RoleId
*/
public readonly roleIdAttr: string;
/**
* @cloudformationReference Ref
* @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html
*/
public readonly refAttr: string;
/**
* `AWS::IAM::Role.AssumeRolePolicyDocument`
* @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-assumerolepolicydocument
*/
public assumeRolePolicyDocument: object | cdk.Token;
/**
* `AWS::IAM::Role.ManagedPolicyArns`
* @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-managepolicyarns
*/
public managedPolicyArns: string[];
/**
* `AWS::IAM::Role.MaxSessionDuration`
* @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-maxsessionduration
*/
public maxSessionDuration: number | cdk.Token;
/**
* `AWS::IAM::Role.Path`
* @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-path
*/
public path: string;
/**
* `AWS::IAM::Role.PermissionsBoundary`
* @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-permissionsboundary
*/
public permissionsBoundary: string;
/**
* `AWS::IAM::Role.Policies`
* @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-policies
*/
public policies: Array<CfnRole.PolicyProperty | cdk.Token> | cdk.Token;
/**
* `AWS::IAM::Role.RoleName`
* @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-rolename
*/
public roleName: string;
/**
* Create a new `AWS::IAM::Role`.
*
* @param scope - scope in which this resource is defined
* @param id - scoped id of the resource
* @param props - resource properties
*/
constructor(scope: cdk.Construct, id: string, props: CfnRoleProps) {
super(scope, id, { type: CfnRole.resourceTypeName, properties: props });
cdk.requireProperty(props, 'assumeRolePolicyDocument', this);
this.arnAttr = this.getAtt('Arn').toString();
this.roleIdAttr = this.getAtt('RoleId').toString();
this.refAttr = this.ref.toString();
}
// .... |
Looks good to me, and indeed you are correct that this is going to be break all of our L2 code. I am wondering if it makes sense to use a common prefix for all attributes instead of a common suffix: role.attrRef
role.attrRoleId
role.attrArn I think this will be more organized and easier to discover, and also clearly distinguishes all attributes from props. Alternatively, we could also just leave the type prefix ( @skinny85 @RomainMuller @rix0rrr any preferences?
My vote I think is option B as it is the most true to CFN and removes all heuristics from our generator, but I don't have a hugely strong opinion - I am fine with any of these options. |
I think I like B as well. The prefix organizes better in my mind and maps directly to CFN (even it I don't agree with it, it's been around a long time). |
If I let go how ugly everything is, I suppose I prefer B as well. |
@eladb I might have missed this, do we want to "remap" the values in the L2 or make them match?
Right now I was keeping the L2 as |
I pushed an example change of the renaming for aws-iam. Yes, I'm aware more codegen work will be necessary for the |
UPDATE -- see next comment @eladb @rix0rrr -- looking for a little guidance here: I think JSII reflect has an issue where the base class hides the interface that holds the attributes (cfn attributes/refs). See here: https://github.com/awslabs/aws-cdk/blob/master/packages/@aws-cdk/aws-iam/lib/group.ts#L54 Versus Role that does a lot of code duplicate here: https://github.com/awslabs/aws-cdk/blob/master/packages/@aws-cdk/aws-iam/lib/role.ts#L112 GroupBase I think is only used to DRY up that import call, but I believe it hides the |
I was wrong! The issue is unlike the base class for other resources Group does not export GroupBase. What is the right convention here, export the abstract base class for Group (and create one for role)? |
Preferably we shouldn't export the base class, and jsii will copy the declarations (incl. any interface definitions and docstrings) from the base class to the derived classes. Can you describe the issue you are running into? |
4808108
to
f823eb2
Compare
The issue I'm hitting is that the |
8356baf
to
6193da3
Compare
I've fixed aws-iam and aws-kms package. I've also changed the design a bit. I'm now creating all the class members for CFN resources and if they are optional I'm enabling |
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.
Looks good. Can you somehow include in the PR (or just upload to gist) an example of a .generated.ts file? I want to see the resulting API...
tools/awslint/lib/rules/resource.ts
Outdated
for (const p of this.construct.classType.allProperties) { | ||
if (p.protected) { | ||
continue; // skip any protected properties | ||
} | ||
|
||
// an attribute property is a property which starts with the type name | ||
// (e.g. "bucketXxx") and/or has an @attribute doc tag. | ||
// an attribute property is a property which starts with `attr` |
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.
This is not true for L2s, only L1s
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.
still open -- will fix next
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.
Still open - will update next.
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.
updated to only L1 resources
@@ -152,7 +153,12 @@ resourceLinter.add({ | |||
message: 'resources must represent all cloudformation attributes as attribute properties. missing property: ', | |||
eval: e => { | |||
for (const name of e.ctx.cfn.attributeNames) { |
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.
Feels like we should make attributeNames
the clean attribute names without the attr
prefix and then we won't need all this manipulation.
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.
So I naively tried to do this, but what do you do for all the situations where attributes and property names collide? For example, CfnCapacityReservation
availabiltyZone
is both attribute and property (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservation.html#cfn-ec2-capacityreservation-availabilityzone). No easy answer here with the naming convention from CloudFormation that I see? We can eliminate all attrRef
and just use ref
.
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.
What I mean here is that I still want generated L1 attribute properties to have the “attr” prefix but the array “attributeNames” in awslint should be the canonical attribute names without the prefix so it will be easy to work with in awslint
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.
alright -- I was I little dense there, I think I made the changes you wanted now.
ac60000
to
abfdd77
Compare
Gist of current kms.generated.ts |
@eladb starting to look at the name collisions: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-flowlog.html#cfn-ec2-flowlog-resourcetype The resourceType property collides with CfnResource.resourceType. This happens to be the property. We didn't prefix these for readability, but now I'm questioning either a prefix or containing object (again).? |
I prefer not to prefix for these (hopefully rare) edge cases. The base classes should have a pretty minimal API, which I am open to renaming. For example, we can add a “cfn” prefix so it becomes cfnResouceType. What else do we have there? |
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 codegen looks good, so I think you can start with the overall fix. A few comments remaining on linter
DependsOn: ignoreEmpty(renderDependsOn(this.dependsOn)), | ||
CreationPolicy: capitalizePropertyNames(this, this.options.creationPolicy), | ||
CreationPolicy: capitalizePropertyNames(this, this.options.creationPolicy), | ||
UpdatePolicy: capitalizePropertyNames(this, this.options.updatePolicy), | ||
UpdateReplacePolicy: capitalizePropertyNames(this, this.options.updateReplacePolicy), | ||
DeletionPolicy: capitalizePropertyNames(this, this.options.deletionPolicy), | ||
Metadata: ignoreEmpty(this.options.metadata), | ||
Condition: this.options.condition && this.options.condition.logicalId | ||
}, props => { |
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.
Curious why we moved renderProperies outside of PostResolve. I suspect the reasoning hasn’t changed or am I missing something?
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.
Also feel like it should be inside.
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.
moved back, but had to re-separate properties and validator/mapper. So the real issue is in the render call we have the mapper/validators and those need to operate on resolved fields.
@@ -67,13 +66,6 @@ export class CfnResourceReflection { | |||
|
|||
this.namespace = fullname.split('::').slice(0, 2).join('::'); | |||
|
|||
// special-case | |||
const basename = this.basename |
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.
We still need these special cases for rendering the L2 linter
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.
yes - I thought they would come back, but I was in a clean everything out.
tools/awslint/lib/rules/resource.ts
Outdated
@@ -152,7 +153,9 @@ resourceLinter.add({ | |||
message: 'resources must represent all cloudformation attributes as attribute properties. missing property: ', | |||
eval: e => { | |||
for (const name of e.ctx.cfn.attributeNames) { | |||
const found = e.ctx.attributes.find(a => a.names.includes(name)); | |||
const lookup = name.startsWith(e.ctx.basename) ? |
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.
Something here still feels wrong. The purpose of this linter rule is to verify that the L2 has a property for each CFN resource attribute with the format “” (I.e bucketArn, topicName,...).
Let’s first render the desired L2 property name in the ResourceReflection class with all the heuristics and special cases (previously those were at the CfnResourceReflection), and then just check that the class has all these properties.
DependsOn: ignoreEmpty(renderDependsOn(this.dependsOn)), | ||
CreationPolicy: capitalizePropertyNames(this, this.options.creationPolicy), | ||
CreationPolicy: capitalizePropertyNames(this, this.options.creationPolicy), | ||
UpdatePolicy: capitalizePropertyNames(this, this.options.updatePolicy), | ||
UpdateReplacePolicy: capitalizePropertyNames(this, this.options.updateReplacePolicy), | ||
DeletionPolicy: capitalizePropertyNames(this, this.options.deletionPolicy), | ||
Metadata: ignoreEmpty(this.options.metadata), | ||
Condition: this.options.condition && this.options.condition.logicalId | ||
}, props => { |
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.
Also feel like it should be inside.
This description will be updated before merging:
This is an initial pass only fixing the generation of property class
members.
There are a good number of collisions with attributes (e.g. Fn::GetAtt)
with the names of the properties. In this implementation I chose to
append
Prop
to all property class members. I'm open to othersuggestions, but my thinkig was attributes have precedence on this name
and the use of L1 property manipulation should be less common.
I have not removed the propertyOverrides feature yet. I am seeking
feedback on the approach I took for minimal impact on generation. Please
see my code comments below.
WIP: #2100
Pull Request Checklist
design
folderBy submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license.