diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..587fb1c --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,89 @@ +version: 2.1 + +executors: + go: + working_directory: /go/src/github.com/freshautomations/aws-lambda-go-template + docker: + - image: golang + environment: + GO111MODULE=on + +commands: + test: + steps: + - run: + name: Test + command: | + make test + lint: + steps: + - run: + name: Lint + command: | + make lint + build: + steps: + - run: + name: Build + command: | + make build + release: + steps: + - run: + name: Release + command: | + /bin/bash .circleci/release.bash aws-lambda-go-template + + +jobs: + lint: + executor: go + steps: + - checkout + - lint + test: + executor: go + steps: + - checkout + - test + build: + executor: go + steps: + - checkout + - build + - persist_to_workspace: + root: build + paths: + - "*" + release: + executor: go + steps: + - checkout + - attach_workspace: + at: /go/src/github.com/freshautomations/aws-lambda-go-template/build + - release + +workflows: + version: 2 + + mini_qa: + jobs: + - lint + - test + - build + release_management: + jobs: + - build: + filters: + branches: + ignore: /.*/ + tags: + only: /^v[0-9]+\.[0-9]+\.[0-9]+/ + - release: + requires: + - build + filters: + branches: + ignore: /.*/ + tags: + only: /^v[0-9]+\.[0-9]+\.[0-9]+/ diff --git a/.circleci/release.bash b/.circleci/release.bash new file mode 100644 index 0000000..66b2337 --- /dev/null +++ b/.circleci/release.bash @@ -0,0 +1,79 @@ +#!/bin/bash + +set -euo pipefail +# Opinionated script to release on GitHub. +# This script runs in CircleCI, in a golang docker container from a folder that is a git repo. +# The script was triggered because of a git tag push. CIRCLE_TAG contains the tag pushed. +# The script expects the binaries to reside in the build folder. +STOML_VERSION=0.3.0 + +# Create GitHub release draft +draftdata=" +{ + \"tag_name\": \"${CIRCLE_TAG}\", + \"target_commitish\": \"${CIRCLE_BRANCH}\", + \"name\": \"${CIRCLE_TAG}\", + \"body\": \"${CIRCLE_TAG} release.\", + \"draft\": true, + \"prerelease\": false +} +" +curl -s -S -X POST -u "${GITHUB_USERNAME}:${GITHUB_TOKEN}" https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/releases --user-agent releasebot -H "Accept: application/vnd.github.v3.json" -d "$draftdata" > draft.json +ERR=$? +if [[ $ERR -ne 0 ]]; then + echo "ERROR: curl error, exitcode $ERR." + exit $ERR +fi + +wget -q "https://github.com/freshautomations/stoml/releases/download/v${STOML_VERSION}/stoml_linux_amd64" +chmod +x ./stoml_linux_amd64 +export id="`./stoml_linux_amd64 draft.json id`" +if [ -z "$id" ]; then + echo "ERROR: Could not get draft id." + exit 1 +fi + +echo "Release ID: ${id}" + +# Upload binaries + +for binary in $* +do +echo -ne "Processing ${binary}... " +if [[ ! -f "build/${binary}" ]]; then + echo "${binary} does not exist." + continue +fi +curl -s -S -X POST -u "${GITHUB_USERNAME}:${GITHUB_TOKEN}" "https://uploads.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/releases/${id}/assets?name=${binary}_${CIRCLE_TAG}_linux_amd64" --user-agent releasebot -H "Accept: application/vnd.github.v3.raw+json" -H "Content-Type: application/octet-stream" -H "Content-Encoding: utf8" --data-binary "@build/${binary}" > upload.json +ERR=$? +if [[ $ERR -ne 0 ]]; then + echo "ERROR: curl error, exitcode $ERR." + exit $ERR +fi + +export uid="`./stoml_linux_amd64 upload.json id`" +if [ -z "$uid" ]; then + echo "ERROR: Could not get upload id for binary ${binary}." + exit 1 +fi + +echo "uploaded binary ${binary}, id ${uid}." +done + +rm draft.json +rm upload.json + +# Publish release +releasedata=" +{ + \"draft\": false, + \"tag_name\": \"${CIRCLE_TAG}\" +} +" +curl -s -S -X POST -u "${GITHUB_USERNAME}:${GITHUB_TOKEN}" "https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/releases/${id}" --user-agent script -H "Accept: application/vnd.github.v3.json" -d "$releasedata" +ERR=$? +if [[ $ERR -ne 0 ]]; then + echo "ERROR: curl error, exitcode $ERR." + exit $ERR +fi + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..afd1c96 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +GOPATH ?= $(shell go env GOPATH) +export GO111MODULE=on + +######################################## +### Build + +build: + go build -ldflags "-extldflags \"-static\"" -o build/mylambda . + +build-linux: + GOOS=linux GOARCH=amd64 $(MAKE) build + + +######################################## +### Testing + +test: + go test -cover -race ./... + +######################################## +### Linting + +$(GOPATH)/bin/golangci-lint: + GO111MODULE=off go get -u github.com/golangci/golangci-lint/cmd/golangci-lint + +lint: $(GOPATH)/bin/golangci-lint + $(GOPATH)/bin/golangci-lint run ./... + +######################################## +### Localnet + +localnet-start: + build/mylambda -webserver + +localnet-lambda: + # (Requirements: pip3 install aws-sam-cli) + sam local start-api + +.PHONY: build build-linux test localnet-start localnet-lambda diff --git a/README.md b/README.md new file mode 100644 index 0000000..973fbfe --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# AWS Lambda function template in Go + +## Overview + +This AWS Lambda function template in Go implements HTTP routing. +It can also be run as a stand-alone web service. + +## How to use locally + +For developers, it is easiest to run the code locally as a web service: + +### Build + +```bash +make build +``` + +### Run + +```bash +build/mylambda -webserver +``` + +- it will run the local webserver on port 3000 and accept connections. + +```bash +curl localhost:3000 +``` + +## How to use on AWS Lambda +The CircleCI configuration will deploy the code into AWS. +CircleCI has to have AWS access code ID and secret access key set up. \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..617202d --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/freshautomations/aws-lambda-go-template + +go 1.12 + +require ( + github.com/aws/aws-lambda-go v1.10.0 + github.com/awslabs/aws-lambda-go-api-proxy v0.2.0 + github.com/gorilla/mux v1.7.1 + github.com/spf13/pflag v1.0.3 + github.com/spf13/viper v1.3.2 + github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c28b5e4 --- /dev/null +++ b/go.sum @@ -0,0 +1,45 @@ +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aws/aws-lambda-go v1.10.0 h1:uafgdfYGQD0UeT7d2uKdyWW8j/ZYRifRPIdmeqLzLCk= +github.com/aws/aws-lambda-go v1.10.0/go.mod h1:zUsUQhAUjYzR8AuduJPCfhBuKWUaDbQiPOG+ouzmE1A= +github.com/awslabs/aws-lambda-go-api-proxy v0.2.0 h1:rlPO5+qdErTggV9EVXU3x+mZkX7zWwG9xL6tmX+1c+8= +github.com/awslabs/aws-lambda-go-api-proxy v0.2.0/go.mod h1:1WYCl0lFZD+KAqdW+usdz46oShDhOEj3uTw09Qv++28= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU= +github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= +github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..2f17ed7 --- /dev/null +++ b/main.go @@ -0,0 +1,170 @@ +// main package that executes the code +package main + +import ( + "encoding/json" + "fmt" + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-lambda-go/lambda" + "github.com/awslabs/aws-lambda-go-api-proxy/gorillamux" + "github.com/gorilla/mux" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "github.com/tomasen/realip" + "log" + "os/signal" + "syscall" + + "net/http" + "os" + "time" +) + +// Handler is an abstraction layer for implementing ServeHTTP. +type Handler struct { + HandlerFunc func(http.ResponseWriter, *http.Request) (int, error) +} + +// ServeHTTP implementation with default error response. https://golang.org/src/net/http/server.go?s=2736:2799#L75 +func (fn Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if status, err := fn.HandlerFunc(w, r); err != nil { + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(struct { + Message string `json:"message"` + }{Message: err.Error()}) + log.Printf("%d %s", status, err.Error()) + } +} + +// lambdaInitialized is an indicator that tells if the AWS Lambda function is in the startup phase. +var lambdaInitialized = false + +// Translates Gorilla Mux calls to AWS API Gateway calls +var lambdaProxy func(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) + +// LambdaHandler is the callback function for the AWS Lambda function. +func LambdaHandler(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { + if !lambdaInitialized { + // stdout and stderr are sent to AWS CloudWatch Logs + log.Print("cold start") + + r := AddRoutes() + muxLambda := gorillamux.New(r) + lambdaProxy = muxLambda.Proxy + + lambdaInitialized = true + } + + return lambdaProxy(req) +} + +// WebServerHandler sets up a local web server for handling incoming requests. +func WebServerHandler(ip string, port int) { + log.Print("web server execution start") + + r := AddRoutes() + + srv := &http.Server{ + Addr: fmt.Sprintf("%s:%d", ip, port), + // Good practice to set timeouts to avoid Slowloris attacks. + WriteTimeout: time.Second * 30, + ReadTimeout: time.Second * 30, + IdleTimeout: time.Second * 60, + Handler: r, + } + + // Manage signals for graceful exit + var gracefulStop = make(chan os.Signal) + signal.Notify(gracefulStop, syscall.SIGTERM) + signal.Notify(gracefulStop, syscall.SIGINT) + go func() { + sig := <-gracefulStop + log.Printf("caught signal: %+v", sig) + log.Print("waiting 2 seconds to finish processing") + time.Sleep(2 * time.Second) + os.Exit(0) + }() + + if err := srv.ListenAndServe(); err != nil { + log.Fatal(err) + } +} + +// MainHandler handles the requests coming to `/`. +func MainHandler(w http.ResponseWriter, _ *http.Request) (status int, err error) { + + // Todo: Build your function + + w.Header().Set("Content-Type", "application/json; charset=utf8") + _ = json.NewEncoder(w).Encode(struct { + Message string `json:"message"` + }{ + Message: fmt.Sprintf("Hello %s", viper.GetString("mysetting1")), + }) + + return +} + +// Create logs for each request +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("[%s] %s", realip.FromRequest(r), r.RequestURI) + next.ServeHTTP(w, r) + }) +} + +// AddRoutes adds the routes of the different calls to GorillaMux. +func AddRoutes() (r *mux.Router) { + + // Gorilla/Mux + r = mux.NewRouter() + + // Todo: Add your routes + + _ = r.Handle("/", Handler{HandlerFunc: MainHandler}) + + // Finally + r.Use(loggingMiddleware) + + return +} + +func main() { + + // Parse command-line parameters + pflag.Bool("version", false, "Return version number and exit.") + pflag.Bool("webserver", false, "run a local web-server instead of as an AWS Lambda function") + pflag.String("config", "", "read config from this local file") + pflag.String("ip", "127.0.0.1", "IP to listen on") + pflag.Uint("port", 3000, "Port to listen on") + pflag.Parse() + _ = viper.BindPFlags(pflag.CommandLine) + + // Todo: Set defaults for configuration variables + viper.SetDefault("mysetting1", "world") + + // Parse configuration from optional config file + if viper.GetString("config") != "" { + viper.SetConfigFile(viper.GetString("config")) + err := viper.ReadInConfig() // Find and read the config file + if err != nil { // Handle errors reading the config file + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + } + // Enable peek at environment variables for configuration settings. + viper.AutomaticEnv() + + if viper.GetBool("version") { + // Todo: Set version number + fmt.Println("v0.0.0") + return + } + + if viper.GetBool("webserver") { + WebServerHandler(viper.GetString("ip"), viper.GetInt("port")) + return + } + + //Lambda function on AWS + lambda.Start(LambdaHandler) +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..210f7d8 --- /dev/null +++ b/main_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "github.com/spf13/viper" + "net/http" + "net/http/httptest" + "testing" +) + +func Test_MainHandler(t *testing.T) { + viper.Set("mysetting1", "world") + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + handler := Handler{HandlerFunc: MainHandler} + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) + } + + expected := "{\"message\":\"Hello world\"}\n" + + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected) + } +}