diff --git a/workshop/content/buildpipe/done/_index.en.md b/workshop/content/buildpipe/done/_index.en.md index 1a7d1dc..4f17ffa 100644 --- a/workshop/content/buildpipe/done/_index.en.md +++ b/workshop/content/buildpipe/done/_index.en.md @@ -34,7 +34,19 @@ CodeBuild created the `sam-app-dev` stack during the `DeployTest` Pipeline step. CodeBuild created `sam-app-prod` during the `DeployProd` step. Look at the `Outputs` tab for each of these CloudFormation stacks to see the API endpoints. You can -use `curl` or other methods to verify the functionality of your two new APIs. +use `curl` or other methods to verify the functionality of your two new APIs. You can export the URL +endpoints for both stages in a terminal. + +```bash +export DEV_ENDPOINT=$(aws cloudformation describe-stacks --stack-name sam-app-dev | jq -r '.Stacks[].Outputs[].OutputValue | select(startswith("https://"))') +export PROD_ENDPOINT=$(aws cloudformation describe-stacks --stack-name sam-app-prod | jq -r '.Stacks[].Outputs[].OutputValue | select(startswith("https://"))') + +echo "Dev endpoint: $DEV_ENDPOINT" +echo "Prod endpoint: $PROD_ENDPOINT" + +curl -s $DEV_ENDPOINT +curl -s $PROD_ENDPOINT +``` ![API endpoints](/images/chapter4-pipelines/sam-app-dev-cfn-outputs.png) diff --git a/workshop/content/buildpipe/gitpush/_index.en.md b/workshop/content/buildpipe/gitpush/_index.en.md index 6e2deb4..ae1e144 100644 --- a/workshop/content/buildpipe/gitpush/_index.en.md +++ b/workshop/content/buildpipe/gitpush/_index.en.md @@ -50,6 +50,10 @@ Branch 'main' set up to track remote branch 'main' from 'origin'. ### Verify in CodeCommit -Navigate to the [AWS CodeCommit console](https://console.aws.amazon.com/codesuite/codecommit/home), find your _sam-app_ repository and click on it to view its contents. Make sure your code is there. You should see a screen like the following: +Navigate to the [AWS CodeCommit console](https://console.aws.amazon.com/codesuite/codecommit/home), +find your `sam-app` repository and click on it to view its contents. Make sure your code is there. +You should see a screen like the following: ![VerifyCodeCommit](/images/screenshot-verify-codecommit.png) + +#### Now that you repo has your code let's start building a CI/CD pipeline! diff --git a/workshop/content/buildpipe/howto/_index.en.md b/workshop/content/buildpipe/howto/_index.en.md index 88f6be4..19a4562 100644 --- a/workshop/content/buildpipe/howto/_index.en.md +++ b/workshop/content/buildpipe/howto/_index.en.md @@ -6,7 +6,7 @@ weight = 20 The best way to automate the creation of CI/CD pipelines is by provisioning them programmatically using Infrastructure as Code (IaC). This is useful in a microservices environment, where you may have -a pipeline per microservice. In such environments there could be dozens, or even hundreds, +a pipeline per service. In such environments there could be dozens, or even hundreds, of CI/CD pipelines. Having an automated way to create those CI/CD pipelines enables developers move quickly without the burden of building them manually. SAM Pipelines, which you'll be using, is a tool to ease that burden. @@ -30,8 +30,6 @@ test our serverless application locally. AWS SAM is a toolset meant to increase developing serverless applications and provides capabilities such as `sam local` that are not present in other IaC tools. -In this section you will be using another feature from SAM to create a CI/CD pipeline. - ### Introducing AWS SAM Pipelines [**SAM Pipelines**](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-pipeline-bootstrap.html) @@ -49,14 +47,14 @@ As of this writing, SAM Pipelines can bootstrap CI/CD pipelines for the followin {{% notice note %}} SAM Pipelines is a feature which bootstraps CI/CD pipelines for the listed providers. This saves saves you the work of setting them up from scratch. However, you can use SAM as a deployment tool -with _any_ CI/CD provider. You use the `sam build` and `sam deploy` commands to build and deploy SAM +with _any_ CI/CD provider. You use various `sam` commands to build and deploy SAM applications regardless of your CI/CD toolset. Furthermore, the configurations SAM Pipelines creates -are a convienence to get you started quickly. You are free to edit these CI/CD configuration +are a convienence to get you started. You are free to edit these CI/CD configuration files after SAM creates them. {{% /notice %}} SAM Pipelines creates appropriate configuration files for your CI/CD provider of choice. For -exampoe, when using +example, when using GitHub Actions SAM will synthesize a `.github/workflows/pipeline.yaml` file. This file defines your CI/CD pipeline using GitHub Actions. In this workshop we will be using AWS CodePipeline. As you will soon see, SAM creates multiple files, one of which is a CloudFormation template named @@ -68,11 +66,11 @@ serverless application automatically. At the end of this section we will have a self-updating CI/CD pipeline using CodePipeline that will perform the following steps. -1. Trigger after a commit to the `main` branch -1. Look for changes to the pipeline itself, and self-update using CloudFormation -1. Run unit tests via CodeBuild -1. Build and package the application code via CodeBuild -1. Deploy to a dev/test environment -1. Deploy to a production environment +1. Trigger after a commit to the `main` branch (`Source` in the screenshot below) +1. Look for changes to the pipeline itself, and self-update using CloudFormation (`UpdtatePipeline`) +1. Run unit tests via CodeBuild (`UnitTest`) +1. Build and package the application code via CodeBuild (`BuildAndPackage`) +1. Deploy to a dev/test environment (`DeployTest`) +1. Deploy to a production environment (`DeployProd`) ![](/images/sam-pipeline-architecture.png) diff --git a/workshop/content/buildpipe/sampipeinit/_index.en.md b/workshop/content/buildpipe/sampipeinit/_index.en.md index 8be18d3..410476c 100644 --- a/workshop/content/buildpipe/sampipeinit/_index.en.md +++ b/workshop/content/buildpipe/sampipeinit/_index.en.md @@ -29,8 +29,8 @@ cd ~/environment/sam-app sam pipeline init --bootstrap ``` -A list of the questions and required anwers for this workshop is enumerated below. Note that numbers -may be different when choosing from an enumerated list. The full output and answers are provided +A list of the questions and required anwers for this workshop is enumerated below. **Note that numbers +may be different when choosing from an enumerated list.** The full output and answers are provided below as an additional reference. 1. Select a pipeline template to get started: `AWS Quick Start Pipeline Templates` (1) diff --git a/workshop/content/canaries/add-error/_index.en.md b/workshop/content/canaries/add-error/_index.en.md index 4220f48..0b5b604 100644 --- a/workshop/content/canaries/add-error/_index.en.md +++ b/workshop/content/canaries/add-error/_index.en.md @@ -71,7 +71,7 @@ exports.lambdaHandler = async (event, context) => { `~/environment/sam-app/hello_world/app.py` -```python +```python {hl_lines=["3-4"]} def lambda_handler(event, context): if True: @@ -100,6 +100,7 @@ def test_lambda_handler(apigw_event, mocker): In the terminal, run the following commands from the root directory of your `sam-app` project. ``` +cd ~/environment/sam-app git add . git commit -m "Breaking the lambda function on purpose" git push @@ -111,14 +112,15 @@ Once you've pushed the code you will need to generate traffic on your production endpoint. **If you don't generate traffic for your Lambda function the CloudWatch alarm will not be triggered!** -Fetch the API Gateway endpoint for your production deployment. This command will export the -production URL to the environment variable `PROD_ENDPOINT`. +{{%expand "If you haven't exported the PROD_ENDPOINT, run the following command." %}} ```bash export PROD_ENDPOINT=$(aws cloudformation describe-stacks --stack-name sam-app-prod | jq -r '.Stacks[].Outputs[].OutputValue | select(startswith("https://"))') echo "$PROD_ENDPOINT" ``` +{{% /expand%}} + Start a `watch` command which will hit this endpoint twice per second. ```bash diff --git a/workshop/content/canaries/codedeploy/_index.en.md b/workshop/content/canaries/codedeploy/_index.en.md index f09922e..6af96b2 100644 --- a/workshop/content/canaries/codedeploy/_index.en.md +++ b/workshop/content/canaries/codedeploy/_index.en.md @@ -20,7 +20,7 @@ Remember to update the unit tests! `~/environment/sam-app/hello-world/app.js` -```javascript +```javascript {hl_lines=["4"]} response = { statusCode: 200, body: JSON.stringify({ @@ -31,7 +31,7 @@ response = { `~/environment/node-sam-app/hello-world/tests/unit/test-handler.js ` -```javascript +```javascript {hl_lines=["12"]} describe("Tests index", function () { it("verifies successful response", async () => { const result = await app.lambdaHandler(event, context) @@ -54,7 +54,7 @@ describe("Tests index", function () { `~/environment/sam-app/hello_world/app.py ` -```python +```python {hl_lines=["4"]} return { "statusCode": 200, "body": json.dumps({ @@ -65,7 +65,7 @@ return { `~/environment/sam-app/tests/unit/test_handler.py` -```python +```python {hl_lines=["7"]} def test_lambda_handler(apigw_event, mocker): ret = app.lambda_handler(apigw_event, "") data = json.loads(ret["body"]) @@ -96,16 +96,21 @@ completes. First, get the `https` endpoint of your dev stage. In a terminal on your Cloud9 enviroment run the following command. +{{%expand "Export the HTTP endpoints if you haven't already" %}} + ```bash -aws cloudformation describe-stacks --stack-name sam-app-dev | jq -r '.Stacks[].Outputs[].OutputValue | select(startswith("https://"))' +export DEV_ENDPOINT=$(aws cloudformation describe-stacks --stack-name sam-app-dev | jq -r '.Stacks[].Outputs[].OutputValue | select(startswith("https://"))') +export PROD_ENDPOINT=$(aws cloudformation describe-stacks --stack-name sam-app-prod | jq -r '.Stacks[].Outputs[].OutputValue | select(startswith("https://"))') ``` -Copy the url returned by the previous command run the following `watch` command. This will hit your -API endpoint every second and print the return value to the screen. This command will also append -the output to the `outputs.txt` file. You can run this command from any directory. +{{% /expand%}} + +Hit your `dev` API endpoint every second and print the return value to the screen. This command will +also append the output to the `outputs.txt` file that you can inspect later. You can run this +command from any directory. ```bash -watch -n 1 "curl -s https://123123123.execute-api.us-east-2.amazonaws.com/Prod/hello/ | jq '.message' 2>&1 | tee -a outputs.txt" +watch -n 1 "curl -s $DEV_ENDPOINT | jq '.message' 2>&1 | tee -a outputs.txt" ``` You should see `Hello my friend` in the terminal. Now that your script is logging the API output turn @@ -117,7 +122,8 @@ the `In Progress` status navigate to the CodeDeploy console. ![CanaryCodeDeploy](/images/screenshot-canary-codedeploy-00.png) In the CodeDeploy console click on `Deployments`. You should see your deployment `In progress`. If -you do not see a deployment, click the refresh icon. Click on the Deployment Id to see the details. +you do not see a deployment, click the refresh icon. **This may take a few minutes to show up!** +Click on the Deployment Id to see the details. ![CanaryCodeDeploy](/images/screenshot-canary-codedeploy-0.png) @@ -130,6 +136,8 @@ ellapsed. In this case we specified the interval to be 5 minutes. When you are in this stage, take a look at your terminal where you started the `watch` command. You will see the message occasionally flash to `I'm using canary deployments`. +![CanaryDeploymentMessages](/images/code-pipeline-canary.gif) + After five minutes CodeDeploy will shift the remaining traffic to the new version and the deployment will be done: diff --git a/workshop/content/canaries/sam/_index.en.md b/workshop/content/canaries/sam/_index.en.md index c8c5c3a..b7e0920 100644 --- a/workshop/content/canaries/sam/_index.en.md +++ b/workshop/content/canaries/sam/_index.en.md @@ -12,6 +12,8 @@ with your `template.yaml` file. {{< tabs >}} {{% tab name="Node" %}} +`~environment/sam-app/template.yaml` + ```yaml {linenos=true,hl_lines=["2-4"],linenostart=20} Runtime: nodejs14.x AutoPublishAlias: live @@ -24,6 +26,8 @@ Architectures: {{% /tab %}} {{% tab name="python" %}} +`~environment/sam-app/template.yaml` + ```yaml {linenos=true,hl_lines=["2-4"],linenostart=20} Runtime: python3.7 AutoPublishAlias: live diff --git a/workshop/content/cleanup/delete-cf-stacks.en.md b/workshop/content/cleanup/delete-cf-stacks.en.md index 4fe0467..1a00c85 100755 --- a/workshop/content/cleanup/delete-cf-stacks.en.md +++ b/workshop/content/cleanup/delete-cf-stacks.en.md @@ -19,7 +19,17 @@ need to make adjustments. The cleanup script in its entirety is shown below. ```bash #!/bin/bash -ROLE_NAME=Cloud9-MyAwesomeAdmin +ROLE_NAME=Cloud9-AwesomeAdmin + +# Function to get the status of a CFN template +get_stack_status () { + status=$(aws cloudformation describe-stacks --stack-name $1) + if [ $? -eq 0 ]; then + echo "$status" | jq -r ".Stacks[].StackStatus" + else + echo "DELETE_COMPLETE" + fi +} cat > cfn-policy.json<< EOF { @@ -42,18 +52,32 @@ ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) ## Create a temporary Admin role aws iam create-role --role-name "$ROLE_NAME" --assume-role-policy-document file://cfn-policy.json aws iam attach-role-policy --role-name "$ROLE_NAME" --policy-arn "arn:aws:iam::aws:policy/AdministratorAccess" +rm cfn-policy.json ## Wait a bit for this role to stick. It cannot be used immediately after creation. -sleep 15 +sleep 10 ## Delete stacks stacks=(sam-app-prod sam-app-dev sam-app-pipeline aws-sam-cli-managed-prod-pipeline-resources aws-sam-cli-managed-dev-pipeline-resources) -for name in "${stacks[@]}" +for stack_name in "${stacks[@]}" do - echo "Deleting stack: $name..." - aws cloudformation delete-stack --role-arn "arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME" --stack-name "$name" - sleep 10 + echo "Deleting stack: $stack_name..." + + aws cloudformation delete-stack \ + --role-arn "arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME" \ + --stack-name "$stack_name" + + status="$(get_stack_status $stack_name)" + + # Wait until the stack is actually deleted + while [ "$status" != "DELETE_COMPLETE" ] + do + sleep 5 + status="$(get_stack_status $stack_name)" + echo "$stack_name: $status" + done + done ## Delete the temporary Admin role diff --git a/workshop/content/local/change/_index.en.md b/workshop/content/local/change/_index.en.md index 8556685..f322bcf 100644 --- a/workshop/content/local/change/_index.en.md +++ b/workshop/content/local/change/_index.en.md @@ -13,7 +13,7 @@ simple change. Change the response message to return `hello my friend` instead o `~environment/sam-app/hello-world/app.js` -```js +```js {hl_lines=["8"]} let response exports.lambdaHandler = async (event, context) => { @@ -37,7 +37,7 @@ exports.lambdaHandler = async (event, context) => { {{% tab name="python" %}} `~environment/sam-app/hello_world/app.py` -```python +```python {hl_lines=["7"]} import json def lambda_handler(event, context): diff --git a/workshop/content/sam/code/_index.en.md b/workshop/content/sam/code/_index.en.md index 5d9fce9..9706f2a 100644 --- a/workshop/content/sam/code/_index.en.md +++ b/workshop/content/sam/code/_index.en.md @@ -4,14 +4,12 @@ date = 2019-10-02T15:21:26-07:00 weight = 15 +++ -{{% notice note %}} -If you consider yourself an expert using Lambda functions, you can probably skip this page. -{{% /notice%}} - Let's take a look at the code of the Hello World Lambda function. -**Note** that your function may have additional commented out code, those lines have been removed -from the following example for clarity. +{{% notice note %}} +Your function may have additional comments. Those lines have been removed from the following example +for clarity. +{{% /notice%}} {{< tabs >}} {{% tab name="Node" %}} diff --git a/workshop/content/sam/init/_index.en.md b/workshop/content/sam/init/_index.en.md index aeab643..c8b8264 100644 --- a/workshop/content/sam/init/_index.en.md +++ b/workshop/content/sam/init/_index.en.md @@ -59,10 +59,14 @@ Which runtime would you like to use? 14 - ruby2.7 ``` -- Node: `nodejs14.x` -- Python: `python3.7` -- Java: `python3.7` -- C#: `dotnet6` +{{< tabs >}} +{{% tab name="Node" %}} +`nodejs14.x` +{{% /tab %}} +{{% tab name="python" %}} +`python3.7` +{{% /tab %}} +{{% /tabs %}} Select `Zip` as the package type and leave `sam-app` as the `Project name`. diff --git a/workshop/static/assets/cleanup-cicd.sh b/workshop/static/assets/cleanup-cicd.sh index 9556166..75a05c2 100644 --- a/workshop/static/assets/cleanup-cicd.sh +++ b/workshop/static/assets/cleanup-cicd.sh @@ -1,6 +1,18 @@ #!/bin/bash -ROLE_NAME=Cloud9-MyAwesomeAdmin +## Note this role *must* start with "Cloud9" in order for it to be permitted in a Cloud9 +## environment +ROLE_NAME=Cloud9-AwesomeAdmin + +# Function to get the status of a CFN template +get_stack_status () { + status=$(aws cloudformation describe-stacks --stack-name $1) + if [ $? -eq 0 ]; then + echo "$status" | jq -r ".Stacks[].StackStatus" + else + echo "DELETE_COMPLETE" + fi +} cat > cfn-policy.json<< EOF { @@ -23,18 +35,32 @@ ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) ## Create a temporary Admin role aws iam create-role --role-name "$ROLE_NAME" --assume-role-policy-document file://cfn-policy.json aws iam attach-role-policy --role-name "$ROLE_NAME" --policy-arn "arn:aws:iam::aws:policy/AdministratorAccess" +rm cfn-policy.json ## Wait a bit for this role to stick. It cannot be used immediately after creation. -sleep 15 +sleep 10 ## Delete stacks stacks=(sam-app-prod sam-app-dev sam-app-pipeline aws-sam-cli-managed-prod-pipeline-resources aws-sam-cli-managed-dev-pipeline-resources) -for name in "${stacks[@]}" +for stack_name in "${stacks[@]}" do - echo "Deleting stack: $name..." - aws cloudformation delete-stack --role-arn "arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME" --stack-name "$name" - sleep 10 + echo "Deleting stack: $stack_name..." + + aws cloudformation delete-stack \ + --role-arn "arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME" \ + --stack-name "$stack_name" + + status="$(get_stack_status $stack_name)" + + # Wait until the stack is actually deleted + while [ "$status" != "DELETE_COMPLETE" ] + do + sleep 5 + status="$(get_stack_status $stack_name)" + echo "$stack_name: $status" + done + done ## Delete the temporary Admin role diff --git a/workshop/static/images/code-pipeline-canary.gif b/workshop/static/images/code-pipeline-canary.gif new file mode 100644 index 0000000..9e2dc8e Binary files /dev/null and b/workshop/static/images/code-pipeline-canary.gif differ