Skip to content
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

Improve experience for renaming L1s #207

Closed
rclark opened this issue Jun 30, 2018 · 16 comments
Closed

Improve experience for renaming L1s #207

rclark opened this issue Jun 30, 2018 · 16 comments
Labels
feature-request A feature should be added or improved.

Comments

@rclark
Copy link

rclark commented Jun 30, 2018

Using a L2 construct (I think that's how you refer to them) to define a resource doesn't allow me to explicitly define the resource's logical name.

new Role(stack, "FunctionRole", {
  assumedBy: new ServicePrincipal('lambda.amazonaws.com')
});

==>

      FunctionRole111A5701:
        Type: 'AWS::IAM::Role'
        Properties:
            AssumeRolePolicyDocument:
                Statement:
                    -
                        Action: 'sts:AssumeRole'
                        Effect: Allow
                        Principal:
                            Service: lambda.amazonaws.com
                Version: '2012-10-17'

Reasons that I'd like to be able to set the logical name precisely:

  • if I need to look at my template to debug anything, or if I have workflows that involve inspecting templates in API responses or the console UI, it can be easier to deal with if I can explicitly set my resource's logical name

  • if I have to resort to L1 constructs from '@aws-cdk/resources', the pattern is changed -- while the name argument looks the same, I end up without a suffixed logical resource name

new logs.LogGroupResource(stack, 'Logs', {
  logGroupName: new FnSub('/aws/lambda/${AWS::StackName}') as any,
  retentionInDays: 14
});

==>

    Logs:
        Type: 'AWS::Logs::LogGroup'
        Properties:
            LogGroupName:
                'Fn::Sub': '/aws/lambda/${AWS::StackName}'
            RetentionInDays: 14
@eladb
Copy link
Contributor

eladb commented Jun 30, 2018

Contrary to CloudFormation, which is a flat list of resources, CDK stacks form a tree. We need a way to assign unique and stable Logical IDs that won't cause conflicts.

From the concepts.html#logical-ids documentation topic (#209 includes some improvements):


When you synthesize a stack into an AWS CloudFormation template, the CDK assigns a logical ID, which must be unique within the template, to each resource in the stack.

Each resource in the construct tree has a unique path that represents its location within the tree. The logical ID of a resource is formed by concatenating the names of all of the constructs in the resource’s path, and appending an eight-character MD5 hash of the path. This final component is necessary since AWS CloudFormation logical IDs cannot include the delimiting slash character (/), so simply concatenating the component values does not work. For example, concatenating the components of the path /a/b/c produces abc, which is the same as concatenating the components of the path /ab/c.

Since logical IDs can only use alphanumeric characters and also restricted in length, we are unable to simply use a delimited path as the logical ID. Instead IDs are allocated by concatenating a human-friendly rendition from the path (concatenation, de-duplicate, trim) with a short MD5 hash of the delimited path:

VPCPrivateSubnet2RouteTable0A19E10E
<-----------human---------><-hash->

Low-level CloudFormation Resources that are direct children of the Stack class use their name as their logical ID without modification. This makes it easier to port existing templates into a CDK app.

This scheme ensures that:

  • Logical IDs have a human-friendly portion: this is useful when interacting directly with the synthesized AWS CloudFormation template during development and deployment.
  • Logical IDs are unique within the stack: this is ensured by the MD5 component, which is based on the absolute path to the resource, which is unique within a stack.
  • Logical IDs remain unchanged across updates: this is true as long as their location within the construct tree doesn’t change. See Creating a Runtime Value for information on how to retain logical IDs despite structural changes in your stack.

The AWS CDK applies some heuristics to improve the human-friendliness of the prefix:

  • If a path component is Resource, it is omitted. This postfix does not normally contribute any additional useful information to the ID.
  • If two subsequent names in the path are the same, only one is retained.
  • If the prefix exceeds 240 characters, it is trimmed to 240 characters. This ensures that the total length of the logical ID does not exceed the 255 character AWS CloudFormation limit for logical IDs.

Renaming Logical IDs

The :py:meth:aws-cdk.Stack.renameLogical method can be used to explicitly assign
logical IDs to certain resources, given either their full path or

class MyStack extends Stack {
      constructor(parent: App, name: string, props: StackProps) {
        super(parent, name);

        // note that `renameLogical` must be called /before/ defining the construct.
        // a good practice would be to always put these at the top of your stack initializer.
        this.renameLogical('MyTableCD117FA1', 'MyTable');
        this.renameLogical('MyQueueAB4432A3', 'MyAwesomeQueue');
        
        new Table(this, 'MyTable');
        new Queue(this, 'MyQueue');
      }
 }

@rclark
Copy link
Author

rclark commented Jun 30, 2018

Thanks for all the information.

Contrary to CloudFormation, which is a flat list of resources, CDK stacks form a tree

I'm trying to make sure that I understand this distinction. Can you give an example of where objects in a CDK application form a tree structure? I assume this means a class that you instantiate by providing a parent that is not a Stack instance?

Even in this case, where the CDK models structures as a tree, the serialization of that tree resolves to a flat Resources section of a CloudFormation template. Once that serialization has been accomplished, what's the benefit to preserving the CDK's tree structure through resource logical IDs? It strikes me that it would be far simpler if my cdk synth step just threw an error if I reused the same logical name for two resources.

See Creating a Runtime Value for information on how to retain logical IDs despite structural changes in your stack

🤔 I don't understand how the runtime values section of the documentation relates here. Actually I fundamentally don't understand what the purpose of the runtime values example is. This section, right?

image

@eladb
Copy link
Contributor

eladb commented Jul 2, 2018

I assume this means a class that you instantiate by providing a parent that is not a Stack instance?

Yes. We have designed the programming model of the CDK to encourage modularity of cloud apps. The ability to encapsulate a set of resources behind an abstraction is one of the main motivations for building the CDK. Any class that extends Construct directly or indirectly can "play" in the CDK tree and be used as a first-class building-block in CDK apps. This enables teams to begin organizing their apps into logical units, test them in isolation, reuse them, share them.

Once that serialization has been accomplished, what's the benefit to preserving the CDK's tree structure through resource logical IDs? It strikes me that it would be far simpler if my cdk synth step just threw an error if I reused the same logical name for two resources.

Throwing errors for conflicting IDs will quickly become very frustrating. A simple example would be instantiating a construct a few times within the stack. Already there, every instance will have duplicated IDs. These types of problems compound when consuming reusable construct libraries which you don't even control.

"See Creating a Runtime Value for information on how to retain logical IDs despite structural changes in your stack."

😨 Just a mistake in our docs. Thanks for calling this out. PR #209 should fix this.

@eladb eladb closed this as completed Jul 4, 2018
@eladb
Copy link
Contributor

eladb commented Jul 4, 2018

@rclark let me know if you need further information or if it's okay to close this for now.

@rui-ktei
Copy link

rui-ktei commented Jan 1, 2019

Well.. my problem with this is that we have a lot existing stacks and if I want to use this tool, I need to program it in such way that it generates the stacks which are similar to our existing stacks and I need to keep the original LogicalIDs unchanged otherwise all of our current resources will be removed and recreated.

We defined our own DSL and we wrote an internal tool to parse that DSL to CloudFormation stacks and deploy them. This tool can really reduce our workload because we don't need to write CloudFormation handlebars to parse our DSL anymore - instead we can do it in programmatic way. However, we'd like to keep our resource logical IDs unchanged because of what I mentioned above.

Could you please kindly give any suggestions on this? Thanks

@rclark

@eladb
Copy link
Contributor

eladb commented Jan 1, 2019

If you create a Stack and define CfnXxx constructs as immediate children of the stack, the ID you provide for the CfnXxx constructs will not be adorned with the hash postfix.

So, for example, if your template looks like this:

Resources:
  MyTopicHaha:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: display-name-for-my-topic

You can define a stack with a CfnTopic construct with the ID MyTopicHaha and you'll get the exact same template when you synthesize.

import cdk = require('@aws-cdk/cdk');
import sns = require('@aws-cdk/aws-sns');

export class MyStack extends cdk.Stack {
  constructor(parent: cdk.App, name: string, props?: cdk.StackProps) {
    super(parent, name, props);

    new sns.CfnTopic(this, 'MyTopicHaha', {
      displayName: 'display-name-for-my-topic'
    });
  }
} 

You can also use stack.renameLogical(from, to) to rename a logical ID of a resource.

Let me know if that helps.

@rui-ktei
Copy link

rui-ktei commented Jan 1, 2019

@eladb
Thank you very much for your help!

With your first approach I think that makes sense and that'll work, though I'll have to work with the "raw" CFN models.

For your second approach, stack.renameLogical(from, to): I can't know the generated logical ID (from) until I run cdk synth. My probelm is that I need to do something like this:

    const bucket = new s3.Bucket(this, "MyFirstBucket",...);
    this.renameLogical(bucket.getLogicalId(), 'MyFirstBucket');

Do you know if this is possible or I'm just simply asking for something impractical here? Thank you

@eladb
Copy link
Contributor

eladb commented Jan 1, 2019

You could get the logical ID of an internal resource this way:

const bucketLogicalId = (bucket.findChild('Resource') as cdk.Resource).logicalId;

@rui-ktei
Copy link

rui-ktei commented Jan 2, 2019

@eladb Thank you.
What you mentioned worked for me but I just realized CDK asks me to call renameLogical before new s3.Bucket so the code has to look like this, otherwise it throws exception.

    this.renameLogical(bucket.findChild('Resource') as cdk.Resource).logicalId, 'MyFirstBucket');
    const bucket = new s3.Bucket(this, "MyFirstBucket",...);

However, as you easily see, this won't work because bucket wasn't even created when I call renameLogical.

So it seems there's just no way so far to use s3.Bucket instead of s3.CfnBucket and also allow me to keep my original logical ID, am I correct?

@eladb
Copy link
Contributor

eladb commented Jan 7, 2019

Obviously that’s a good point.

In the meantime, you could synthesize first to determine the generated ID and then use “rename” but that’s a very broken experience.

Let us give this a bit of thought. Reopening for now.

@eladb eladb reopened this Jan 7, 2019
@eladb eladb changed the title Motivation for appending random suffix to resource names? Improve experience for renaming L1s Jan 7, 2019
@eladb eladb added feature-request A feature should be added or improved. needs-design labels Jan 7, 2019
@lkoniecz
Copy link

lkoniecz commented Jul 26, 2019

@eladb
do we have any update on renaming logical of L2 constructs?

this is a functionality a lot of poeple are asking for, but I still wasn't able to find a straight and documented solution

@RomainMuller
Copy link
Contributor

Hey -

With the current release of the CDK, given a CfnResource sub-class (such as CfnBucket) instance, you can use the overrideLogicalId method to set the logical ID to the value of your choice:

import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/core');

const app = new cdk.App();
const stack = new cdk.Stack(app, 'Test');

const bucket = new s3.Bucket(stack, 'MyBucket');

(bucket.node.defaultChild! as cdk.CfnResource).overrideLogicalId('Bazinga');

Synthesizes to:

Resources:
  Bazinga: # Wheeeeee!
    Type: AWS::S3::Bucket
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Metadata:
      aws:cdk:path: Test/MyBucket/Resource

@lkoniecz
Copy link

@RomainMuller
I must of missed that. Thanks a lot!

@lkoniecz
Copy link

@eladb @RomainMuller

Is this feature working only on a subset of resources? Autoscaling Group L2 construct, for instance, does not have default_child property set to underlying Cfn Construct.

@mdforbes500
Copy link

This is causing me a lot of problems with nested stacks aws_cloudformation.NestedStack where I am including a CFN template using CfnInclude and I need the nested stacks to have precise names - since I'm referencing resources using core.Fn.get_att(ChildStack, Outputs.attribute).to_string().

If there anyway to turn off the dynamic naming, maybe a boolean **kwargs? It would make life so much easier for people who are still migrating from CFN to CDK.

@eladb
Copy link
Contributor

eladb commented Mar 26, 2020

This is causing me a lot of problems with nested stacks aws_cloudformation.NestedStack where I am including a CFN template using CfnInclude and I need the nested stacks to have precise names - since I'm referencing resources using core.Fn.get_att(ChildStack, Outputs.attribute).to_string().

If there anyway to turn off the dynamic naming, maybe a boolean **kwargs? It would make life so much easier for people who are still migrating from CFN to CDK.

Perhaps you can use (nested.node.defaultChild as CfnStack).logicalId to obtain the logical id of the stack.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request A feature should be added or improved.
Projects
None yet
Development

No branches or pull requests

6 participants