Skip to content

Latest commit

 

History

History
389 lines (316 loc) · 24.3 KB

README.md

File metadata and controls

389 lines (316 loc) · 24.3 KB

Amazon Connect Automation using AWS Developer Tools, AWS Cloud Development Kit (CDK), and TypeScript

This workshop will teach you how to implement a Continuous Integration and Continuous Delivery (CI/CD) process for a contact center built using AWS services. This solution has Multi-Region support, which can be tailored for Amazon Connect Global Resiliency

Concepts

This pattern uses AWS CodePipeline for CI/CD and AWS CodeBuild as a build service. The code is written in typescript and uses CDK to model the application components.

CI/CD Pipeline Architecture

The pipelines that deploy Amazon Connect and other associated items are installed into a single DevOps tooling account. All pipelines will run here and can deploy to any other supported region. The tooling account and the application account regions are configured independently and don't necessarily need to be the same.

There are four pipelines deployed:

  • instance pipeline which creates the Amazon Connect instance itself.
  • supporting-infra pipeline which creates supporting architectures such as lex bots, s3 buckets, and any other resource which is not primarily code.
  • lambdas pipeline which creates all associated lambda functions and layers. This is separate so that appropriate testing and security patterns can be run against the code itself.
  • contact-flows pipeline which deploys the Amazon Connect contact flows to the instance.

The resources are deployed into the specified account and region first in the primary region and then to other secondary region(s).

[architecture.png]

Monorepo Architecture

The four application stacks are stored in a single monorepo. An API gateway backed by routing lambda receives webhooks from Github and starts the specified pipeline based upon the files added or modified. The github webhooks are secured using secrets to verify that all incoming requests are from the legitimate Github repo.

[Monorepo.png]

Amazon Connect Flows

In Amazon Connect deployments it is common to deploy Amazon Connect Flows that are created using the Amazon Connect console which refer to the various flow block definitions (e.g., Play prompt, Set working queue, Get customer input, Invoke AWS Lambda function). These resources included in the flow, such as queues and voice prompts, are referenced within the flow using the name of the resource and the Amazon Resource Name (ARN). The ARN is a unique identifier for a resource that is specific to the service and Region in which the resource is created. The functionality works great in a single account, however it becomes an issue when you need to move those flows between instances in different accounts and regions. The resource ARNs from the source instance need to be mapped to ones on the destination instance. If the names are the same, these resources can usually be resolved, however if they are different, one needs to manually resolve. This can be time consuming and prone to human error when you have multiple instances and a large numbers of flows.

This design uses Amazon Connect contact attributes instead of ARNs. In the flow that the phone number is attached to (ACME_Main in our case) there is a mapping Lambda that runs that has an entry for all possible keys that one could encounter within a particular Amazon Connect instance and maps those keys to the corresponding ARN.

This is an Amazon Lex bot example.
[lexbot-attribute.png]

This is a Set working queue example.
[queue-attribute.png]

These flows are then exported from an Amazon Connect flow development instance and added to the repository under amzconnect-contact-flows/lib/callflows. This is an example flow.

{
    "Name": "ACME_agent_whisper",
    "ContactFlowType": "AGENT_WHISPER",
    "Content": "{\"Version\":\"2019-10-30\",\"StartAction\":\"73611771-2716-4560-afb3-bae51560752c\",\"Metadata\":{\"entryPointPosition\":{\"x\":68,\"y\":115},\"snapToGrid\":false,\"ActionMetadata\":{\"ef747366-46cb-4d97-a096-dd34af2a807f\":{\"position\":{\"x\":578,\"y\":145}},\"73611771-2716-4560-afb3-bae51560752c\":{\"position\":{\"x\":256,\"y\":113},\"useDynamic\":false}}},\"Actions\":[{\"Identifier\":\"ef747366-46cb-4d97-a096-dd34af2a807f\",\"Parameters\":{},\"Transitions\":{},\"Type\":\"EndFlowExecution\"},{\"Identifier\":\"73611771-2716-4560-afb3-bae51560752c\",\"Parameters\":{\"Text\":\"$.Queue.Name\"},\"Transitions\":{\"NextAction\":\"ef747366-46cb-4d97-a096-dd34af2a807f\",\"Errors\":[],\"Conditions\":[]},\"Type\":\"MessageParticipant\"}]}"
}

The only flow that acts differently is the ACME_Main flow because we need to refer to the mapping Lambda by its ARN. The developer will remove the ARN from the flow and replace it with "ARNREPLACE". During the deployment process the ARN for the specific instance is inserted back into the flow via a Lambda that works to provision the flows into Amazon Connect.

Limitations

  1. If your solution uses multiple Lambda functions, then you will need to insert a Set contact attributes definition. This will move the mapping variables from the external memory space to the user memory space. This is required because the next Lambda function that runs will overwrite the external memory space. For the purposes of this Workshop, it wasn't necessary. The Set contact attributes definition will look like this:
    • [set-contact-attributes.png]
  2. ACME_customer_queue is a Customer queue flow that contains a Loop prompts definition. This definition does not support dynamic attributes so it's deployed with only a text prompt. In reality, you will add an audio prompt after the flow has been deployed.
  3. This solution only supports one AWS Region. The next version will include Multi-Region support. This is required for Amazon Connect Global Resiliency.

Prerequisites

AWS Cloud9

You can use AWS Cloud9 to run this workshop. AWS CDK, AWS CLI, Node.js and NPM will be installed if you execute these steps:

  1. Create an AWS Cloud9 instance
    1. Download cf.yaml. This is an AWS CloudFormation template that will configure AWS Cloud9 in an Amazon Virtual Private Cloud (VPC)  with a public subnet.
    2. Sign in to the AWS Tooling account
    3. Select the AWS CloudFormation service
    4. Create stack, with new resources
    5. Upload a new template file
    6. Choose the file that you downloaded
    7. Select the Next button
    8. Enter a stack name (e.g., CICDConnectWorkshop01Cloud9)
    9. Optionally, change the VpcCIDR
    10. Select the Next button
    11. Select the Next button
    12. Select the Submit button
    13. Wait for the stack to finish
  2. Launch AWS Cloud9
    1. Select the AWS Cloud9 service
    2. Select the CICDConnectWorkshop01Cloud9 environment
    3. Select the Open in Cloud9 button
  3. Setup AWS Cloud9
    1. Upload setup.sh to /home/ec2-user/environment
    2. Open a terminal in AWS Cloud9 and run these commands:
      1. cd ~/environment/
      2. chmod +x setup.sh
      3. ./setup.sh

AWS and GitHub Configuration Steps

Develop, Stage, and Production accounts

CDK Bootstrap

  1. Run cdk bootstrap in each account. You can run this command using AWS CloudShell
    1. Select the AWS CloudShell service
    2. Run cdk bootstrap aws://<account number>/<region>

Create the AWS IAM Roles that will be used by the AWS CodePipelines

  1. Download pipelineDeploymentRole.yml. This can be run as an AWS CloudFormation StackSets if you are using AWS Control Tower or as a CloudFormation stack in each account. This only needs to be run once per account because it supports multiple regions.
    1. Enter a stack name (e.g., CICDConnectWorkshop01PipelineDeploymentRole)
    2. Enter the Tooling Account Number for the pToolingAccountId parameters
    3. Check the "I acknowledge that AWS CloudFormation might create IAM resources with custom names."

Fork this repository to your GitHub account

Follow these steps

Clone the forked repository in your IDE (e.g., AWS Cloud9)

  1. Follow these steps to create a personal access token
  2. Follow these steps to Clone

[cloned-repo.png]

Create a GitHub connection in the Tooling account

This Github connection is used to authenticate you to the repository. Please see https://docs.aws.amazon.com/codepipeline/latest/userguide/connections-github.html for additional information.

  1. Select the AWS CodePipeline service
  2. Select Settings and then Connections
  3. Select the Create connection button
  4. Select GitHub in the Select a Provider section
  5. Add a Connection name (e.g., CICDConnectWorkshop01GitHub)
  6. Select the Connect to GitHub button
  7. Select Install a new app button. This will take you to the GitHub Confirm access page where you need to provide your credentials. You will then provide access to the forked repository.
  8. Select the Connect button
  9. Note the Arn because it will be used in a later step

[github-connection.png]

Update amzconnect-devops-pipelines/env/accountvars.json

  1. cd ~/environment/cicd-connect-workshop/amzconnect-devops-pipelines/env/
  2. Update the branch field with the branch name that will be associated with each AWS Account.
  3. Update the account field with the AWS Account ID for each account.
  4. Update the region field to your AWS Regions. The AWS Region needs to support Amazon Connect.
{
    "accounts": [
    {
        "env": "dev",
        "branch": "develop",
        "enabled": true,
        "account": "XXXXXXXXXXXX",
        "region": ["us-east-1", "us-west-2"],
        "app": "ACME"
    },
    {
        "env": "staging",
        "branch": "staging",
        "enabled": true,
        "account": "XXXXXXXXXXXX",
        "region": ["us-east-1"],
        "app": "ACME"
    },
    {
        "env": "prod",
        "branch": "main",
        "enabled": true,
        "account": "XXXXXXXXXXXX",
        "region": ["us-east-1"],
        "app": "ACME"
    }]
}

Update amzconnect-devops-pipelines/env/devops.json

  1. Update the account field with the AWS Account ID for the tooling account.
  2. Update the region field.
  3. Update the codestarArn field with the ARN from the GitHub Connection.
  4. Update the owner field with your Github account alias.
  5. Update the email field with your email address.
{
    "env": "devops",
    "account": "XXXXXXXXXXXX",
    "region": "us-east-1",
    "codestarArn": "arn:aws:codestar-connections:us-east-1:XXXXXXXXXXXX:connection/XXXXXX",
    "repo": "cicd-connect-workshop",
    "owner": "Github account alias",
    "email": "[email protected]"
}

Push the modified files to your repository's main branch

  1. cd ~/environment/cicd-connect-workshop/
  2. git add --all
  3. git commit -m "Updated configuration files"
  4. git push

[PipelineConfigPush.png]

Deploy the CodePipelines

  1. cd ~/environment/cicd-connect-workshop/
  2. cdk bootstrap aws://<Tooling account number>/<region> --profile default
  3. cd ~/environment/cicd-connect-workshop/amzconnect-devops-pipelines/
  4. npm install
  5. npm run build
  6. cdk deploy --all --profile default
    • Do you wish to deploy these changes (y/n)? y

These steps created the CodePipelines (four for each environment) and supporting services. It also created an Amazon API Gateway Lambda function that will be used by the GitHub Webhook. The URL is in the output section. See CICDStack.githubRouterApiPayloadUrl. This will be created in the next step.

[DeployedPipelines.png] [CICDStackOutput.png]

Create a GitHub Webhook

  1. Navigate to the Settings menu for your forked repository in GitHub.
  2. Select Webhooks.
  3. Select Add webhook button.
  4. Enter the API Gateway URL that was created in the previous step in the Payload URL field.
  5. Select application/json for the Content type field.
  6. Enter a secret in the Secret field. Remember this because you will need it in the next step.
  7. Select the Add webhook button.

[GitHubWebhook.png]

Add the GitHub Webhook secret to AWS Secrets Manager

  1. Select the AWS Secrets Manager service.
  2. Select githubWebhookSecret secret.
  3. Select Retrieve secret value button in the Secret value section.
  4. Select Plaintext and then the Edit button.
  5. Enter your secret from the previous section.
  6. Select the Save button.

[SecretsManager.png]

Run the individual pipelines in each environment

These steps will use the Develop environment. It's the same process for Stage and Production.

Create a develop branch

The branch name is the value you provided in the cicd-connect-workshop/amzconnect-devops-pipelines/env/accountvars.json branch field.

  • cd ~/environment/cicd-connect-workshop/
  • git checkout -b develop
    • The -b flag creates the branch. If the branch is already created then you can remove this flag.

Create an Amazon Connect instance using the amzconnect-instance-develop CodePipeline

  1. Modify /cicd-connect-workshop/amzconnect-instance/lib/instance-stack/configuration.json
    • Provide a instanceAlias and instanceStorageBucketName. These needs to be globally unique and follow the naming rules.
    • An AWS Step Function will run. The Step Functions will append dev/staging/prod to whatever you use here to make sure that they are different from each other. The config file will also configure the instance storage requirements as well as two queues which we need for our test flows to work.
{
  "instanceAlias": "mytestalias-aaazzz",
  "instanceStorageBucketName": "connect-aaazzz",
}
  1. Modify /cicd-connect-workshop/amzconnect-instance/env/devops.json
    • Provide the AWS Region and AWS Account ID for the Tooling account.
{
    "account": "XXXXXXXXXXXX",
    "region": "us-east-1"
}
  1. Push the modified files to your repository's develop branch
  • cd ~/environment/cicd-connect-workshop/amzconnect-instance/
  • git add --all
  • git commit -m "Updated configuration files"
  • git push --set-upstream origin develop

[amzconnect-instance-repo-update.png]

  1. The amzconnect-instance-develop pipeline will now start to build the Amazon Connect instance. Follow these step to verify that the pipeline was successful:
    • Select the AWS CodePipeline service.
    • Select Pipeline and then Pipelines in the menu.
    • This pipeline will show Succeeded in the Most recent execution column.

[CodePipeline-instance]

Create all the supporting infrastructure (e.g., Amazon Lex) except for the Lambda functions

  1. Modify /cicd-connect-workshop/amzconnect-supporting-infra/env/devops.json
    • Provide the AWS Region and AWS Account ID for the Tooling account.
{
    "account": "XXXXXXXXXXXX",
    "region": "us-east-1"
}
  1. Push the modified files to your repository's develop branch
  • cd ~/environment/cicd-connect-workshop/amzconnect-supporting-infra/
  • git add --all
  • git commit -m "Updated configuration files"
  • git push --set-upstream origin develop
  1. The amzconnect-supporting-infra-develop pipeline will now start to build the supporting infrastructure. Follow these step to verify that the pipeline was successful:
    • This pipeline will show Succeeded in the Most recent execution column.

[CodePipeline-infra]

Create the Lambda Functions

This code will create all of the necessary Lambda functions. This pipeline also has an area where you could run various unit, integration, or code coverage tests.

  1. Modify /cicd-connect-workshop/amzconnect-lambdas/devops.json
    • Provide the AWS Region and AWS Account ID for the Tooling account.
{
    "account": "XXXXXXXXXXXX",
    "region": "us-east-1"
}
  1. Push the modified files to your repository's develop branch
  • cd ~/environment/cicd-connect-workshop/amzconnect-lambdas/
  • git add --all
  • git commit -m "Updated configuration files"
  • git push --set-upstream origin develop
  1. The amzconnect-lambdas-develop pipeline will now start to build the Lambda functions. Follow these step to verify that the pipeline was successful:
    • This pipeline will show Succeeded in the Most recent execution column.

[CodePipeline-lambda]

Deploy the Contact Flows

This code will deploy the contact flows. This pipeline operates a little differently in that we are not deploying any code using CDK. We are using AWS CodeBuild to copy the contact flows into our contact flow bucket, and then starting the callflowProvisioner Lambda function which will take care of the rest of the configuration.

  1. Modify /cicd-connect-workshop/amzconnect-contact-flows/devops.json
    • Provide the AWS Region and AWS Account ID for the Tooling account.
{
    "account": "XXXXXXXXXXXX",
    "region": "us-east-1"
}
  1. Push the modified files to your repository's develop branch
  • cd ~/environment/cicd-connect-workshop/amzconnect-contact-flows/
  • git add --all
  • git commit -m "Updated configuration files"
  • git push --set-upstream origin develop
  1. The amzconnect-contact-flows-develop pipeline will now start to deploy the contact flows. Follow these step to verify that the pipeline was successful:
    • This pipeline will show Succeeded in the Most recent execution column.

[CodePipeline-contactflows]

Additional Details

  • The Amazon Connect stack has been deployed whose main components consist of an S3 bucket for Contact Flow storage, three Lambda functions, a Lex bot, and Contact Flows that have been deployed to the instance. One of the Lambda functions is the mapping function which maps the unique attribute name to the ARN, and the second function is the Contact Flow Provisioner. During the build phase, the provisioner makes calls to various services to validate the Contact Flows on the instance, retrieve the ARNs for the Lex bot(s), prompts and queues and ultimately packages all of that up and updates the mapping Lambda function.

  • Lambda functions and Lex bots specifically need to be granted permission to be called by Connect so there is a custom resource that attaches them to Amazon Connect as well.

How to Deploy Contact Flows

  • The contact flows are in /cicd-connect-workshop/amzconnect-contact-flows/lib/callflows. The format of each flow looks similar to below:
{
    "Name": "ACME_agent_whisper",
    "ContactFlowType": "AGENT_WHISPER",
    "Content": "{\"Version\":\"2019-10-30\",\"StartAction\":\"73611771-2716-4560-afb3-bae51560752c\",\"Metadata\":{\"entryPointPosition\":{\"x\":68,\"y\":115},\"snapToGrid\":false,\"ActionMetadata\":{\"ef747366-46cb-4d97-a096-dd34af2a807f\":{\"position\":{\"x\":578,\"y\":145}},\"73611771-2716-4560-afb3-bae51560752c\":{\"position\":{\"x\":256,\"y\":113},\"useDynamic\":false}}},\"Actions\":[{\"Identifier\":\"ef747366-46cb-4d97-a096-dd34af2a807f\",\"Parameters\":{},\"Transitions\":{},\"Type\":\"EndFlowExecution\"},{\"Identifier\":\"73611771-2716-4560-afb3-bae51560752c\",\"Parameters\":{\"Text\":\"$.Queue.Name\"},\"Transitions\":{\"NextAction\":\"ef747366-46cb-4d97-a096-dd34af2a807f\",\"Errors\":[],\"Conditions\":[]},\"Type\":\"MessageParticipant\"}]}"
}
  • An easy way to get the content value of a flow after developing one is to use the AWS CLI. All flows on an instance can be listed using the list-contact-flows command which will give you the flows and their unique contact-flow-id
aws connect list-contact-flows --instance-id <value>

To describe a particular flow:

aws connect describe-contact-flow --instance-id <value> --contact-flow-id <value>

Example output:

{
    "ContactFlow": {
        "Arn": "arn:aws:connect:us-east-1:XXXXXXXXXXXX:instance/34ed4674-e1c9-43ee-880d-XXXXXXXXXXXX/contact-flow/a318e3af-f88e-45e0-88ac-35808a898b52",
        "Id": "a318e3af-f88e-45e0-88ac-XXXXXXXXXXXX",
        "Name": "Default agent whisper",
        "Type": "AGENT_WHISPER",
        "State": "ACTIVE",
        "Description": "Default whisper played to the agent.",
        "Content": "{\"Version\":\"2019-10-30\",\"StartAction\":\"222caecc-c107-4553-87fc-85a74c34bb06\",\"Metadata\":{\"entryPointPosition\":{\"x\":75,\"y\":20},\"snapToGrid\":false,\"ActionMetadata\":{\"95dc2179-0f18-4646-8e15-15377c9cbb29\":{\"position\":{\"x\":491.0034484863281,\"y\":141.5555419921875}},\"222caecc-c107-4553-87fc-85a74c34bb06\":{\"position\":{\"x\":231.00344848632812,\"y\":96.5555419921875},\"useDynamic\":false}}},\"Actions\":[{\"Identifier\":\"95dc2179-0f18-4646-8e15-15377c9cbb29\",\"Parameters\":{},\"Transitions\":{},\"Type\":\"EndFlowExecution\"},{\"Identifier\":\"222caecc-c107-4553-87fc-85a74c34bb06\",\"Parameters\":{\"Text\":\"$.Queue.Name\"},\"Transitions\":{\"NextAction\":\"95dc2179-0f18-4646-8e15-15377c9cbb29\",\"Errors\":[],\"Conditions\":[]},\"Type\":\"MessageParticipant\"}]}",
        "Tags": {}
    }
}

Test the solution

  1. To test that the solution works, we need to get into Amazon Connect, claim a phone number and attach it to the ACME_Main flow. The example shows a US DID, however choose whatever works for you.

[claim-number.png]

  1. Call the phone number that you claimed. The contact flow will ask you what department do you need to speak to?