├── .github └── pull_request_template.md ├── .gitignore ├── LICENSE ├── README.md ├── app ├── codedeploy.tf ├── outputs.tf ├── variables.tf └── versions.tf ├── deployment-group ├── main.tf ├── variables.tf └── versions.tf ├── notify-slack ├── README.md ├── functions │ └── lambda-slack.py ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf └── s3bucket ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Related Issue 7 | 8 | 9 | 10 | 11 | 12 | ## Motivation and Context 13 | 14 | 15 | ## How Has This Been Tested? 16 | 17 | 18 | 19 | 20 | ## Types of changes 21 | 22 | - [ ] Bug fix (non-breaking change which fixes an issue) 23 | - [ ] New feature (non-breaking change which adds functionality) 24 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 25 | 26 | ## Checklist: 27 | 28 | 29 | - [ ] My code follows the code style of this project. 30 | - [ ] My change requires a change to the documentation. 31 | - [ ] I have updated the documentation accordingly. 32 | - [ ] I have added tests to cover my changes. 33 | - [ ] All new and existing tests passed. 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | .terraform 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Skyscrapers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Terraform modules that are related to codedeploy 2 | 3 | # app 4 | Create a codedeploy app 5 | 6 | ## Requirements 7 | 8 | | Name | Version | 9 | |------|---------| 10 | | terraform | >= 0.12 | 11 | 12 | ## Providers 13 | 14 | | Name | Version | 15 | |------|---------| 16 | | aws | n/a | 17 | 18 | ## Inputs 19 | 20 | | Name | Description | Type | Default | Required | 21 | |------|-------------|------|---------|:--------:| 22 | | name | Name of your codedeploy application | `any` | n/a | yes | 23 | | project | The current project | `any` | n/a | yes | 24 | | s3_bucket_arn | ARN of the S3 bucket where to fetch the application revision packages | `string` | `""` | no | 25 | 26 | ## Outputs 27 | 28 | | Name | Description | 29 | |------|-------------| 30 | | app_name | n/a | 31 | | deployer_policy_arn | n/a | 32 | | deployer_policy_id | n/a | 33 | | deployer_policy_name | n/a | 34 | 35 | ## Example 36 | ``` 37 | module "codedeploy" { 38 | source = "github.com/skyscrapers/terraform-codedeploy//app" 39 | name = "application" 40 | project = "example" 41 | } 42 | ``` 43 | 44 | # deployment-group 45 | Create an deployment group for a codedeploy app 46 | 47 | ## Requirements 48 | 49 | | Name | Version | 50 | |------|---------| 51 | | terraform | >= 0.12 | 52 | 53 | ## Providers 54 | 55 | | Name | Version | 56 | |------|---------| 57 | | aws | n/a | 58 | 59 | ## Inputs 60 | 61 | | Name | Description | Type | Default | Required | 62 | |------|-------------|------|---------|:--------:| 63 | | app_name | Name of the app | `any` | n/a | yes | 64 | | autoscaling_groups | Autoscaling groups you want to attach to the deployment group | `list(string)` | n/a | yes | 65 | | environment | Environment where your codedeploy deployment group is used for | `any` | n/a | yes | 66 | | service_role_arn | IAM role that is used by the deployment group | `any` | n/a | yes | 67 | | alb_target_group | Name of the ALB target group to use, define it when traffic need to be blocked from ALB during deployment | `string` | `null` | no | 68 | | blue_termination_behavior | The action to take on instances in the original environment after a successful deployment. Only relevant when `enable_bluegreen` is `true` | `string` | `"KEEP_ALIVE"` | no | 69 | | bluegreen_timeout_action | When to reroute traffic from an original environment to a replacement environment. Only relevant when `enable_bluegreen` is `true` | `string` | `"CONTINUE_DEPLOYMENT"` | no | 70 | | ec2_tag_filter | Filter key and value you want to use for tags filters. Defined as key/value format, example: `{"Environment":"staging"}` | `map(string)` | `null` | no | 71 | | enable_bluegreen | Enable all bluegreen deployment options | `bool` | `false` | no | 72 | | green_provisioning | The method used to add instances to a replacement environment. Only relevant when `enable_bluegreen` is `true` | `string` | `"COPY_AUTO_SCALING_GROUP"` | no | 73 | | rollback_enabled | Whether to enable auto rollback | `bool` | `false` | no | 74 | | rollback_events | The event types that trigger a rollback | `list(string)` |
[
"DEPLOYMENT_FAILURE"
]
| no | 75 | | trigger_events | events that can trigger the notifications | `list(string)` |
[
"DeploymentStop",
"DeploymentRollback",
"DeploymentSuccess",
"DeploymentFailure",
"DeploymentStart"
]
| no | 76 | | trigger_target_arn | The ARN of the SNS topic through which notifications are sent | `string` | `null` | no | 77 | 78 | ## Outputs 79 | 80 | No output. 81 | 82 | 83 | ## Example 84 | ``` 85 | module "deployment_group" { 86 | source = "github.com/skyscrapers/terraform-codedeploy//deployment-group" 87 | environment = "production" 88 | app_name = module.codedeploy.app_name 89 | service_role_arn = module.iam.arn_role 90 | autoscaling_groups = ["autoscaling1", "autoscaling2"] 91 | } 92 | ``` 93 | 94 | 95 | # S3 bucket 96 | 97 | Create an S3 bucket to use with Codedeploy, to store application revisions. 98 | ## Requirements 99 | 100 | | Name | Version | 101 | |------|---------| 102 | | terraform | >= 0.12 | 103 | 104 | ## Providers 105 | 106 | | Name | Version | 107 | |------|---------| 108 | | aws | n/a | 109 | 110 | ## Inputs 111 | 112 | | Name | Description | Type | Default | Required | 113 | |------|-------------|------|---------|:--------:| 114 | | name_prefix | Prefix for the bucket name. Note that the same bucket is used for all codedeploy deployment groups | `any` | n/a | yes | 115 | 116 | ## Outputs 117 | 118 | | Name | Description | 119 | |------|-------------| 120 | | bucket_arn | n/a | 121 | | bucket_id | n/a | 122 | | policy_arn | n/a | 123 | | policy_id | n/a | 124 | | policy_name | n/a | 125 | 126 | ## Example 127 | 128 | ``` 129 | module "codedeploy_bucket" { 130 | source = "github.com/skyscrapers/terraform-codedeploy//s3bucket?ref=478373f6f8d4a46b7a1ec96090707365e0ae3e42" 131 | name_prefix = "app" 132 | } 133 | ``` 134 | 135 | # notify-slack 136 | Creates a lambda function that notifies Slack via the [incoming webhooks](https://skyscrapers.slack.com/apps/A0F7XDUAZ-incoming-webhooks) when a deployment event happens using an SNS topic to call the lambda function. 137 | 138 | 139 | ## Requirements 140 | 141 | | Name | Version | 142 | |------|---------| 143 | | terraform | >= 0.12 | 144 | 145 | ## Providers 146 | 147 | | Name | Version | 148 | |------|---------| 149 | | archive | n/a | 150 | | aws | n/a | 151 | 152 | ## Inputs 153 | 154 | | Name | Description | Type | Default | Required | 155 | |------|-------------|------|---------|:--------:| 156 | | kms_key_arn | KMS used for encrypting the webhook | `any` | n/a | yes | 157 | | slack_channel | E.g. #channel_name | `any` | n/a | yes | 158 | | slack_webhook_url | Needs to be encrypted from a file with _no_ encryption context, using: aws kms encrypt --key-id 'arn:' --plaintext 'fileb://webhook' --output text --query CiphertextBlob | `any` | n/a | yes | 159 | | notify_users | Slack usernames for mentions as a space separated string as '<@name1> <@name2>' or '' or '' | `string` | `""` | no | 160 | | verbose | All codedeploy messages will be output if true. Only CREATED, FAILED, STOPPED and SUCCEEDED if it is empty or false | `string` | `"true"` | no | 161 | 162 | ## Outputs 163 | 164 | | Name | Description | 165 | |------|-------------| 166 | | sns_topic | n/a | 167 | 168 | 169 | ## Example 170 | ``` 171 | module "slack-notification" { 172 | source = "github.com/skyscrapers/terraform-codedeploy//notify-slack" 173 | slack_webhook_url = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 174 | slack_channel = "#channel_name" 175 | kms_key_arn = aws_kms_key.kms_key.arn 176 | } 177 | ``` 178 | 179 | -------------------------------------------------------------------------------- /app/codedeploy.tf: -------------------------------------------------------------------------------- 1 | data "aws_region" "current" { 2 | } 3 | 4 | data "aws_caller_identity" "current" { 5 | } 6 | 7 | resource "aws_codedeploy_app" "app" { 8 | name = "${var.project}-${var.name}" 9 | } 10 | 11 | # This policy allows to upload application revisions to S3 (if the S3 bucket arn is provided), 12 | # register application revisions and trigger deployments on all deployment groups 13 | # It's basically taken from the AWS documentation here 14 | # http://docs.aws.amazon.com/codedeploy/latest/userguide/auth-and-access-control-iam-identity-based-access-control.html 15 | resource "aws_iam_policy" "deployer_policy" { 16 | name = "${var.project}-${var.name}-deployer-policy" 17 | description = "Policy to create a codedeploy application revision and to deploy it, for application ${aws_codedeploy_app.app.name}" 18 | 19 | policy = < <@name2>" or if the customer requests it, it can simply be "" or "". 13 | 14 | ## Testing 15 | 16 | To test that the lamda code is working, you can publish to the topic from the AWS CLI, setting the SNS ARN for the below command. 17 | Please note that currently the built in AWS Lamda test for SNS does not supply a correct JSON formatted message. 18 | 19 | `aws sns publish --topic-arn arn:...:cd-sns-lambda --message '{"applicationName": "test-slack-notify", "deploymentId": "d-12A3BCDEF", "deploymentGroupName": "dep-group-def-123", "status": "succeeded"}'` 20 | -------------------------------------------------------------------------------- /notify-slack/functions/lambda-slack.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import json,urllib2,urllib 3 | import os, boto3 4 | from base64 import b64decode 5 | import re 6 | import sys 7 | import traceback 8 | import logging 9 | logger = logging.getLogger() 10 | logger.setLevel(logging.INFO) 11 | 12 | def decrypt(in_message): 13 | """ 14 | Decrypts the message with kms 15 | AWS lamda SNS test is not valid json at 13-03-18 16 | """ 17 | region = os.environ['AWS_DEFAULT_REGION'] 18 | try: 19 | kms = boto3.client('kms', region_name=region) 20 | return kms.decrypt(CiphertextBlob=b64decode(in_message))['Plaintext'] 21 | except: 22 | logger.error(''.join(traceback.format_exception(sys.exc_info()))) 23 | 24 | 25 | def send_slack(message): 26 | """ 27 | Send Slack Message to Deployments Channel 28 | """ 29 | region = os.environ['AWS_DEFAULT_REGION'] 30 | slack_url = decrypt(os.environ['SLACK_WEBHOOK']) 31 | slack_channel = os.environ['SLACK_CHANNEL'] 32 | notify_users = os.environ['NOTIFY_USERS'] 33 | 34 | severity_level = "good" 35 | icon_emoji = ":codedeploy:" 36 | title = message['status'] 37 | pretext = "" 38 | deployment_url = '' 39 | 40 | text = "The deployment for app *%s* in group %s\n with id %s" % ( message['applicationName'], 41 | message['deploymentGroupName'], deployment_url) 42 | 43 | matchObj = re.match( r'fail', message['status'], re.I) # Check for FAILED state 44 | if matchObj : 45 | severity_level = "danger" 46 | text = text + ' failed.' 47 | if notify_users != "" : 48 | pretext = '*' + notify_users + '*' 49 | 50 | 51 | matchObj = re.match(r'stop', message['status'], re.I) # Check for STOPPED state 52 | if matchObj : 53 | severity_level = "warning" 54 | text = text + ' stopped.' 55 | 56 | payload = { 57 | "channel": slack_channel, 58 | "username": "codedeploy", 59 | "icon_emoji": icon_emoji, 60 | "attachments": [{ 61 | "pretext": pretext, 62 | "title": title, 63 | "markdwn_in" : ["text", "pretext"], 64 | "color": severity_level, 65 | "text": text, 66 | }] 67 | } 68 | 69 | data = urllib.urlencode({"payload":json.dumps(payload)}) 70 | req = urllib2.Request(slack_url, data) 71 | response = urllib2.urlopen(req) 72 | 73 | def lambda_handler(event, context): 74 | message = json.loads(event['Records'][0]['Sns']['Message']) 75 | 76 | # Verbose outputs all messages, otherwise only those set for ['status'] 77 | if os.environ['VERBOSE'] : 78 | send_slack(message) 79 | elif message['status'] == 'CREATED' or message['status'] == 'STOPPED' or message['status'] == 'FAILED' or message['status'] == 'SUCCEEDED': 80 | send_slack(message) 81 | else : 82 | send_slack(message) 83 | 84 | return message 85 | -------------------------------------------------------------------------------- /notify-slack/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_role" "iam_for_lambda" { 2 | name = "default_lambda" 3 | assume_role_policy = data.aws_iam_policy_document.iam_for_lambda.json 4 | } 5 | 6 | data "aws_iam_policy_document" "iam_for_lambda" { 7 | statement { 8 | actions = [ 9 | "sts:AssumeRole", 10 | ] 11 | principals { 12 | type = "Service" 13 | identifiers = ["lambda.amazonaws.com"] 14 | } 15 | } 16 | } 17 | 18 | resource "aws_iam_role_policy" "cd_sns_lambda_policy" { 19 | name = "cd_sns-lambda-policy" 20 | role = aws_iam_role.iam_for_lambda.id 21 | 22 | policy = data.aws_iam_policy_document.cd_sns_lambda_policy.json 23 | } 24 | 25 | data "aws_iam_policy_document" "cd_sns_lambda_policy" { 26 | statement { 27 | actions = [ 28 | "logs:CreateLogGroup", 29 | "logs:CreateLogStream", 30 | "logs:PutLogEvents", 31 | "kms:Decrypt" 32 | ] 33 | resources = ["*"] 34 | } 35 | } 36 | 37 | resource "aws_sns_topic" "cd-sns-lambda" { 38 | name = "cd-sns-lambda" 39 | } 40 | 41 | resource "aws_sns_topic_subscription" "lambda-subscription" { 42 | topic_arn = aws_sns_topic.cd-sns-lambda.arn 43 | protocol = "lambda" 44 | endpoint = aws_lambda_function.cd_sns_lambda.arn 45 | } 46 | 47 | data "archive_file" "slack_notification_zip" { 48 | type = "zip" 49 | output_path = "${path.module}/lambda-slack.zip" 50 | 51 | source_dir = "${path.module}/functions/" 52 | } 53 | 54 | 55 | resource "aws_lambda_function" "cd_sns_lambda" { 56 | function_name = "cd_sns_lambda" 57 | role = aws_iam_role.iam_for_lambda.arn 58 | handler = "lambda-slack.lambda_handler" 59 | 60 | filename = data.archive_file.slack_notification_zip.output_path 61 | source_code_hash = data.archive_file.slack_notification_zip.output_base64sha256 62 | 63 | runtime = "python2.7" 64 | timeout = "120" 65 | kms_key_arn = var.kms_key_arn 66 | 67 | environment { 68 | variables = { 69 | SLACK_WEBHOOK = var.slack_webhook_url 70 | SLACK_CHANNEL = var.slack_channel 71 | NOTIFY_USERS = var.notify_users 72 | VERBOSE = var.verbose 73 | } 74 | } 75 | } 76 | 77 | resource "aws_lambda_permission" "cd_sns_lambda" { 78 | statement_id = "AllowExecutionFromSNS" 79 | action = "lambda:InvokeFunction" 80 | function_name = aws_lambda_function.cd_sns_lambda.function_name 81 | principal = "sns.amazonaws.com" 82 | source_arn = aws_sns_topic.cd-sns-lambda.arn 83 | } 84 | -------------------------------------------------------------------------------- /notify-slack/outputs.tf: -------------------------------------------------------------------------------- 1 | output "sns_topic" { 2 | value = aws_sns_topic.cd-sns-lambda.arn 3 | } 4 | 5 | -------------------------------------------------------------------------------- /notify-slack/variables.tf: -------------------------------------------------------------------------------- 1 | variable "slack_webhook_url" { 2 | description = "Needs to be encrypted from a file with _no_ encryption context, using: aws kms encrypt --key-id 'arn:' --plaintext 'fileb://webhook' --output text --query CiphertextBlob" 3 | } 4 | 5 | variable "slack_channel" { 6 | description = "E.g. #channel_name" 7 | } 8 | 9 | variable "kms_key_arn" { 10 | description = "KMS used for encrypting the webhook" 11 | } 12 | 13 | variable "notify_users" { 14 | description = "Slack usernames for mentions as a space separated string as '<@name1> <@name2>' or '' or ''" 15 | type = string 16 | default = "" 17 | } 18 | 19 | variable "verbose" { 20 | description = "All codedeploy messages will be output if true. Only CREATED, FAILED, STOPPED and SUCCEEDED if it is empty or false" 21 | type = string 22 | default = "true" 23 | } 24 | 25 | -------------------------------------------------------------------------------- /notify-slack/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.12" 4 | } 5 | -------------------------------------------------------------------------------- /s3bucket/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "codedeploy_bucket" { 2 | bucket = "${var.name_prefix}-codedeploy-releases" 3 | acl = "private" 4 | 5 | versioning { 6 | enabled = true 7 | } 8 | } 9 | 10 | resource "aws_iam_policy" "codedeploy_policy" { 11 | name = "${var.name_prefix}_codedeploy_s3bucket_access" 12 | 13 | policy = <