Skip to content

Commit

Permalink
Merge pull request #165 from CloudBytes-Academy/article
Browse files Browse the repository at this point in the history
ART: Export & Import CDK Output / Input
  • Loading branch information
rehanhaider authored Oct 27, 2023
2 parents 1fca584 + ad2d0cd commit 303e16b
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 0 deletions.
146 changes: 146 additions & 0 deletions content/aws/50006000-cdk-how-to-use-cfn-outputs.md
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)


149 changes: 149 additions & 0 deletions content/aws/50007000-cdk-how-to-import-output.md
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.

0 comments on commit 303e16b

Please sign in to comment.