├── .gitignore ├── LICENSE ├── README.md ├── buildspec.yml ├── deployment ├── buildspec.yml ├── interface.tf └── main.tf ├── interface.tf ├── main.tf ├── pipeline ├── deployer │ ├── interface.tf │ └── main.tf ├── dev │ ├── interface.tf │ └── main.tf ├── main.tf ├── prod │ ├── interface.tf │ └── main.tf └── stage │ ├── interface.tf │ └── main.tf ├── project.clj └── src └── lambda_deployment_example └── stream_handler.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lambda-deployment-example 2 | 3 | This repository has been archived. You can find a more up-to-date approach in my [pipeline-example](https://github.com/jdhollis/pipeline-example) and [deployment-pipeline](https://github.com/jdhollis/deployment-pipeline) repositories. 4 | 5 | An example of how to deploy a Lambda function automatically using CodePipeline and Terraform. 6 | 7 | Discussion on my site [here](https://theconsultingcto.com/posts/automated-lambda-deployments-with-terraform-codepipeline/). 8 | -------------------------------------------------------------------------------- /buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | LEIN_ROOT: 1 6 | 7 | phases: 8 | install: 9 | commands: 10 | - curl -o /tmp/lein https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein 11 | - chmod a+x /tmp/lein 12 | - mv /tmp/lein /usr/bin/ 13 | build: 14 | commands: 15 | - lein uberjar 16 | post_build: 17 | commands: 18 | - mkdir artifacts 19 | - cp -R deployment/* artifacts/ 20 | - cp target/lambda-deployment-example.jar artifacts/ 21 | 22 | artifacts: 23 | files: 24 | - '**/*' 25 | base-directory: artifacts 26 | -------------------------------------------------------------------------------- /deployment/buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | TERRAFORM_VERSION: 0.10.3 6 | TERRAFORM_SHA256: f316c6ff8b2abe257250d19cbe0e3cf745dedfa67b37bb4afaf95e0291efeade 7 | 8 | phases: 9 | install: 10 | commands: 11 | - yum -y install jq 12 | - curl 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI | jq 'to_entries | [ .[] | select(.key | (contains("Expiration") or contains("RoleArn")) | not) ] | map(if .key == "AccessKeyId" then . + {"key":"AWS_ACCESS_KEY_ID"} else . end) | map(if .key == "SecretAccessKey" then . + {"key":"AWS_SECRET_ACCESS_KEY"} else . end) | map(if .key == "Token" then . + {"key":"AWS_SESSION_TOKEN"} else . end) | map("export \(.key)=\(.value)") | .[]' -r > /tmp/aws_credentials # See https://github.com/hashicorp/terraform/issues/8746 13 | - cd /tmp && curl -o terraform.zip https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && echo "${TERRAFORM_SHA256} terraform.zip" | sha256sum -c --quiet && unzip terraform.zip && mv terraform /usr/bin 14 | pre_build: 15 | commands: 16 | - cd $CODEBUILD_SRC_DIR 17 | - aws s3 cp lambda-deployment-example.jar s3://${BUILD_ARTIFACTS_BUCKET}/lambda/deployed/lambda-deployment-example.jar --sse aws:kms --sse-kms-key-id $BUILD_ARTIFACTS_KMS_KEY_ARN 18 | build: 19 | commands: 20 | - source /tmp/aws_credentials && terraform init -no-color -backend-config "bucket=${TERRAFORM_STATE_BUCKET}" -backend-config "region=${TERRAFORM_STATE_REGION}" -backend-config "dynamodb_table=${TERRAFORM_STATE_LOCKING_TABLE}" -backend-config "kms_key_id=${TERRAFORM_STATE_KMS_KEY_ARN}" 21 | - source /tmp/aws_credentials && terraform apply -no-color -var "region=${REGION}" -var "build_artifacts_bucket=${BUILD_ARTIFACTS_BUCKET}" -var "function_name=${FUNCTION_NAME}" -var "function_handler=${FUNCTION_HANDLER}" -var "function_jar=${FUNCTION_JAR}" 22 | -------------------------------------------------------------------------------- /deployment/interface.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = "string" 3 | description = "AWS region" 4 | } 5 | 6 | variable "function_name" { 7 | type = "string" 8 | default = "lambda-deployment-example" 9 | } 10 | 11 | variable "function_handler" { 12 | type = "string" 13 | default = "lambda_deployment_example.stream_handler" 14 | } 15 | 16 | variable "function_jar" { 17 | type = "string" 18 | default = "lambda-deployment-example.jar" 19 | } 20 | 21 | variable "build_artifacts_bucket" { 22 | type = "string" 23 | description = "Bucket where the build artifacts are stored" 24 | } 25 | 26 | output "function_name" { 27 | value = "${aws_lambda_function.lambda_deployment_example.function_name}" 28 | } 29 | 30 | output "function_arn" { 31 | value = "${aws_lambda_function.lambda_deployment_example.arn}" 32 | } 33 | 34 | output "role_arn" { 35 | value = "${aws_iam_role.lambda_deployment_example.arn}" 36 | } 37 | -------------------------------------------------------------------------------- /deployment/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "s3" { 3 | bucket = "" # Partial configuration 4 | key = "lambda/lambda-deployment-example/terraform.tfstate" # Alas, no variable interpolation in the backend config 5 | encrypt = true 6 | } 7 | } 8 | 9 | data "aws_caller_identity" "current" {} 10 | 11 | data "aws_region" "current" { 12 | current = true 13 | } 14 | 15 | data "aws_iam_policy_document" "assume_role" { 16 | statement { 17 | actions = ["sts:AssumeRole"] 18 | 19 | principals { 20 | identifiers = ["lambda.amazonaws.com"] 21 | type = "Service" 22 | } 23 | } 24 | } 25 | 26 | data "aws_iam_policy_document" "function_permissions" { 27 | statement { 28 | actions = [ 29 | "logs:CreateLogGroup", 30 | "logs:CreateLogStream", 31 | "logs:PutLogEvents", 32 | ] 33 | 34 | resources = [ 35 | "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/${var.function_name}", 36 | "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/${var.function_name}:*", 37 | ] 38 | } 39 | 40 | # Add function-specific permissions here 41 | } 42 | 43 | resource "aws_iam_role" "function_role" { 44 | name = "${var.function_name}" 45 | assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}" 46 | } 47 | 48 | resource "aws_iam_role_policy" "lambda_deployment_example" { 49 | name = "${var.function_name}" 50 | policy = "${data.aws_iam_policy_document.function_permissions.json}" 51 | role = "${aws_iam_role.function_role.name}" 52 | } 53 | 54 | resource "aws_lambda_function" "definition" { 55 | s3_bucket = "${var.build_artifacts_bucket}" 56 | s3_key = "lambda/deployed/${var.function_jar}" 57 | source_code_hash = "${base64sha256(file(var.function_jar))}" 58 | function_name = "${var.function_name}" 59 | handler = "${var.function_handler}" 60 | role = "${aws_iam_role.function_role.arn}" 61 | memory_size = 256 62 | runtime = "java8" 63 | timeout = 30 64 | publish = true 65 | 66 | # Add function-specific environment variables here 67 | } 68 | -------------------------------------------------------------------------------- /interface.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = "string" 3 | description = "AWS region" 4 | default = "us-east-1" 5 | } 6 | 7 | variable "function_name" { 8 | type = "string" 9 | default = "lambda-deployment-example" 10 | } 11 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | provider "terraform" { 2 | version = "~> 0.1" 3 | } 4 | 5 | terraform { 6 | backend "s3" { 7 | bucket = "" 8 | key = "lambda/lambda-deployment-example/terraform.tfstate" 9 | region = "us-east-1" 10 | dynamodb_table = "" 11 | profile = "" 12 | encrypt = true 13 | kms_key_id = "" 14 | } 15 | } 16 | 17 | provider "aws" { 18 | version = "~> 0.1" 19 | region = "${var.region}" 20 | profile = "givewith" 21 | } 22 | 23 | module "pipeline" { 24 | source = "./pipeline" 25 | } 26 | -------------------------------------------------------------------------------- /pipeline/deployer/interface.tf: -------------------------------------------------------------------------------- 1 | variable "function_name" { 2 | type = "string" 3 | } 4 | 5 | variable "function_arn" { 6 | type = "string" 7 | } 8 | 9 | variable "function_jar" { 10 | type = "string" 11 | } 12 | 13 | variable "pipeline_name" { 14 | type = "string" 15 | } 16 | 17 | variable "lambda_build_artifacts_bucket" { 18 | type = "string" 19 | } 20 | 21 | variable "lambda_build_artifacts_key_arn" { 22 | type = "string" 23 | } 24 | 25 | variable "remote_state_bucket" { 26 | type = "string" 27 | } 28 | 29 | variable "remote_state_bucket_arn" { 30 | type = "string" 31 | } 32 | 33 | variable "remote_state_kms_key_arn" { 34 | type = "string" 35 | } 36 | 37 | variable "remote_state_locking_table_arn" { 38 | type = "string" 39 | } 40 | 41 | variable "remote_state_region" { 42 | type = "string" 43 | } 44 | 45 | output "project_name" { 46 | value = "${aws_codebuild_project.deployer.name}" 47 | } 48 | -------------------------------------------------------------------------------- /pipeline/deployer/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_region" "current" { 2 | current = true 3 | } 4 | 5 | data "aws_caller_identity" "current" {} 6 | 7 | data "aws_iam_policy_document" "assume_codebuild_service_role" { 8 | statement { 9 | actions = ["sts:AssumeRole"] 10 | 11 | principals { 12 | identifiers = ["codebuild.amazonaws.com"] 13 | type = "Service" 14 | } 15 | } 16 | } 17 | 18 | data "aws_iam_policy_document" "deployer" { 19 | statement { 20 | actions = [ 21 | "logs:CreateLogGroup", 22 | "logs:CreateLogStream", 23 | "logs:PutLogEvents", 24 | ] 25 | 26 | resources = [ 27 | "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/codebuild/${var.function_name}-deployer", 28 | "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/codebuild/${var.function_name}-deployer:*", 29 | ] 30 | } 31 | 32 | statement { 33 | actions = ["s3:ListBucket"] 34 | resources = ["${var.remote_state_bucket_arn}"] 35 | } 36 | 37 | statement { 38 | actions = [ 39 | "s3:GetObject", 40 | "s3:GetObjectVersion", 41 | "s3:PutObject", 42 | "s3:DeleteObject", 43 | ] 44 | 45 | resources = ["${var.remote_state_bucket_arn}/lambda/${var.function_name}/terraform.tfstate"] 46 | } 47 | 48 | statement { 49 | actions = [ 50 | "s3:GetObject", 51 | "s3:GetObjectVersion", 52 | ] 53 | 54 | resources = ["arn:aws:s3:::${var.lambda_build_artifacts_bucket}/${substr(var.pipeline_name, 0, 19)}/*"] 55 | } 56 | 57 | statement { 58 | actions = [ 59 | "s3:GetObject", 60 | "s3:PutObject", 61 | ] 62 | 63 | resources = ["arn:aws:s3:::${var.lambda_build_artifacts_bucket}/lambda/deployed/${var.function_name}.jar"] 64 | } 65 | 66 | statement { 67 | actions = [ 68 | "dynamodb:GetItem", 69 | "dynamodb:PutItem", 70 | "dynamodb:DeleteItem", 71 | ] 72 | 73 | resources = ["${var.remote_state_locking_table_arn}"] 74 | } 75 | 76 | statement { 77 | actions = [ 78 | "iam:AttachRolePolicy", 79 | "iam:CreatePolicy", 80 | "iam:CreateRole", 81 | "iam:DeleteRolePolicy", 82 | "iam:GetRole", 83 | "iam:GetRolePolicy", 84 | "iam:PutRolePolicy", 85 | ] 86 | 87 | resources = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.function_name}"] 88 | } 89 | 90 | statement { 91 | actions = [ 92 | "lambda:AddPermission", 93 | "lambda:CreateAlias", 94 | "lambda:CreateEventSourceMapping", 95 | "lambda:CreateFunction", 96 | "lambda:GetFunction", 97 | "lambda:GetPolicy", 98 | "lambda:ListVersionsByFunction", 99 | "lambda:PublishVersion", 100 | "lambda:RemovePermission", 101 | "lambda:UpdateAlias", 102 | "lambda:UpdateEventSourceMapping", 103 | "lambda:UpdateFunctionCode", 104 | "lambda:UpdateFunctionConfiguration", 105 | ] 106 | 107 | resources = [ 108 | "arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:function:${var.function_name}*", 109 | "arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:event-source-mappings:*", 110 | ] 111 | } 112 | 113 | statement { 114 | actions = [ 115 | "ec2:DescribeAccountAttributes", 116 | "ec2:DescribeRegions", 117 | ] 118 | 119 | resources = ["*"] 120 | } 121 | } 122 | 123 | resource "aws_iam_role" "deployer" { 124 | name = "${var.function_name}-deployer" 125 | assume_role_policy = "${data.aws_iam_policy_document.assume_codebuild_service_role.json}" 126 | } 127 | 128 | resource "aws_iam_role_policy" "deployer" { 129 | name = "${var.function_name}-deployer" 130 | role = "${aws_iam_role.deployer.id}" 131 | policy = "${data.aws_iam_policy_document.deployer.json}" 132 | } 133 | 134 | resource "aws_codebuild_project" "deployer" { 135 | name = "${var.function_name}-deployer" 136 | 137 | source { 138 | type = "CODEPIPELINE" 139 | } 140 | 141 | artifacts { 142 | type = "CODEPIPELINE" 143 | } 144 | 145 | environment { 146 | image = "aws/codebuild/eb-java-8-amazonlinux-64:2.4.3" 147 | type = "LINUX_CONTAINER" 148 | compute_type = "BUILD_GENERAL1_SMALL" 149 | 150 | environment_variable { 151 | name = "TERRAFORM_STATE_BUCKET" 152 | value = "${var.remote_state_bucket}" 153 | } 154 | 155 | environment_variable { 156 | name = "TERRAFORM_STATE_LOCK_TABLE" 157 | value = "${element(split("/", var.remote_state_locking_table_arn), 1)}" 158 | } 159 | 160 | environment_variable { 161 | name = "TERRAFORM_STATE_REGION" 162 | value = "${var.remote_state_region}" 163 | } 164 | 165 | environment_variable { 166 | name = "TERRAFORM_STATE_KMS_KEY_ARN" 167 | value = "${var.remote_state_kms_key_arn}" 168 | } 169 | 170 | environment_variable { 171 | name = "REGION" 172 | value = "${data.aws_region.current.name}" 173 | } 174 | 175 | environment_variable { 176 | name = "BUILD_ARTIFACTS_BUCKET" 177 | value = "${var.lambda_build_artifacts_bucket}" 178 | } 179 | 180 | environment_variable { 181 | name = "BUILD_ARTIFACTS_KMS_KEY_ARN" 182 | value = "${var.lambda_build_artifacts_key_arn}" 183 | } 184 | 185 | environment_variable { 186 | name = "FUNCTION_NAME" 187 | value = "${var.function_name}" 188 | } 189 | 190 | environment_variable { 191 | name = "FUNCTION_ARN" 192 | value = "${var.function_arn}" 193 | } 194 | 195 | environment_variable { 196 | name = "FUNCTION_JAR" 197 | value = "${var.function_jar}" 198 | } 199 | 200 | # Add function-specific environment variables here 201 | } 202 | 203 | service_role = "${aws_iam_role.deployer.arn}" 204 | build_timeout = "5" # minutes 205 | } 206 | -------------------------------------------------------------------------------- /pipeline/dev/interface.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = "string" 3 | } 4 | 5 | variable "assume_role_arn" { 6 | type = "string" 7 | } 8 | 9 | variable "function_name" { 10 | type = "string" 11 | } 12 | 13 | variable "function_arn" { 14 | type = "string" 15 | } 16 | 17 | variable "function_jar" { 18 | type = "string" 19 | } 20 | 21 | variable "source_github_token" { 22 | type = "string" 23 | } 24 | 25 | variable "source_owner" { 26 | type = "string" 27 | } 28 | 29 | variable "source_repo" { 30 | type = "string" 31 | } 32 | 33 | variable "source_branch" { 34 | type = "string" 35 | default = "master" 36 | } 37 | 38 | # 39 | # Deployer 40 | # 41 | 42 | variable "lambda_build_artifacts_bucket" { 43 | type = "string" 44 | } 45 | 46 | variable "lambda_build_artifacts_key_arn" { 47 | type = "string" 48 | } 49 | 50 | variable "remote_state_bucket" { 51 | type = "string" 52 | } 53 | 54 | variable "remote_state_bucket_arn" { 55 | type = "string" 56 | } 57 | 58 | variable "remote_state_kms_key_arn" { 59 | type = "string" 60 | } 61 | 62 | variable "remote_state_locking_table_arn" { 63 | type = "string" 64 | } 65 | 66 | variable "remote_state_region" { 67 | type = "string" 68 | } 69 | 70 | # 71 | # Pipeline 72 | # 73 | 74 | variable "codepipeline_service_role_arn" { 75 | type = "string" 76 | } 77 | 78 | variable "lambda_builder_project_name" { 79 | type = "string" 80 | } 81 | -------------------------------------------------------------------------------- /pipeline/dev/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "${var.region}" 3 | profile = "" 4 | 5 | assume_role { 6 | role_arn = "${var.assume_role_arn}" 7 | } 8 | } 9 | 10 | module "deployer" { 11 | source = "../deployer" 12 | 13 | function_name = "${var.function_name}" 14 | function_arn = "${var.function_arn}" 15 | function_jar = "${var.function_jar}" 16 | 17 | pipeline_name = "${var.function_name}-pipeline" 18 | 19 | lambda_build_artifacts_bucket = "${var.lambda_build_artifacts_bucket}" 20 | lambda_build_artifacts_key_arn = "${var.lambda_build_artifacts_key_arn}" 21 | remote_state_bucket = "${var.remote_state_bucket}" 22 | remote_state_bucket_arn = "${var.remote_state_bucket_arn}" 23 | remote_state_kms_key_arn = "${var.remote_state_kms_key_arn}" 24 | remote_state_locking_table_arn = "${var.remote_state_locking_table_arn}" 25 | remote_state_region = "${var.remote_state_region}" 26 | } 27 | 28 | resource "aws_codepipeline" "pipeline" { 29 | name = "${var.function_name}-pipeline" 30 | role_arn = "${var.codepipeline_service_role_arn}" 31 | 32 | artifact_store { 33 | location = "${var.lambda_build_artifacts_bucket}" 34 | type = "S3" 35 | 36 | encryption_key { 37 | id = "${var.lambda_build_artifacts_key_arn}" 38 | type = "KMS" 39 | } 40 | } 41 | 42 | stage { 43 | name = "Source" 44 | 45 | action { 46 | category = "Source" 47 | name = "Source" 48 | owner = "ThirdParty" 49 | provider = "GitHub" 50 | version = "1" 51 | 52 | output_artifacts = ["lambda-src"] 53 | 54 | configuration { 55 | OAuthToken = "${var.source_github_token}" 56 | Owner = "${var.source_owner}" 57 | Repo = "${var.source_repo}" 58 | Branch = "${var.source_branch}" 59 | } 60 | } 61 | } 62 | 63 | stage { 64 | name = "Build" 65 | 66 | action { 67 | category = "Build" 68 | name = "Build" 69 | owner = "AWS" 70 | provider = "CodeBuild" 71 | version = "1" 72 | 73 | input_artifacts = ["lambda-src"] 74 | output_artifacts = ["lambda-package"] 75 | 76 | configuration { 77 | ProjectName = "${var.lambda_builder_project_name}" 78 | } 79 | } 80 | } 81 | 82 | stage { 83 | name = "Deploy" 84 | 85 | action { 86 | category = "Build" 87 | name = "Deploy" 88 | owner = "AWS" 89 | provider = "CodeBuild" 90 | version = "1" 91 | 92 | input_artifacts = ["lambda-package"] 93 | 94 | configuration { 95 | ProjectName = "${module.deployer.project_name}" 96 | } 97 | } 98 | } 99 | 100 | stage { 101 | name = "Promote" 102 | 103 | action { 104 | category = "Invoke" 105 | name = "Promote" 106 | owner = "AWS" 107 | provider = "Lambda" 108 | version = "1" 109 | 110 | input_artifacts = ["lambda-package"] 111 | 112 | configuration { 113 | FunctionName = "lambda-package-promoter" 114 | UserParameters = "lambda/promoted/${var.function_name}.zip" 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /pipeline/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_region" "current" { 2 | current = true 3 | } 4 | 5 | # Insert any remote state here 6 | 7 | module "dev" { 8 | source = "./dev" 9 | region = "${data.aws_region.current.name}" 10 | 11 | assume_role_arn = "" 12 | 13 | function_name = "lambda-deployment-example" 14 | function_arn = "" 15 | function_jar = "lambda-deployment-example.jar" 16 | 17 | source_github_token = "" 18 | source_owner = "" 19 | source_repo = "" 20 | source_branch = "" 21 | 22 | codepipeline_service_role_arn = "" 23 | lambda_build_artifacts_bucket = "" 24 | lambda_build_artifacts_key_arn = "" 25 | lambda_builder_project_name = "" 26 | 27 | remote_state_bucket = "" 28 | remote_state_bucket_arn = "" 29 | remote_state_kms_key_arn = "" 30 | remote_state_locking_table_arn = "" 31 | remote_state_region = "" 32 | } 33 | 34 | module "stage" { 35 | source = "./stage" 36 | region = "" 37 | 38 | assume_role_arn = "" 39 | 40 | function_name = "lambda-deployment-example" 41 | function_arn = "" 42 | function_jar = "lambda-deployment-example.jar" 43 | 44 | codepipeline_service_role_arn = "" 45 | lambda_build_artifacts_key_arn = "" 46 | lambda_build_artifacts_bucket = "" 47 | lambda_builder_project_name = "" 48 | 49 | remote_state_bucket = "" 50 | remote_state_bucket_arn = "" 51 | remote_state_kms_key_arn = "" 52 | remote_state_locking_table_arn = "" 53 | remote_state_region = "" 54 | } 55 | 56 | module "prod" { 57 | source = "./prod" 58 | region = "" 59 | 60 | assume_role_arn = "" 61 | 62 | function_name = "lambda-deployment-example" 63 | function_arn = "" 64 | function_jar = "lambda-deployment-example.jar" 65 | 66 | codepipeline_service_role_arn = "" 67 | lambda_build_artifacts_key_arn = "" 68 | lambda_build_artifacts_bucket = "" 69 | lambda_builder_project_name = "" 70 | 71 | remote_state_bucket = "" 72 | remote_state_bucket_arn = "" 73 | remote_state_kms_key_arn = "" 74 | remote_state_locking_table_arn = "" 75 | remote_state_region = "" 76 | } 77 | -------------------------------------------------------------------------------- /pipeline/prod/interface.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = "string" 3 | } 4 | 5 | variable "assume_role_arn" { 6 | type = "string" 7 | } 8 | 9 | variable "function_name" { 10 | type = "string" 11 | } 12 | 13 | variable "function_arn" { 14 | type = "string" 15 | } 16 | 17 | variable "function_jar" { 18 | type = "string" 19 | } 20 | 21 | # 22 | # Deployer 23 | # 24 | 25 | variable "lambda_build_artifacts_bucket" { 26 | type = "string" 27 | } 28 | 29 | variable "lambda_build_artifacts_key_arn" { 30 | type = "string" 31 | } 32 | 33 | variable "remote_state_bucket" { 34 | type = "string" 35 | } 36 | 37 | variable "remote_state_bucket_arn" { 38 | type = "string" 39 | } 40 | 41 | variable "remote_state_kms_key_arn" { 42 | type = "string" 43 | } 44 | 45 | variable "remote_state_locking_table_arn" { 46 | type = "string" 47 | } 48 | 49 | variable "remote_state_region" { 50 | type = "string" 51 | } 52 | 53 | # 54 | # Pipeline 55 | # 56 | 57 | variable "codepipeline_service_role_arn" { 58 | type = "string" 59 | } 60 | 61 | variable "lambda_builder_project_name" { 62 | type = "string" 63 | } 64 | -------------------------------------------------------------------------------- /pipeline/prod/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "${var.region}" 3 | profile = "" 4 | 5 | assume_role { 6 | role_arn = "${var.assume_role_arn}" 7 | } 8 | } 9 | 10 | module "deployer" { 11 | source = "../deployer" 12 | 13 | function_name = "${var.function_name}" 14 | function_arn = "${var.function_arn}" 15 | function_jar = "${var.function_jar}" 16 | 17 | pipeline_name = "${var.function_name}-pipeline" 18 | 19 | lambda_build_artifacts_bucket = "${var.lambda_build_artifacts_bucket}" 20 | lambda_build_artifacts_key_arn = "${var.lambda_build_artifacts_key_arn}" 21 | remote_state_bucket = "${var.remote_state_bucket}" 22 | remote_state_bucket_arn = "${var.remote_state_bucket_arn}" 23 | remote_state_kms_key_arn = "${var.remote_state_kms_key_arn}" 24 | remote_state_locking_table_arn = "${var.remote_state_locking_table_arn}" 25 | remote_state_region = "${var.remote_state_region}" 26 | } 27 | 28 | resource "aws_codepipeline" "pipeline" { 29 | name = "${var.function_name}-pipeline" 30 | role_arn = "${var.codepipeline_service_role_arn}" 31 | 32 | artifact_store { 33 | location = "${var.lambda_build_artifacts_bucket}" 34 | type = "S3" 35 | 36 | encryption_key { 37 | id = "${var.lambda_build_artifacts_key_arn}" 38 | type = "KMS" 39 | } 40 | } 41 | 42 | stage { 43 | name = "Source" 44 | 45 | action { 46 | category = "Source" 47 | name = "Source" 48 | owner = "AWS" 49 | provider = "S3" 50 | version = "1" 51 | 52 | output_artifacts = ["promoted-lambda-package"] 53 | 54 | configuration { 55 | S3Bucket = "${var.lambda_build_artifacts_bucket}" 56 | S3ObjectKey = "lambda/promoted/${var.function_name}.zip" 57 | } 58 | } 59 | } 60 | 61 | stage { 62 | name = "Deploy" 63 | 64 | action { 65 | category = "Build" 66 | name = "Deploy" 67 | owner = "AWS" 68 | provider = "CodeBuild" 69 | version = "1" 70 | 71 | input_artifacts = ["promoted-lambda-package"] 72 | 73 | configuration { 74 | ProjectName = "${module.deployer.project_name}" 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pipeline/stage/interface.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | type = "string" 3 | } 4 | 5 | variable "assume_role_arn" { 6 | type = "string" 7 | } 8 | 9 | variable "function_name" { 10 | type = "string" 11 | } 12 | 13 | variable "function_arn" { 14 | type = "string" 15 | } 16 | 17 | variable "function_jar" { 18 | type = "string" 19 | } 20 | 21 | # 22 | # Deployer 23 | # 24 | 25 | variable "lambda_build_artifacts_bucket" { 26 | type = "string" 27 | } 28 | 29 | variable "lambda_build_artifacts_key_arn" { 30 | type = "string" 31 | } 32 | 33 | variable "remote_state_bucket" { 34 | type = "string" 35 | } 36 | 37 | variable "remote_state_bucket_arn" { 38 | type = "string" 39 | } 40 | 41 | variable "remote_state_kms_key_arn" { 42 | type = "string" 43 | } 44 | 45 | variable "remote_state_locking_table_arn" { 46 | type = "string" 47 | } 48 | 49 | variable "remote_state_region" { 50 | type = "string" 51 | } 52 | 53 | # 54 | # Pipeline 55 | # 56 | 57 | variable "codepipeline_service_role_arn" { 58 | type = "string" 59 | } 60 | 61 | variable "lambda_builder_project_name" { 62 | type = "string" 63 | } 64 | -------------------------------------------------------------------------------- /pipeline/stage/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "${var.region}" 3 | profile = "" 4 | 5 | assume_role { 6 | role_arn = "${var.assume_role_arn}" 7 | } 8 | } 9 | 10 | module "deployer" { 11 | source = "../deployer" 12 | 13 | function_name = "${var.function_name}" 14 | function_arn = "${var.function_arn}" 15 | function_jar = "${var.function_jar}" 16 | 17 | pipeline_name = "${var.function_name}-pipeline" 18 | 19 | lambda_build_artifacts_bucket = "${var.lambda_build_artifacts_bucket}" 20 | lambda_build_artifacts_key_arn = "${var.lambda_build_artifacts_key_arn}" 21 | remote_state_bucket = "${var.remote_state_bucket}" 22 | remote_state_bucket_arn = "${var.remote_state_bucket_arn}" 23 | remote_state_kms_key_arn = "${var.remote_state_kms_key_arn}" 24 | remote_state_locking_table_arn = "${var.remote_state_locking_table_arn}" 25 | remote_state_region = "${var.remote_state_region}" 26 | } 27 | 28 | resource "aws_codepipeline" "pipeline" { 29 | name = "${var.function_name}-pipeline" 30 | role_arn = "${var.codepipeline_service_role_arn}" 31 | 32 | artifact_store { 33 | location = "${var.lambda_build_artifacts_bucket}" 34 | type = "S3" 35 | 36 | encryption_key { 37 | id = "${var.lambda_build_artifacts_key_arn}" 38 | type = "KMS" 39 | } 40 | } 41 | 42 | stage { 43 | name = "Source" 44 | 45 | action { 46 | category = "Source" 47 | name = "Source" 48 | owner = "AWS" 49 | provider = "S3" 50 | version = "1" 51 | 52 | output_artifacts = ["promoted-lambda-package"] 53 | 54 | configuration { 55 | S3Bucket = "${var.lambda_build_artifacts_bucket}" 56 | S3ObjectKey = "lambda/promoted/${var.function_name}.zip" 57 | } 58 | } 59 | } 60 | 61 | stage { 62 | name = "Deploy" 63 | 64 | action { 65 | category = "Build" 66 | name = "Deploy" 67 | owner = "AWS" 68 | provider = "CodeBuild" 69 | version = "1" 70 | 71 | input_artifacts = ["promoted-lambda-package"] 72 | 73 | configuration { 74 | ProjectName = "${module.deployer.project_name}" 75 | } 76 | } 77 | } 78 | 79 | stage { 80 | name = "Promote" 81 | 82 | action { 83 | category = "Invoke" 84 | name = "Promote" 85 | owner = "AWS" 86 | provider = "Lambda" 87 | version = "1" 88 | 89 | input_artifacts = ["promoted-lambda-package"] 90 | 91 | configuration { 92 | FunctionName = "lambda-package-promoter" 93 | UserParameters = "lambda/promoted/${var.function_name}.zip" 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject lambda-deployment-example "0.1.0-SNAPSHOT" 2 | :description "Example of automated Lambda deployment with Terraform & CodePipeline" 3 | :url "https://github.com/jdhollis/lambda-deployment-example" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.9.0-alpha19"] 7 | [com.amazonaws/aws-lambda-java-core "1.1.0"]] 8 | :profiles {:uberjar {:aot :all 9 | :uberjar-name "lambda-deployment-example.jar"}}) 10 | -------------------------------------------------------------------------------- /src/lambda_deployment_example/stream_handler.clj: -------------------------------------------------------------------------------- 1 | (ns lambda-deployment-example.stream-handler 2 | (:gen-class 3 | :implements [com.amazonaws.services.lambda.runtime.RequestStreamHandler])) 4 | 5 | (defn -handleRequest [_ input-stream output-stream context] 6 | (println "This is it.")) 7 | --------------------------------------------------------------------------------