-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #165 from CloudBytes-Academy/article
ART: Export & Import CDK Output / Input
- Loading branch information
Showing
7 changed files
with
295 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
Title: CDK Output: How to Output data from a stack | ||
Date: 2023-10-27 | ||
Category: AWS Academy | ||
Series: AWS CDK | ||
series_index: 9 | ||
Tags: aws, cdk, python | ||
Author: Rehan Haider | ||
Summary: Explanation of concept of Outputs, how to use them to share data to other stacks | ||
Keywords: AWS, cdk, python, outputs | ||
|
||
|
||
[TOC] | ||
|
||
Previously, we learnt how to [create multiple stacks]({filename}50004000-cdk-multiple-stacks.md). For most applications that you would build using CDK, you would need to share data between the stacks. For example, you might want to create an S3 bucket in one stack and then a Lambda function in another stack that uses that S3 bucket. | ||
|
||
In this case, you would need to share the name of the S3 bucket between the stacks. This is where Outputs come in. | ||
|
||
## What are Outputs? | ||
|
||
In AWS CloudFormation (which the CDK leverages under the hood), Outputs are a way to export specific values from a stack. These values can be anything: an S3 bucket name, a database connection string, or even a computed value. | ||
|
||
Outputs are especially useful when: | ||
|
||
1. Linking Multiple Stacks: They allow one stack to use a resource from another stack. | ||
2. External Usage: When you want to use a specific value from your cloud infrastructure in an external system or application. | ||
|
||
## How to create an Output in CDK? | ||
|
||
To create an Output in CDK, you need to use the `CfnOutput` class. This class is available in the `aws_cdk` module. | ||
|
||
Create a new file called `cdk_app/s3_stack.py` and add the following code to it: | ||
|
||
```python | ||
# cdk_app/s3_stack.py | ||
from aws_cdk import ( | ||
Stack, | ||
aws_s3 as s3, | ||
RemovalPolicy, | ||
) | ||
|
||
from aws_cdk import CfnOutput # 👈🏽 Import the CfnOutput class | ||
|
||
from constructs import Construct | ||
|
||
class S3Stack(Stack): | ||
BUCKET_ID = "MyS3Bucket" | ||
|
||
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: | ||
super().__init__(scope, construct_id, **kwargs) | ||
|
||
myBucket = s3.Bucket(self, self.BUCKET_ID, removal_policy=RemovalPolicy.DESTROY) | ||
|
||
# 👇🏽 Output the bucket ARN | ||
CfnOutput(self, "S3BucketARN", value=myBucket.bucket_arn, export_name="MyS3BucketARN") | ||
``` | ||
|
||
In the above example, we are creating an S3 bucket and then exporting its ARN as an Output. | ||
|
||
CfnOutput takes the following parameters: | ||
|
||
1. `scope`: The scope of the Output. In this case, we are using the current stack as the scope. | ||
2. `id`: The ID of the Output. This is used to uniquely identify the Output within the stack. | ||
3. `value`: The value of the Output. This can be a string, a number, or even a complex object. | ||
4. `export_name`: The name of the Output. This is used to uniquely identify the Output across stacks. | ||
|
||
### Viewing Outputs | ||
|
||
Let's modify our `app.py` file to view the outputs of our stack. | ||
|
||
```python | ||
# app.py | ||
|
||
import aws_cdk as cdk | ||
|
||
from cdk_app.s3_stack import S3Stack | ||
|
||
app = cdk.App() | ||
|
||
S3Stack(app, "S3Stack") | ||
|
||
app.synth() | ||
``` | ||
|
||
Now, run `cdk deploy` to deploy the stack. Once the stack is deployed, you will see the following output: | ||
|
||
![CDK deploy CfnOutput]({static}/images/aws-academy/50006000-01-cdk-deploy-output.png) | ||
|
||
You can also view the outputs of a stack using the AWS Console. Go to the CloudFormation service and select your stack. Then, click on the Outputs tab. You will see the following: | ||
|
||
![CDK CloudFormation Outputs]({static}/images/aws-academy/50006000-02-cdk-console-output.png) | ||
|
||
### What happens during to Output during `cdk synth`? | ||
|
||
Let's try printing the Output by modifying our `cdk_app/s3_stack.py` file: | ||
|
||
```python | ||
# cdk_app/s3_stack.py | ||
|
||
from aws_cdk import ( | ||
Stack, | ||
aws_s3 as s3, | ||
RemovalPolicy, | ||
) | ||
|
||
from aws_cdk import CfnOutput # 👈🏽 Import the CfnOutput class | ||
|
||
from constructs import Construct | ||
|
||
class S3Stack(Stack): | ||
BUCKET_ID = "MyS3Bucket" | ||
|
||
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: | ||
super().__init__(scope, construct_id, **kwargs) | ||
|
||
myBucket = s3.Bucket(self, self.BUCKET_ID, removal_policy=RemovalPolicy.DESTROY) | ||
|
||
# 👇🏽 Print the bucket ARN | ||
print(myBucket.bucket_arn) | ||
|
||
# 👇🏽 Output the bucket ARN | ||
CfnOutput(self, "S3BucketARN", value=myBucket.bucket_arn, export_name="MyS3BucketARN") | ||
``` | ||
|
||
Now run `cdk synth`, we get the following: | ||
|
||
![CDK synth CfnOutput]({static}/images/aws-academy/50006000-03-cdk-synth-output.png) | ||
|
||
So what is this `Token` that is being printed? Token is a placeholder value that is replaced with the actual value by CloudFormation during deployment. | ||
|
||
So, that means that the value of the Output is not known before deployments and cannot be accessed in our code. We can use a reference but CDK will not be able to resolve it during synth hence if you put in conditional logic based on the value of the Output, it will not work. | ||
|
||
### Print Output values to a file in CDK | ||
|
||
Sometimes, you might want to print the values of the Outputs to a file. For example, you might want to print the values of the Outputs to a file and then use that file in your CI/CD pipeline. | ||
|
||
To do add modify your deployment command as shown below: | ||
|
||
```bash | ||
cdk deploy <stack-name> --outputs-file ./output.json | ||
``` | ||
|
||
This will print the values of the Outputs to a file called `output.json` in the current directory. | ||
|
||
![CDK Output to file]({static}/images/aws-academy/50006000-04-cdk-output-file.png) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
Title: How to Import Stack Output from another stack | ||
Date: 2023-10-27 | ||
Category: AWS Academy | ||
Series: AWS CDK | ||
series_index: 9 | ||
Tags: aws, cdk, python | ||
Author: Rehan Haider | ||
Summary: A guide to importing Stack Outputs and using them as Cross Stack references | ||
Keywords: AWS, cdk, python, outputs | ||
|
||
|
||
In the previous article, I explained how to export data from a stack using Outputs. Outputs are a way to export specific values from a stack. These values can be anything: an S3 bucket name, a database connection string, or even a computed value. | ||
|
||
Output can be imported by another stack as a reference helping you access resources created in another stack. | ||
|
||
## How to import an Output in CDK? | ||
|
||
While printing the outputs is useful, the real power of Outputs is when you use them in other stacks. | ||
|
||
We need to use the `Fn.import_value` function to import the value of an Output. This function is available in the `aws_cdk` module. | ||
|
||
|
||
### Export the Output | ||
Let's first create the S3 bucket stack from the previous article. Create a new file called `cdk_app/s3_stack.py` and add the following code to it: | ||
|
||
```python | ||
# cdk_app/s3_stack.py | ||
|
||
# cdk_app/s3_stack.py | ||
|
||
from aws_cdk import ( | ||
Stack, | ||
aws_s3 as s3, | ||
RemovalPolicy, | ||
) | ||
|
||
from aws_cdk import CfnOutput # 👈🏽 Import the CfnOutput class | ||
from constructs import Construct | ||
|
||
class S3Stack(Stack): | ||
BUCKET_ID = "MyS3Bucket" | ||
|
||
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: | ||
super().__init__(scope, construct_id, **kwargs) | ||
|
||
myBucket = s3.Bucket(self, self.BUCKET_ID, removal_policy=RemovalPolicy.DESTROY) | ||
|
||
# 👇🏽 Output the bucket ARN | ||
CfnOutput(self, "S3BucketARN", value=myBucket.bucket_arn, export_name="MyS3BucketARN") | ||
``` | ||
|
||
In the above example, we are creating an S3 bucket and then exporting its ARN as an Output. | ||
|
||
|
||
### Import the Output | ||
|
||
Now, let's create a new stack that will import the S3 bucket ARN. Create a new file called `cdk_app/lambda_stack.py` and add the following code to it: | ||
|
||
```python | ||
# cdk_app/lambda_stack.py | ||
|
||
from aws_cdk import ( | ||
Stack, | ||
aws_lambda as _lambda, | ||
aws_s3 as s3, | ||
) | ||
|
||
from aws_cdk import Fn # 👈🏽 Import the Fn class this contains the import_value method | ||
|
||
from constructs import Construct | ||
|
||
|
||
class LambdaStack(Stack): | ||
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: | ||
super().__init__(scope, construct_id, **kwargs) | ||
|
||
bucket_arn = Fn.import_value("MyS3BucketARN") | ||
|
||
myBucket = s3.Bucket.from_bucket_arn(self, "MyImportedBucket", bucket_arn) | ||
|
||
_lambda.Function( | ||
self, | ||
"MyLambdaFn", | ||
# 👇🏽 Pass the bucket name as an environment variable | ||
environment={"BUCKET_NAME": myBucket.bucket_name}, | ||
runtime=_lambda.Runtime.PYTHON_3_10, | ||
code=_lambda.Code.from_asset("./"), # 👈🏽 Use the current directory as the source | ||
handler="index.main", # 👈🏽 Filename is index.py and the function is called main | ||
) | ||
|
||
``` | ||
|
||
In the above example, we imported the S3 bucket ARN using the `Fn.import_value` method. We then used the `from_bucket_arn` method to create a reference to the S3 bucket. | ||
|
||
We then created a Lambda function and passed the bucket name as an environment variable. | ||
|
||
Now we create the Lambda function in the `cdk_app/lambda/index.py` file: | ||
|
||
```python | ||
# cdk_app/lambda/index.py | ||
|
||
import os | ||
|
||
bucket_name = os.environ["BUCKET_NAME"] | ||
|
||
def main(event, context): | ||
print(f"Bucket Name: {bucket_name}") | ||
|
||
return { | ||
"statusCode": 200, | ||
"body": bucket_name, | ||
} | ||
``` | ||
|
||
|
||
### Deploy the stacks | ||
|
||
Now, let's modify our `app.py` file to deploy both stacks: | ||
|
||
```python | ||
# app.py | ||
|
||
import aws_cdk as cdk | ||
|
||
from cdk_app.s3_stack import S3Stack | ||
from cdk_app.lambda_stack import LambdaStack | ||
|
||
app = cdk.App() | ||
|
||
# LambdaStack depends on S3Stack | ||
|
||
s3_stack = S3Stack(app, "S3Stack") | ||
lambda_stack = LambdaStack(app, "LambdaStack") | ||
|
||
# 👇🏽 Add the dependency to ensure S3Stack is deployed first | ||
lambda_stack.add_dependency(s3_stack) | ||
|
||
app.synth() | ||
``` | ||
|
||
Note that we have added a dependency between the two stacks. This is because the Lambda function depends on the S3 bucket so we need to ensure that the S3 bucket is deployed first. | ||
|
||
Now, run `cdk deploy --all` to deploy the stacks. | ||
|
||
|
||
### Testing the stacks | ||
Go to the AWS Console and run the Lambda function. You will see the following output: | ||
|
||
![Lambda function output]({static}/images/aws-academy/50007000-01-lambda-read-output.png) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.