aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.47k stars 3.83k forks source link

(aws-servicecatalog): Get the ProductStack Construct to use Properties of a Stack that it's Initialized in. #24757

Open pepito-j opened 1 year ago

pepito-j commented 1 year ago

Describe the feature

Extend the Construct initializer props of the aws_servicecatalog.ProductStack() object to include some/all properties of the aws_cdk_lib.Stack object it's getting initialized in and that Stack-related information to the initializer of the ProductStack super() call alongside the assetBucket property.

Use Case

Primarily, my reason for opening this is that it will allow useful features such as lookups to be executed within the ProductStack that depend on non-agnostic information. As for other uses, it will have the same capabilities as a normal aws_cdk_lib.Stack

Proposed Solution

The expected input is currently a ProductStackProps object that only intakes an "assetBucket" object.

We can extend this interface to also include some of the normal properties of a Stack object like "env" and pass it to the super() call of the ProductStack Construct.

Other Information

Currently on the ProductStack documentation, it reflects all properties of a normal Stack that you would see on the aws_cdk_lib.Stack page:

To my initial understanding of it, I was expecting to treat a ProductStack like a standalone Stack object where I was attempting to do lookups (i.e. using aws_ec2.Vpc.fromLookup() ) from within the ProductStack. But when attempting to do so after initializing the ProductStack for use within my main stack, this failed with

"RuntimeError: Error: Cannot retrieve value from context provider vpc-provider since account/region are not specified at the stack level."

To me, this was unexpected until I took a deeper into look into it. Upon further investigation and from the examples given on the public documents, it's expected not to propagate information about the Stack it's being initialized in to the underlying ProductStack object:

With how it's currently implemented, this seems to be expected as well such that if you want to initialize the Base ProductStack, you can only pass in an "assetBucket" to be used by the ProductStack's own synthesizer:

Overall, it seems to be implied that ProductStack's won't work similar to the normal capabilities of a standalone aws_cdk_lib.Stack. This is mentioned on the ProductStack doc page where it's not meant to be treated as an independent deployment resource. I can definitely see this such we shouldn't be expecting to be able to do cross-stack references with ProductStacks or should we ever be messing with it's synthesizer as it's currently using it's own iteration, "ProductStackSynthesizer".

But, because we're only concerned with the synthesized asset template I believe adding in an "env" property should be feasible unless there's a reason why ProductStack's are environment agnostic. One potential issue may be with how the synthesizer interacts with account information being tied to it, but I didn't notice any account/region information being used by the ProductStackSynthesier which I thought may interfere with it. To my understanding, it should be able to do lookups like normal so long we propagate the environment info:

I tried doing a deeper look to see if this was expected, but on the overview and main document page for the ProductStack synthesizer, I didn't see anything explicitly stating that this should be environment-agnostic (no information as well about this being needed on the source page of the ProductStack code).

This was also an issue described here but closed as similar workarounds that I had found were followed.

Expanding on it slightly, one workaround for this is to instead use a lookUp() method from outside of the ProductStack and then pass in the information to the object deriving itself from the ProductStack similar to the following:

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as sc from 'aws-cdk-lib/aws-servicecatalog'

class CustomProductStack extends sc.ProductStack  {
  constructor(scope: Construct, id: string, vpc: ec2.IVpc) {
    super(scope, id);

    new cdk.CfnOutput(this, "MyTest", {
      value: vpc.vpcId
    })
    console.log(`Environment within ${id}: ${this.environment}`)

  }
}

const app = new cdk.App();

const stack = new cdk.Stack(app, "MyStack", { env: { account: '******', region: 'us-east-1' }} )

const vpc = ec2.Vpc.fromLookup(stack , "MyDefaultVpc", {isDefault: true})
new CustomProductStack(stack , "ProductStack", vpc)

Another way would be to use from_attribute() methods as mentioned on the related Github issue such that don't invoke backend providers within the derived ProductStack classes since they won't depend on the environment being agnostic or not.

class CustomProductStack extends sc.ProductStack  {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    new cdk.CfnOutput(this, "MyTest", {
      value: ec2.Vpc.fromVpcAttributes(this, "ImportedVPC", { 
        vpcId:"vpc-*****", availabilityZones: ["us-east-1a", "us-east-2a"] 
      }).vpcId
    })
  }
}

Although there's different workarounds that can be followed, I believe this is bound to introduce headaches as it isn't explicitly mentioned on any front-facing docs the limitations of a ProudctStack. Even though it looks like custom environment configurations are allowed per the ProductStack properties, we may not be able to treat it as such with environment configurations being potentially one possible limitation:

Acknowledgements

CDK version used

2.69.0

Environment details (OS name and version, etc.)

Windows 10 | Node v16.13.0

pahud commented 1 year ago

Hi @pepito-j

Thank you for your detailed information. We'd be glad to review your PR when it's ready.