AWS DevOps & Developer Productivity Blog
Deploying Alexa Skills with the AWS CDK
So you’re expanding your reach by leveraging voice interfaces for your applications through the Alexa ecosystem. You’ve experimented with a new Alexa Skill via the Alexa Developer Console, and now you’re ready to productionalize it for your customers. How exciting!
You are also a proponent of Infrastructure as Code (IaC). You appreciate the speed, consistency, and change management capabilities enabled by IaC. Perhaps you have other applications that you provision and maintain via DevOps practices, and you want to deploy and maintain your Alexa Skill in the same way. Great idea!
That’s where AWS CloudFormation and the AWS Cloud Development Kit (AWS CDK) come in. AWS CloudFormation lets you treat infrastructure as code, so that you can easily model a collection of related AWS and third-party resources, provision them quickly and consistently, and manage them throughout their lifecycles. The AWS CDK is an open-source software development framework for modeling and provisioning your cloud application resources via familiar programming languages, like TypeScript, Python, Java, and .NET. AWS CDK utilizes AWS CloudFormation in the background in order to provision resources in a safe and repeatable manner.
In this post, we show you how to achieve Infrastructure as Code for your Alexa Skills by leveraging powerful AWS CDK features.
Concepts
Alexa Skills Kit (ASK)
In addition to the Alexa Developer Console, skill developers can utilize the Alexa Skills Kit (ASK) to build interactive voice interfaces for Alexa. ASK provides a suite of self-service APIs and tools for building and interacting with Alexa Skills, including the ASK CLI, the Skill Management API (SMAPI), and SDKs for Node.js, Java, and Python. These tools provide a programmatic interface for your Alexa Skills in order to update them with code rather than through a user interface.
AWS CloudFormation
AWS CloudFormation lets you create templates written in either YAML or JSON format to model your infrastructure in code form. CloudFormation templates are declarative and idempotent, allowing you to check them into a versioned code repository, deploy them automatically, and track changes over time.
The ASK CloudFormation resource allows you to incorporate Alexa Skills in your CloudFormation templates alongside your other infrastructure. However, this has limitations that we’ll discuss in further detail in the Problem section below.
AWS Cloud Development Kit (AWS CDK)
Think of the AWS CDK as a developer-centric toolkit that leverages the power of modern programming languages to define your AWS infrastructure as code. When AWS CDK applications are run, they compile down to fully formed CloudFormation JSON/YAML templates that are then submitted to the CloudFormation service for provisioning. Because the AWS CDK leverages CloudFormation, you still enjoy every benefit provided by CloudFormation, such as safe deployment, automatic rollback, and drift detection. AWS CDK currently supports TypeScript, JavaScript, Python, Java, C#, and Go (currently in Developer Preview).
Perhaps the most compelling part of AWS CDK is the concept of constructs—the basic building blocks of AWS CDK apps. The three levels of constructs reflect the level of abstraction from CloudFormation. A construct can represent a single resource, like an AWS Lambda Function, or it can represent a higher-level component consisting of multiple AWS resources.
The three different levels of constructs begin with low-level constructs, called L1 (short for “level 1”) or Cfn (short for CloudFormation) resources. These constructs directly represent all of the resources available in AWS CloudFormation. The next level of constructs, called L2, also represents AWS resources, but it has a higher-level and intent-based API. They provide not only similar functionality, but also the defaults, boilerplate, and glue logic you’d be writing yourself with a CFN Resource construct. Finally, the AWS Construct Library includes even higher-level constructs, called L3 constructs, or patterns. These are designed to help you complete common tasks in AWS, often involving multiple resource types. Learn more about constructs in the AWS CDK developer guide.
One L2 construct example is the Custom Resources module. This lets you execute custom logic via a Lambda Function as part of your deployment in order to cover scenarios that the AWS CDK doesn’t support yet. While the Custom Resources module leverages CloudFormation’s native Custom Resource functionality, it also greatly reduces the boilerplate code in your CDK project and simplifies the necessary code in the Lambda Function. The open-source construct library referenced in the Solution section of this post utilizes Custom Resources to avoid some limitations of what CloudFormation and CDK natively support for Alexa Skills.
Problem
The primary issue with utilizing the Alexa::ASK::Skill
CloudFormation resource, and its corresponding CDK CfnSkill
construct, arises when you define the Skill’s backend Lambda Function in the same CloudFormation template or CDK project. When the Skill’s endpoint is set to a Lambda Function, the ASK service validates that the Skill has the appropriate permissions to invoke that Lambda Function. The best practice is to enable Skill ID verification in your Lambda Function. This effectively restricts the Lambda Function to be invokable only by the configured Skill ID. The problem is that in order to configure Skill ID verification, the Lambda Permission must reference the Skill ID, so it cannot be added to the Lambda Function until the Alexa Skill has been created. If we try creating the Alexa Skill without the Lambda Permission in place, insufficient permissions will cause the validation to fail. The endpoint validation causes a circular dependency preventing us from defining our desired end state with just the native CloudFormation resource.
Unfortunately, the AWS CDK also does not yet support any L2 constructs for Alexa skills. While the ASK Skill Management API is another option, managing imperative API calls within a CI/CD pipeline would not be ideal.
Solution
Overview
AWS CDK is extensible in that if there isn’t a native construct that does what you want, you can simply create your own! You can also publish your custom constructs publicly or privately for others to leverage via package registries like npm, PyPI, NuGet, Maven, etc.
We could write our own code to solve the problem, but luckily this use case allows us to leverage an open-source construct library that addresses our needs. This library is currently available for TypeScript (npm) and Python (PyPI).
The complete solution can be found at the GitHub repository, here. The code is in TypeScript, but you can easily port it to another language if necessary. See the AWS CDK Developer Guide for more guidance on translating between languages.
Prerequisites
You will need the following in order to build and deploy the solution presented below. Please be mindful of any prerequisites for these tools.
- Alexa Developer Account
- AWS Account
- Docker
- Used by CDK for bundling assets locally during synthesis and deployment.
- See Docker website for installation instructions based on your operating system.
- AWS CLI
- Used by CDK to deploy resources to your AWS account.
- See AWS CLI user guide for installation instructions based on your operating system.
- Node.js
- The CDK Toolset and backend runs on Node.js regardless of the project language. See the detailed requirements in the AWS CDK Getting Started Guide.
- See the Node.js website to download the specific installer for your operating system.
Clone Code Repository and Install Dependencies
The code for the solution in this post is located in this repository on GitHub. First, clone this repository and install its local dependencies by executing the following commands in your local Terminal:
# clone repository
git clone https://github.com/aws-samples/aws-devops-blog-alexa-cdk-walkthrough
# navigate to project directory
cd aws-devops-blog-alexa-cdk-walkthrough
# install dependencies
npm install
Note that CLI commands in the sections below (ask
, cdk
) use npx. This executes the command from local project binaries if they exist, or, if not, it installs the binaries required to run the command. In our case, the local binaries are installed as part of the npm install
command above. Therefore, npx
will utilize the local version of the binaries even if you already have those tools installed globally. We use this method to simplify setup and alleviate any issues arising from version discrepancies.
Get Alexa Developer Credentials
To create and manage Alexa Skills via CDK, we will need to provide Alexa Developer account credentials, which are separate from our AWS credentials. The following values must be supplied in order to authenticate:
- Vendor ID: Represents the Alexa Developer account.
- Client ID: Represents the developer, tool, or organization requiring permission to perform a list of operations on the skill. In this case, our AWS CDK project.
- Client Secret: The secret value associated with the Client ID.
- Refresh Token: A token for reauthentication. The ASK service uses access tokens for authentication that expire one hour after creation. Refresh tokens do not expire and can retrieve a new access token when needed.
Follow the steps below to retrieve each of these values.
Get Alexa Developer Vendor ID
Easily retrieve your Alexa Developer Vendor ID from the Alexa Developer Console.
- Navigate to the Alexa Developer console and login with your Amazon account.
- After logging in, on the main screen click on the “Settings” tab.
- Your Vendor ID is listed in the “My IDs” section. Note this value.
Create Login with Amazon (LWA) Security Profile
The Skill Management API utilizes Login with Amazon (LWA) for authentication, so first we must create a security profile for LWA under the same Amazon account that we will use to create the Alexa Skill.
- Navigate to the LWA console and login with your Amazon account.
- Click the “Create a New Security Profile” button.
- Fill out the form with a Name, Description, and Consent Privacy Notice URL, and then click “Save”.
- The new Security Profile should now be listed. Hover over the gear icon, located to the right of the new profile name, and click “Web Settings”.
- Click the “Edit” button and add the following under “Allowed Return URLs”:
http://127.0.0.1:9090/cb
https://s3.amazonaws.com/ask-cli/response_parser.html
- Click the “Save” button to save your changes.
- Click the “Show Secret” button to reveal your Client Secret. Note your Client ID and Client Secret.
Get Refresh Token from ASK CLI
Your Client ID and Client Secret let you generate a refresh token for authenticating with the ASK service.
- Navigate to your local Terminal and enter the following command, replacing
<your Client ID>
and<your Client Secret>
with your Client ID and Client Secret, respectively:
# ensure you are in the root directory of the repository
npx ask util generate-lwa-tokens --client-id "<your Client ID>" --client-confirmation "<your Client Secret>" --scopes "alexa::ask:skills:readwrite alexa::ask:models:readwrite"
- A browser window should open with a login screen. Supply credentials for the same Amazon account with which you created the LWA Security Profile previously.
- Click the “Allow” button to grant the refresh token appropriate access to your Amazon Developer account.
- Return to your Terminal. The credentials, including your new refresh token, should be printed. Note the value in the
refresh_token
field.
NOTE: If your Terminal shows an error like CliFileNotFoundError: File ~/.ask/cli_config not exists.
, you need to first initialize the ASK CLI with the command npx ask configure
. This command will open a browser with a login screen, and you should enter the credentials for the Amazon account with which you created the LWA Security Profile previously. After signing in, return to your Terminal and enter n
to decline linking your AWS account. After completing this process, try the generate-lwa-tokens
command above again.
NOTE: If your Terminal shows an error like CliError: invalid_client
, make sure that you have included the quotation marks ("
) around the --client_id
and --client-confirmation
arguments.
Add Alexa Developer Credentials to AWS SSM Parameter Store / AWS Secrets Manager
Our AWS CDK project requires access to the Alexa Developer credentials we just generated (Client ID, Client Secret, Refresh Token) in order to create and manage our Skill. To avoid hard-coding these values into our code, we can store the values in AWS Systems Manager (SSM) Parameter Store and AWS Secrets Manager, and then retrieve them programmatically when deploying our CDK project. In our case, we are using SSM Parameter Store to store the non-sensitive values in plaintext, and Secrets Manager to store the secret values in encrypted form.
The repository contains a shell script at scripts/upload-credentials.sh
that can create the appropriate parameters and secrets via AWS CLI. You’ll just need to supply the credential values from the previous steps. Alternatively, instructions for creating parameters and secrets via the AWS Console or AWS CLI can each be found in the AWS Systems Manager User Guide and AWS Secrets Manager User Guide.
You will need the following resources created in your AWS account before proceeding:
Name | Service | Type |
/alexa-cdk-blog/alexa-developer-vendor-id | SSM Parameter Store | String |
/alexa-cdk-blog/lwa-client-id | SSM Parameter Store | String |
/alexa-cdk-blog/lwa-client-secret | Secrets Manager | Plaintext / secret-string |
/alexa-cdk-blog/lwa-refresh-token | Secrets Manager | Plaintext / secret-string |
Code Walkthrough
Skill Package
When you programmatically create an Alexa Skill, you supply a Skill Package, which is a zip file consisting of a set of files defining your Skill. A skill package includes a manifest JSON file, and optionally a set of interaction model files, in-skill product files, and/or image assets for your skill. See the Skill Management API documentation for details regarding skill packages.
The repository contains a skill package that defines a simple Time Teller Skill at src/skill-package
. If you want to use an existing Skill instead, replace the contents of src/skill-package
with your skill package.
If you want to export the skill package of an existing Skill, use the ASK CLI:
- Navigate to the Alexa Developer console and log in with your Amazon account.
- Find the Skill you want to export and click the link under the name “Copy Skill ID”. Either make sure this stays on your clipboard or note the Skill ID for the next step.
- Navigate to your local Terminal and enter the following command, replacing
<your Skill ID>
with your Skill ID:
# ensure you are in the root directory of the repository
cd src
npx ask smapi export-package --stage development --skill-id <your Skill ID>
NOTE: To export the skill package for a live skill, replace --stage development
with --stage live
.
NOTE: The CDK code in this solution will dynamically populate the manifest.apis
section in skill.json
. If that section is populated in your skill package, either clear it out or know that it will be replaced when the project is deployed.
Skill Backend Lambda Function
The Lambda Function code for the Time Teller Alexa Skill’s backend also resides within the CDK project at src/lambda/skill-backend
. If you want to use an existing Skill instead, replace the contents of src/lambda/skill-backend
with your Lambda code. Also note the following if you want to use your own Lambda code:
- The CDK code in the repository assumes that the Lambda Function runtime is Python. However, you can modify for another runtime if necessary by using either the aws-lambda or aws-lambda-nodejs CDK module instead of aws-lambda-python.
- If you’re using your own Python Lambda Function code, please note the following to ensure the Lambda Function definition compatibility in the sample CDK project. If your Lambda Function varies from what is below, then you may need to modify the CDK code. See the Python Lambda code in the repository for an example.
- The
skill-backend/
directory should contain all of the necessary resources for your Lambda Function. For Python functions, this should include at least a file namedindex.py
that contains your Lambda entrypoint, and arequirements.txt
file containing your pip dependencies. - For Python functions, your Lambda handler function should be called
handler()
. This generally looks likehandler = SkillBuilder().lambda_handler()
when using the Python ASK SDK.
- The
Open-Source Alexa Skill Construct Library
As mentioned above, this solution utilizes an open-source construct library to create and manage the Alexa Skill. This construct library utilizes the L1 CfnSkill
construct along with other L1 and L2 constructs to create a complete Alexa Skill with a functioning backend Lambda Function. Utilizing this construct library means that we are no longer limited by the shortcomings of only using the Alexa::ASK::Skill
CloudFormation resource or L1 CfnSkill construct.
Look into the construct library code if you’re curious. There’s only one construct—Skill
—and you can follow the code to see how it dodges the Lambda Permission issue.
CDK Stack
The CDK stack code is located in lib/alexa-cdk-stack.ts
. Let’s dive in to understand what’s happening. We’ll look at one section at a time:
...
const PARAM_PREFIX = '/alexa-cdk-blog/'
export class AlexaCdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Get Alexa Developer credentials from SSM Parameter Store/Secrets Manager.
// NOTE: Parameters and secrets must have been created in the appropriate account before running `cdk deploy` on this stack.
// See sample script at scripts/upload-credentials.sh for how to create appropriate resources via AWS CLI.
const alexaVendorId = ssm.StringParameter.valueForStringParameter(this, `${PARAM_PREFIX}alexa-developer-vendor-id`);
const lwaClientId = ssm.StringParameter.valueForStringParameter(this, `${PARAM_PREFIX}lwa-client-id`);
const lwaClientSecret = cdk.SecretValue.secretsManager(`${PARAM_PREFIX}lwa-client-secret`);
const lwaRefreshToken = cdk.SecretValue.secretsManager(`${PARAM_PREFIX}lwa-refresh-token`);
...
}
}
First, within the stack’s constructor, after calling the constructor of the base class, we retrieve the credentials we uploaded earlier to SSM and Secrets Manager. This lets us to store our account credentials in a safe place—encrypted in the case of our lwaClientSecret
and lwaRefreshToken
secrets—and we avoid storing sensitive data in plaintext or source control.
...
export class AlexaCdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
...
// Create the Lambda Function for the Skill Backend
const skillBackend = new lambdaPython.PythonFunction(this, 'SkillBackend', {
entry: 'src/lambda/skill-backend',
timeout: cdk.Duration.seconds(7)
});
...
}
}
Next, we create the Lambda Function containing the skill’s backend logic. In this case, we are using the aws-lambda-python
module. This transparently handles every aspect of the dependency installation and packaging for us. Rather than leave the default 3-second timeout, specify a 7-second timeout to correspond with the Alexa service timeout of 8 seconds.
...
export class AlexaCdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
...
// Create the Alexa Skill
const skill = new Skill(this, 'Skill', {
endpointLambdaFunction: skillBackend,
skillPackagePath: 'src/skill-package',
alexaVendorId: alexaVendorId,
lwaClientId: lwaClientId,
lwaClientSecret: lwaClientSecret,
lwaRefreshToken: lwaRefreshToken
});
}
}
Finally, we create our Skill! All we need to do is pass the Lambda Function with the Skill’s backend code into where the skill package is located, as well as the credentials for authenticating into our Alexa Developer account. All of the wiring for deploying the skill package and connecting the Lambda Function to the Skill is handled transparently within the construct code.
Deploy CDK project
Now that all of our code is in place, we can deploy our project and test it out!
- Make sure that you have bootstrapped your AWS account for CDK. If not, you can bootstrap with the following command:
# ensure you are in the root directory of the repository
npx cdk bootstrap
- Make sure that the Docker daemon is running locally. This is generally done by starting the Docker Desktop application.
- You can also use the following Terminal command to determine whether the Docker daemon is running. The command will return an error if the daemon is not running.
docker ps -q
-
- See more details regarding starting the Docker daemon based on your operating system via the Docker website.
- Synthesize your CDK project in order to confirm that your project is building properly.
# ensure you are in the root directory of the repository
npx cdk synth
NOTE: In addition to generating the CloudFormation template for this project, this command also bundles the Lambda Function code via Docker, so it may take a few minutes to complete.
- Deploy!
# ensure you are in the root directory of the repository
npx cdk deploy
-
- Feel free to review the IAM policies that will be created, and enter
y
to continue when prompted. - If you would like to skip the security approval requirement and deploy in one step, use
cdk deploy --require-approval never
instead.
- Feel free to review the IAM policies that will be created, and enter
Check it out!
Once your project finishes deploying, take a look at your new Skill!
- Navigate to the Alexa Developer console and log in with your Amazon account.
- After logging in, on the main screen you should now see your new Skill listed. Click on the name to go to the “Build” screen.
- Investigate the console to confirm that your Skill was created as expected.
- On the left-hand navigation menu, click “Endpoint” and confirm that the ARN for your backend Lambda Function is showing in the “Default Region” field. This ARN was added dynamically by our CDK project.
- Test the Skill to confirm that it functions properly.
- Click on the “Test” tab and enable testing for the “Development” stage of your skill.
- Type your Skill’s invocation name in the Alexa Simulator in order to launch the skill and invoke a response.
- If you deployed the sample skill package and Lambda Function, the invocation name is “time teller”. If Alexa responds with the current time in UTC, it is working properly!
Bonus Points
Now that you can deploy your Alexa Skill via the AWS CDK, can you incorporate your new project into a CI/CD pipeline for automated deployments? Extra kudos if the pipeline is defined with the CDK :) Follow these links for some inspiration:
- CDK Pipelines: Continuous delivery for AWS CDK applications
- Continuous integration and delivery (CI/CD) using CDK Pipelines
- Creating a pipeline using the AWS CDK
Cleanup
After you are finished, delete the resources you created to avoid incurring future charges. This can be easily done by deleting the CloudFormation stack from the CloudFormation console, or by executing the following command in your Terminal, which has the same effect:
# ensure you are in the root directory of the repository
npx cdk destroy
Conclusion
You can, and should, strive for IaC and CI/CD in every project, and the powerful AWS CDK features make that easier with a set of simple yet flexible constructs. Leverage the simplicity of declarative infrastructure definitions with convenient default configurations and helper methods via the AWS CDK. This example also reveals that if there are any gaps in the built-in functionality, you can easily fill them with a custom resource construct, or one of the thousands of open-source construct libraries shared by fellow CDK developers around the world. Happy coding!
Jeff Gardner
Jeff Gardner is a Solutions Architect with Amazon Web Services (AWS). In his role, Jeff helps enterprise customers through their cloud journey, leveraging his experience with application architecture and DevOps practices. Outside of work, Jeff enjoys watching and playing sports and chasing around his three young children.