-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Exporting/importing across CDK apps #1095
Comments
I think the solution should be to define some sort of "parameter pack" that has an identifier. In effect, the mechanism would be something like this: // App 1
const props = vpc.export();
stack.publishParameterPack('MyVpc', props);
// Will publish something like:
// MyVpc_VPCID = vpc-12354
// MyVpc_AZs = us-east-1a,us-east-1b, us-east-1c
// MyVpc_SubnetIds = s-123,s-456,s-12435
// ... etc
// App2
const props = stack.readParameterPack('MyVpc');
const vpc = VpcNetwork.import(this, 'MyVpc', props); Obviously the function names and mechanisms here are obtuse and hard to discover, so we should make that more streamlined. |
Better syntax (and I think actually what people would expect) would be this: // App 1
vpc.export('MyVpc');
// App2
const vpc = VpcNetwork.import(this, 'MyVpc'); // Not great because it ties export ID to construct ID
const vpc = VpcNetwork.import(this, 'MyVpc', 'MyVpc'); // Not great because what's with the stutter?
const vpc = VpcNetwork.import('MyVpc'); // Best. Possible if we don't make the imported thing a construct
// But not always possible to not make the imported thing a construct, sometimes it needs to construct other
// resources.
const vpc = VpcNetwork.import(this, 'MyVpc', {
exportName: 'MyVpc' // A fair compromise?
}); |
We can also make |
Something to consider: the use case of importing a construct through a set of concrete values must be idiomatic, and not 2nd class ( Also - can you share some thoughts on implementation? Do you plan to use SSM/stack-import-export, etc? Will this work across regions/accounts? |
I like the export(“boom |
Accidental |
In-app referencesSo in my mind, the ideal situation is that inside the same app, you don't even have to do Of course, as I've been trying to implement it in my spare time, I'm running into the fact that this is HARD to implement at the construct level, specifically because of lazy evaluation: new cdk.Token(() => otherStack.queue.queueArn); The use of
I'd prefer the second one, honestly. ImplementationEven for cross-region access, I think the correct implementation is to put "Outputs" on a stack. The only difference is how we consume them:
For now I was only planning to do the first case. The other ones can be left as an extension for later. Direct and packed importsMy proposal was to do the following: Bucket.import(this, 'Bucket', {
exportName: 'ABC'
});
new ImportedBucket(this, 'Bucket', {
bucketName: 'XYZ'
}); But something tells me you'd prefer this (:wink:): Bucket.import(this, 'Bucket', {
exportName: 'ABC'
});
Bucket.import(this, 'Bucket', {
bucketName: 'XYZ'
}); With a runtime check on the presence of arguments. |
I agree that in-app references should be implicit, and I think it would be enormously valuable to properly model cross references at the construct-level. As you hinted above, I believe we made a mistake and conflated the concepts of lazy evaluation and cross references (via magical "tokens"). The first thing we would need is to encode the source of a cross reference into the token. I think that's something that the As for resolution, it's not only an export that needs to be added, it's also that the value in the consumer side would need to be I wonder if the right way is to add a post-synthesis phase which allows users to reflect on the synthesized output (alongside metadata generated about cross-references for example) and mutate it.
Sounds reasonable
I was thinking of something like: Bucket.importFromAnotherStack(exportName);
// or
Bucket.importFromConcreteValues({ bucketName: ...}); (names pending) |
Don't know whether I'd want
These are easy, since all places where these are instantiated are generated by Less nice are these:
They also return an intrinsic that depends on the source stack, and especially Tokens that don't need an anchor:
Yes. The same Token object may be consumed in two different places, and may need to yield a different output for both I've been solving this by adding context to the Substituting is also something that a
I fear any work done at the template level is going to be incomplete, because IDs across stacks can conflict and at that point we've lost the information that would allow us to tell objects apart (the object identity tells us which actual resource we were referring to, but we can't use that anymore at the template level). Two templates with a cross-stack reference, in which we output the same
We can start working around this with more levels of indirection, but it starts to become convoluted. |
Here's what we do for the cross-stack references: We use the pre-synthesis moment to crawl all stackelements and call |
Import
Some imported resources still need to instantiate new constructs, so they must be constructs themselves. For example: const loadBalancer = ApplicationLoadBalancer.import(...);
loadBalancer.addListener(...); // Works, creates a new `ApplicationListener` object. To make this work, ApplicationLoadBalancer.import(this, 'LoadBalancer', 'ExportName'); First of all:
So I'd prefer for this to be: ApplicationLoadBalancer.import(this, 'LoadBalancer', {
export: 'ExportName'
}); So how about: Resource.import(parent, id, props);
Resource.importDirect(parent, id, props); ExportI've considered doing export like this: class Bucket {
public export(exportName?: string): BucketRefProps;
} In a bid to reduce API friction in migration. Existing code will continue to work (using the return value), but people can supply a name for cross-app reuse. However, I think it's a step in the wrong direction to give people two ways to do the same thing, especially when one of the ways will work in all situations and the other will only work in one of the two situations. There's no downside to exporting/importing by name in a same-app situation, but it's not possible to export/import by props object in a cross-app situation. So why not force everyone to refactor to exporting/importing by name? So the API will be: class Bucket {
public export(exportName: string): void;
} |
After some discussion, decided to park the larger design question in favor of a context provider for VPCs. |
Mental note: we should change the API of VpcNetwork to make it easier to So that means changing: vpc.subnets(SubnetType.Public).map(s => subnetId)
// TO
vpc.subnetIds(SubnetType.Public) So that we can |
Add a context provider for looking up existing VPCs in an account. This is useful if the VPC is defined outside of your CDK app, such as in a different CDK app, by hand or in a CloudFormation template. Addresses some need of #1095.
Add a context provider for looking up existing VPCs in an account. This is useful if the VPC is defined outside of your CDK app, such as in a different CDK app, by hand or in a CloudFormation template. Addresses some need of #1095.
Add a context provider for looking up existing VPCs in an account. This is useful if the VPC is defined outside of your CDK app, such as in a different CDK app, by hand or in a CloudFormation template. Addresses some of the needs in #1095.
I've spent a lot of time on designing and started the implementation of a solution, and eventually realized a solution that's only based on CloudFormation imports/exports is too fringy right now. Let's list the use cases for referencing external resources in the CDK:
There are other use cases that are NOT covered by this change such as:
So, you ask, why do we actually need this serialization/deserialization thing? Well, the initial use case we can planned to solve with serialization/deserialization is:
What does that mean? Let's say CDK App A defines a VPC and wants to "publish" it so other apps that run in the same environment (account/region), this mechanism will let users "export" the entire VPC construct (including all it's details) under some CFN export name and then any app that runs within the same account/region will be able to import it and use it. We don't have strong signal from customers that this is something they urgently need. In most cases, the VPC use case can be addressed using the lookup approach, and in the rare case where someone would need to implement something like import complex constructs across apps within the same account, they can always use The other aspect of this work is the general serialization/deserialization capability it brought with it, and that might have future value in general. For example, we could use this to marshal resources from construction time to the runtime space, save/load from SSM parameter store, etc. Given this situation, we'll punt this work for now. I believe that at some point we will implement a serialization pattern/scheme for constructs, but we should have a more compelling use case before we spend this energy across the entire framework. |
We do have this request actually from ECS, where a very common user story is have team A manage the VPC+Cluster, and teams B-E run individual services on that single cluster. You would want to give each team an individual repository with an individual CDK app, but they all need to import the complex objects VPC+Cluster, managed by team A in a different app. In a CI/CD environment with multiple production stages, a single static ARN is not enough because the ARN will be different for every environment. Yes, team A could vend a A custom lookup might be able to solve this problem, but it's not clear how to reference the intended resources at all. Cluster.lookup(); // Which one, what if there are multiple?
Cluster.lookupByName('x'); // What if the name is automatically generated? I now need to statically define it which brings other problems with CFN. We need an indirection.
Cluster.lookupByNameFoundInSsmps('/SharedClusterName'); // Guess that works but it's a lot of ad-hoc machinery Also, lookups require updates to both toolkit and construct library, which breaks compatibility. |
What is the outcome here or the suggested way to tackle these? I'd like to define a database in a separate CDK app and reference it in other apps. What would be the best way to achieve this? |
@peterjuras I have a workaround for this, the crux is that we use Child:utils.ts
stacks/childService.ts
constructs/childService.ts
Parent (completely different repo):utils.ts
constructs/service.ts
|
Probably offtopic, but in the current doc
What does const vpc = Vpc.fromVpcAttributes(this, `ImportedVpc`, {
vpcId: Fn.importValue('OutputVpcId'),
availabilityZones: [hardcodedAzs]
})
vpc.publicSubnets.map(i=>i.subnetId) // => []
vpc.privateSubnets.map(i=>i.subnetId) // => []
vpc.isolatedSubnets.map(i=>i.subnetId) // => [] |
@ivawzh an export is an output that gets placed into the global CFN scope. If you're using nested stacks, outputs are the way to go. |
Do you mean exporting the VPC ID with something like this? new CfnOutput(this, 'OutputVpcId', {
value: this.vpc.vpcId,
exportName: 'OutputVpcId',
}) But Yes, I am doing cross-cdk-repo VPC sharing. |
@ivawzh yeah, when I originally discovered the workaround I needed to output all of the values that are needed to accurately reference it. Unless the API has changed since I wrote it that is likely what you need to do. |
I am thinking why can't CDK populate the other attributes of VPC dynamically by quering respective VPC APIs using vpcId. |
@rix0rrr although this issue is closed for the particular case of VPCs we still have the same problem for Hosted Zones used accross CDK apps. See #30384. Update: I believe we can create a stack |
exporting/importing works wonders inside a CDK app, but we don't have a good solution across CDK apps.
Case in point: VPCs, which are an ID and the IDs of a variable number of subobjects.
This will come up if multiple teams are running their CDK apps inside the same VPC; the 3 components here would probably be in their own code repositories and sharing between those is not necessarily something we want to force people to do.
Better would be to do do export/import along some identifier, probably, or use a context provider (but would the latter work in a CI/CD context?)
cc @eladb, I want to tackle this soonish.
The text was updated successfully, but these errors were encountered: