Integration & Automation
Use Python to manage third-party resources in AWS CloudFormation
In this post, I demonstrate how to use Python and the AWS CloudFormation registry to manage third-party resources, which are common components of modern cloud architectures. The benefits of using CloudFormation to manage third-party resources include reducing deployment complexity and enabling the inherent benefits of CloudFormation, such as rollback if a failure occurs.
Using the CloudFormation Command Line Interface (CLI), I create a CloudFormation resource provider, which enables you to manage GitHub repositories and AWS resources from a single template.
About this blog post | |
Time to read | ~10 min |
Time to complete | ~45 min |
Cost | ~$1 |
Learning level | Advanced (300) |
AWS services | AWS CloudFormation |
Solution overview
The provided example is modeled after a GitHub repository, which enables you to manage repositories as part of an AWS CloudFormation template. This post is divided into the following steps:
- Set up development environment
- Initialize provider
- Add handler code
- Declare dependencies
- Submit resource to AWS CloudFormation
- Test resource
Prerequisites
This post assumes that you’re familiar with AWS CloudFormation template, Python, and GitHub. For this walkthrough, you must have the following:
1. Set up development environment
Get your development environment running by completing the following steps:
a. Install Python 3.6 or later by either downloading Python or using your operating system’s package manager.
b. Use the following command to install both the AWS CloudFormation CLI and Python language plugin:
2. Initialize provider
a. The CloudFormation CLI provides a command to bootstrap a resource provider. Use the following commands to create an empty folder and initialize a new provider:
c. The CloudFormation CLI prompts you for the name of the resource type, which maps to the Type attribute for resources in an AWS CloudFormation template. For this example, use Demo::GitHub::Repository
, and choose python37
for the language.
d. Choose whether to use Docker to package Python dependencies. This is useful for resources that have binary dependencies that use a specific platform or architecture. For this resource, all of the dependencies are Python based, so you can choose to disable Docker builds.
You have now initialized your project.
Resource schema
AWS CloudFormation resource providers use JSON to declare a schema. The schema primarily declares which properties the resource accepts and which outputs it provides to template authors (via !GetAtt
). For more information, see Resource type schema.
For modeling GitHub repositories, use any text editor to open demo-github-repository.json, and replace its contents with the following JSON code:
The properties that are not included in the readOnlyProperties section are available when declaring the resource in an AWS CloudFormation template.
AccessToken: Token used to authenticate the GitHub API. Note that AccessToken
is included in the primaryIdentifier
section of the schema. This ensures that the token is passed to read and list handlers.
Name: Repository name.
Org: GitHub organization where the repository is created. If this is unspecified, the repository is created in your account and not in an org. This property is defined in the createOnlyProperties section, which means it cannot be changed using the GitHub API. If a stack update changes this value, the AWS CloudFormation API attempts to create a repository in org, which replaces the previous repository. For more information, see Replacement.
Visibility: Defines whether the repository is private or public. By default, the repository is set to private. Note that this behavior is defined in the handler source code in the following section.
Properties that are marked readOnly cannot be defined by the user, but the properties are available using !GetAtt
in the template. This resource defines the following read-only properties:
HttpsUrl/SshUrl: URLs that can be used to clone the repository.
Namespace: Full name of the repository, which joins org with the repository name, separated by a forward slash.
Id: When a repository is created, this ID is returned by the GitHub API. It is used as the PrimaryIdentifier for the resource, which can be retrieved by using the !Ref
function in the CloudFormation template.
Each time the schema updates, the project must regenerate to ensure that the CloudFormation CLI project code is in sync with the schema. To do this, run the following command from within the folder you used to initialize the project:
3. Add handler code
Handler code manages CREATE, UPDATE, DELETE, and READ operations on the stack that contains the resource type. To simplify this, the CloudFormation CLI generates example code in the src/demo_github_repository/handlers.py file.
Open this file, and replace its contents with the following code, which implements the CREATE, UPDATE, DELETE, and READ handlers required by AWS CloudFormation to facilitate the modeling of the resource:
In the Create handler, I catch errors that indicate a repository with the same name already exists. I then raise an AlreadyExists exception to let AWS CloudFormation know what kind of error it is. Similarly, in the get_repo
function—called by the UPDATE, DELETE, and READ handlers—I trap exceptions that indicate that the resource doesn’t exist and to raise a NotFound exception. By using the exception types provided by the AWS CloudFormation resource library, I can control behaviors such as retries and the information returned to the user.
4. Declare dependencies
I must use the Python support library to enable the handler (cloudformation-cli-python-lib). In this case, I also need the PyGithub repository, which simplifies interactions with the GitHub API. To do this, I use a standard Python requirements.txt file, which is created when the project initializes. It exists in the root folder and already contains the CloudFormation CLI support library, so I only need to add PyGithub. When it’s complete, the file contains the following:
5. Submit resource to AWS CloudFormation
In this example, I specify US West (Oregon) as the AWS Region, but you can use any Region that supports AWS CloudFormation.
6. Test resource
Create a template that launches a stack, and save the YAML file as github-repo.yaml:
The template uses a dynamic reference to an AWS Secrets Manager secret to store the GitHub access token. So, before I launch the stack, I create and store an access token. For more information, see Creating a personal access token.
Replace <ACCESS_TOKEN> with your token value. If you changed the AWS Region when you registered the resource, ensure that you update it to reflect the AWS Region for your resource.
Now you can use the following command to launch the stack:
Stack creation takes a few minutes. The stack output contains the repository details, which can be retrieved using the following command:
Sign in to GitHub. You should see a new repository called test-repo-from-cfn.
From here, experiment by updating the stack to change the repository name, set the visibility to public, and specify a GitHub organization.
Cleanup
To avoid incurring future costs, use the following commands to delete the stack and resource type:
In this post, I used Python to create, register, and use a resource type. AWS CloudFormation manages all of your application’s components and makes the template a single source of truth that extends the benefits of CloudFormation to your third-party party components.
To start building your own resource, see Creating resource types. Use the comments to provide feedback about this post or to let me know which resources you’re looking for.