├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── bindata.go ├── deploy.go ├── docker.go ├── example └── example.go ├── info.go ├── lambda ├── lambda.go └── types.go ├── main.go ├── misc.go └── module ├── module.c └── module.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /go-lambda 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5 5 | - tip 6 | 7 | install: 8 | - go get github.com/xlab/go-lambda 9 | 10 | script: go test 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.7-wheezy 2 | MAINTAINER Maxim Kupriianov 3 | 4 | RUN apt-get update -q \ 5 | && DEBIAN_FRONTEND=noninteractive apt-get install -qy pkg-config python2.7-dev \ 6 | && apt-get clean \ 7 | && rm -rf /var/lib/apt 8 | 9 | CMD /usr/local/go/bin/go 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 Maxim Kupriianov 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the “Software”), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-lambda [![aws](https://d0.awsstatic.com/logos/aws/AWS_Logo_PoweredBy_127px.png)](https://aws.amazon.com) [![Build Status](https://travis-ci.org/xlab/go-lambda.svg)](https://travis-ci.org/xlab/go-lambda) 2 | 3 | `go-lambda` is a multi-purpose tool for creating and managing AWS Lambda instances backed by arbitrary Go code. Since there is no official support of Go, this tool automatically generates a wrappig module in Python 2.7 which is able to pass data back and forth to the Go land. Check out an [example](/example/example.go) function. 4 | 5 | ### Features at glance 6 | 7 | * `<10ms` startup time, feels like native experience; 8 | * Resulting `source.zip` size is only `1.0M` and in most cases has 2 files; 9 | * Easy to use: start writing your own lambdas in Go just in few minutes; 10 | * Relies on the official [AWS SDK for Go](https://github.com/aws/aws-sdk-go) while making all the requests, security is guaranteed; 11 | * No any boilerplate or "all-in-one" aims: the tool is made to do its job and nothing else. Yes, this is also a feature. 12 | 13 | ### Installation 14 | 15 | ``` 16 | $ go get github.com/xlab/go-lambda 17 | ``` 18 | 19 | Keep in mind that if you are using something other than `Linux x86_64`, this tool will invoke the [xlab/go-lambda](https://hub.docker.com/r/xlab/go-lambda/) Docker image in order to create cross-platform modules for AWS. Prepare your Docker setup beforehand. 20 | 21 | ### Reference 22 | 23 | ``` 24 | Usage: go-lambda [OPTIONS] COMMAND [arg...] 25 | 26 | A tiny AWS Lambda manager for Go deployments. 27 | 28 | Options: 29 | --verbose=false Run in verbose mode. 30 | -v, --version=false Show the version and exit 31 | -r, --region="eu-west-1" Specify the region. 32 | 33 | Commands: 34 | list Lists all defined AWS Lambda functions in the region. 35 | regions Lists regions available for AWS Lambda service. 36 | info Gets info about specific AWS Lambda function (specified by ID or NAME). 37 | source Gets .zip of an AWS Lambda function source (specified by ID or NAME). 38 | delete Deletes an AWS Lambda function (specified by ID or NAME). 39 | create Creates an AWS Lambda function and uploads specified Go package as its source. 40 | update Updates source of an AWS Lambda function (specified by ID or NAME). 41 | 42 | Run 'go-lambda COMMAND --help' for more information on a command. 43 | ``` 44 | 45 | ### Examples 46 | 47 | #### List available lambda functions in default region: 48 | 49 | ``` 50 | $ ./go-lambda list 51 | ╭──────────────────────────────────────────────────────────────────────────────────────────╮ 52 | │ AWS LAMBDA FUNCTIONS: 2 (eu-west-1) │ 53 | ├───┬─────────────────┬─────────────────────┬──────────┬───────────┬─────────┬─────────────┤ 54 | │ # │ NAME │ UPDATED │ SIZE │ MEM LIMIT │ TIMEOUT │ DESCRIPTION │ 55 | ├───┼─────────────────┼─────────────────────┼──────────┼───────────┼─────────┼─────────────┤ 56 | │ 1 │ xxx-handler │ 20 Dec 15 11:24 UTC │ 459.22K │ 128M │ 3s │ │ 57 | │ 2 │ yyy-handler │ 19 Dec 15 09:20 UTC │ 384.10K │ 128M │ 3s │ │ 58 | ╰───┴─────────────────┴─────────────────────┴──────────┴───────────┴─────────┴─────────────╯ 59 | ``` 60 | 61 | #### Create a new lambda backed by Go code: 62 | 63 | ``` 64 | $ go-lambda create --role arn:aws:iam::account-id:role/lambda_basic_execution handler github.com/xlab/go-lambda/example 65 | 66 | ╭───────────────────────────────────────────────────────────────────────────────────────╮ 67 | │ AWS LAMBDA FUNCTION example-handler (eu-west-1) │ 68 | ├──────────────────────┬────────────────────────────────────────────────────────────────┤ 69 | │ SHA256 Hash │ rcUmH7y39GVzmnwEbeV4aCj9e4vkPdoZIF14oj+nyWM= │ 70 | │ Code Size │ 1067.95K │ 71 | │ Description │ │ 72 | │ Amazon Resource Name │ arn:aws:lambda:eu-west-1:account-id:function:example-handler │ 73 | │ Handler │ example-handler.handler │ 74 | │ Last Modified │ 20 Dec 15 14:00 UTC │ 75 | │ Memory Size │ 128M │ 76 | │ Role │ arn:aws:iam::account-id:role/lambda_basic_execution │ 77 | │ Runtime │ python2.7 │ 78 | │ Timeout │ 3s │ 79 | │ Version │ 25 │ 80 | ╰──────────────────────┴────────────────────────────────────────────────────────────────╯ 81 | ``` 82 | 83 | Now you can manage your function in the AWS dashboard, create API endpoints and event handlers. 84 | 85 | #### Update existing function after fixing its Go code: 86 | 87 | ``` 88 | $ go-lambda update example-handler handler github.com/xlab/go-lambda/example 89 | ``` 90 | 91 | ### Roadmap 92 | 93 | - [ ] Replace JSON marshalling with something efficient (maybe); 94 | - [ ] Try to convince AWS staff that we need the native Go support in their cloud. 95 | 96 | Please, spread the word about `go-lambda` — let people use their favourite language for AWS Lambda. 🍻 97 | 98 | ![go-lambda](http://cl.ly/1w1U1n3w3W2n/go-lamda-alt.png) 99 | 100 | ### Useful links 101 | 102 | * [AWS Serverless Multi-Tier Architectures Whitepaper](https://d0.awsstatic.com/whitepapers/AWS_Serverless_Multi-Tier_Architectures.pdf) 103 | * [Lambda limits](http://docs.aws.amazon.com/lambda/latest/dg/limits.html) 104 | * [The Twelve Days of Lambda](https://aws.amazon.com/blogs/compute/the-twelve-days-of-lambda/) 105 | * [GoSparta Project Limitations](http://gosparta.io/docs/limitations/) 106 | 107 | ### License 108 | 109 | [MIT](/LICENSE) 110 | -------------------------------------------------------------------------------- /bindata.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-bindata. 2 | // sources: 3 | // module/module.c 4 | // module/module.go 5 | // DO NOT EDIT! 6 | 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "compress/gzip" 12 | "fmt" 13 | "io" 14 | "io/ioutil" 15 | "os" 16 | "path/filepath" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | func bindataRead(data []byte, name string) ([]byte, error) { 22 | gz, err := gzip.NewReader(bytes.NewBuffer(data)) 23 | if err != nil { 24 | return nil, fmt.Errorf("Read %q: %v", name, err) 25 | } 26 | 27 | var buf bytes.Buffer 28 | _, err = io.Copy(&buf, gz) 29 | clErr := gz.Close() 30 | 31 | if err != nil { 32 | return nil, fmt.Errorf("Read %q: %v", name, err) 33 | } 34 | if clErr != nil { 35 | return nil, err 36 | } 37 | 38 | return buf.Bytes(), nil 39 | } 40 | 41 | type asset struct { 42 | bytes []byte 43 | info os.FileInfo 44 | } 45 | 46 | type bindataFileInfo struct { 47 | name string 48 | size int64 49 | mode os.FileMode 50 | modTime time.Time 51 | } 52 | 53 | func (fi bindataFileInfo) Name() string { 54 | return fi.name 55 | } 56 | func (fi bindataFileInfo) Size() int64 { 57 | return fi.size 58 | } 59 | func (fi bindataFileInfo) Mode() os.FileMode { 60 | return fi.mode 61 | } 62 | func (fi bindataFileInfo) ModTime() time.Time { 63 | return fi.modTime 64 | } 65 | func (fi bindataFileInfo) IsDir() bool { 66 | return false 67 | } 68 | func (fi bindataFileInfo) Sys() interface{} { 69 | return nil 70 | } 71 | 72 | var _moduleModuleC = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x94\x55\x5d\x6f\xa3\x38\x14\x7d\x86\x5f\xe1\xa5\x52\x05\x11\xd3\x89\x34\x8f\xdd\xa9\x14\xb5\xc9\x6e\xa4\x4e\x82\x9a\x74\x35\xfb\x64\x39\x60\x12\x76\xc1\x66\x6d\x33\x1d\x76\xd4\xff\xbe\xd7\x36\xa4\x40\x98\x19\x36\x2f\xc4\xbe\xc7\xe7\x7e\x9c\xeb\xeb\xab\x2c\x4d\x68\x8a\x70\xb4\xdd\xad\x3f\xe3\x7b\xbc\xdb\x3e\x3f\xdd\x2f\xdd\xab\x8a\x8d\x6e\x53\x96\x64\xa9\xeb\x5e\x65\x2c\xce\xab\x84\x22\x2f\xaa\xd5\x89\xb3\x9b\x93\xd7\xd9\x93\x4a\x54\xb1\x2a\x68\x71\xa0\xa2\x6f\x81\x3d\x2e\x6a\x7e\xf8\x8b\xc6\xaa\x6f\x39\x54\x69\x4a\xc5\x9b\x05\x4c\x29\x8a\xfe\xc4\x7f\x2c\x9f\x76\xeb\xed\x06\xff\xbe\xfc\x8c\xee\xd0\xfc\xeb\xfc\xc3\xdc\xfc\x20\x14\x21\xb8\x40\x5e\x69\x02\x40\x1f\x50\x26\x11\xe3\x0a\xd5\x54\x21\x59\x95\x25\x17\x8a\x26\xde\x39\x62\x55\x97\x54\x67\x94\x31\x85\x72\x52\x1c\x12\x82\x4f\x84\x25\x39\xbd\x7d\xb3\xd9\xb8\xd1\x37\xd7\x89\xea\xad\x89\x04\xdc\x2e\x1e\x5c\xa7\x77\x00\xb5\xe7\x5e\xfb\x44\x02\x98\xe8\x57\x45\x05\xeb\xef\xa3\x23\x55\xb8\xb7\xe3\x07\x00\x95\x8a\xa8\x2c\x46\xad\xa7\x99\xdb\x27\xc3\x8c\xbe\xf8\x51\xbd\x87\xd0\x2c\x00\xcd\x74\x98\xe1\xf9\x00\x9a\x11\x71\x94\xdd\xf5\xdf\x2f\x89\x0c\x74\xf4\x7d\x26\x34\x93\x34\x4f\x6f\x5d\x47\x7f\xd0\x47\xe4\x0f\xcd\x81\x26\x7e\x77\xa7\x4a\x4c\xf2\x9c\xc7\xbe\xf5\x33\x0f\x9a\x23\xef\xee\x9a\x3c\x3e\x8e\x67\xe2\x08\xaa\x2a\x48\xda\x3f\xa7\x12\x58\x87\xaf\xe7\x24\xbf\xf0\x2c\x19\xe6\x97\x50\xeb\x6d\x2c\x58\x93\x85\xf5\xcd\x0f\xf8\x1c\x5e\x2a\x28\xf5\x07\x6e\x82\xc6\x8f\x51\xae\xc7\x85\x63\x70\x80\x9b\xe0\x80\x2f\x3e\x11\x31\x43\x09\x51\x44\x27\x96\xfd\x4b\x31\x34\x0a\x7c\x80\xe0\x4d\xb9\x9f\x12\x5d\x5a\xfc\x41\x77\x84\xc8\x7a\xa2\x5f\x28\x53\xed\x22\xe6\x4c\x81\x8b\x11\xe1\xd1\x85\xf2\x90\xe9\x25\x6f\x53\x99\x1f\xe9\xcf\xd5\x89\x8a\xa0\x4d\x15\xcd\x4c\x00\xb7\xed\xaa\x89\x00\xd6\x70\xb1\xfc\x5f\xa2\x7a\x21\x8e\x38\x22\x42\xd2\x7d\x55\x82\x92\x96\xcd\x93\xd2\x0b\xd1\x75\x13\xfb\x75\x1b\xb6\x61\x6d\x85\xde\x3c\x3f\x3e\x02\xcd\x2b\x54\xf1\x67\xd5\x82\x8f\x6d\x10\x68\x9e\x91\xca\x75\xdb\x2b\x3c\x17\xec\x5c\x2a\xe7\x2d\x37\x41\x65\x95\x6b\x96\xa8\xde\x29\x91\xb1\x23\x5e\x09\x5e\xd8\xbf\x0b\x96\xec\x40\x47\x1f\xdc\xdc\x68\x79\x43\xed\xf6\x46\x4b\xdb\xe9\x4e\x4b\xd0\x6d\xca\xde\xe5\x1a\x4a\x00\x16\x70\x36\x9c\x04\x78\xbd\x59\xef\x7d\x9d\x7f\xe0\x3a\xf3\x10\x7d\xef\xf7\x7e\x06\x6d\xab\x03\x98\xbd\x77\x1d\xaf\xcf\xed\x85\x5d\x1c\x68\xcd\x48\x61\x70\x1a\xcf\xd3\x81\xea\x41\x78\xc6\x1d\x88\xcc\xe2\x96\xf4\x87\xce\x01\x9c\x29\x5a\xb4\x58\x3f\xa1\x56\x28\x2e\x82\xf1\x4b\x18\x3a\xe6\x50\xb3\x9a\xc2\x5f\x42\xe1\xd5\x14\x20\x8c\x0c\xa2\x94\x98\x02\x95\xd3\xa1\x31\x2f\x4a\x22\x26\x55\x42\xd0\x72\x12\x25\x91\x98\x55\xfa\xc5\x9a\x08\x96\xf4\x9f\x8a\xb2\x78\x52\x0c\x00\x2f\x48\x59\x42\xaf\x4e\x41\x9f\x88\x3c\x21\x23\x9c\x1e\x49\x44\xd4\x69\xc5\xe2\xa1\x72\xcd\x90\x68\x94\xd3\x7f\x27\x95\x78\x5a\x79\x1b\xd1\xf8\xff\x50\x6d\x12\x16\xea\x60\xdf\x79\x0d\x8e\x6a\xbc\x8f\x56\x8f\x8b\xdf\x76\xf8\x61\xb9\x5a\x3c\x3f\xee\xc3\x1e\x38\xcd\xc9\x51\x9a\x0b\xd4\xbd\x32\xc8\x71\x1c\x83\x40\xba\x61\x79\xac\x0b\xa5\xe7\x77\x54\x7f\xda\x3e\xe8\xeb\x89\x57\xcf\x9b\x7b\x37\x63\x99\x2a\x78\x52\xc1\x60\xd3\x8f\x4f\xe0\x7e\xeb\x4e\x13\x6b\x81\x53\xce\xc8\xbd\xbf\xd1\x77\x92\xbe\x0c\x27\x96\x79\x91\x9b\xe9\x69\x47\x07\x7e\xa2\x24\xa9\xfd\xeb\x11\x8e\x00\xfd\x0a\x0f\x68\x67\x68\xea\x79\x89\x68\x2e\xa9\xd9\x83\xd4\xd7\x9b\xfb\xa7\xe5\x6a\xfc\xb0\x19\xae\xae\x63\xa3\x34\x33\x0f\xaf\x21\x9f\x4f\x36\x1f\xcf\xee\x43\x51\xcc\x20\x32\x63\xd2\x9a\xf0\x22\x49\x6c\x8a\xbe\xc5\xc0\x3c\xbf\x18\x3e\x9d\xf7\xf3\x3b\xce\xc1\xf7\x7f\x01\x00\x00\xff\xff\x2f\x55\x10\x80\x16\x0a\x00\x00") 73 | 74 | func moduleModuleCBytes() ([]byte, error) { 75 | return bindataRead( 76 | _moduleModuleC, 77 | "module/module.c", 78 | ) 79 | } 80 | 81 | func moduleModuleC() (*asset, error) { 82 | bytes, err := moduleModuleCBytes() 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | info := bindataFileInfo{name: "module/module.c", size: 2582, mode: os.FileMode(420), modTime: time.Unix(1471796165, 0)} 88 | a := &asset{bytes: bytes, info: info} 89 | return a, nil 90 | } 91 | 92 | var _moduleModuleGo = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x74\x93\x51\x6b\xdb\x30\x10\xc7\x9f\xa3\x4f\x71\x78\x50\xe4\x90\x28\xdb\x18\x1b\x94\x65\x30\xf2\xb2\xc1\x0a\x25\x30\xf6\x30\x4a\x50\xe4\xb3\x23\xa2\x48\x46\x96\xbb\x64\xa5\xdf\xbd\x92\xa5\xc6\x55\x43\xf3\x12\xf9\x2f\xdd\x5f\xf7\xbb\x3b\xb5\x5c\xec\x79\x83\x70\xe0\x52\x13\xb2\x58\xbc\x13\x8d\x81\x76\xdf\xcc\x85\xd1\xb5\x6c\xae\xa1\x3d\xb9\x9d\xd1\xf3\x8f\xec\x0b\xcc\xe7\xa2\x56\xbc\xe9\xfc\x42\xc9\x6d\x17\x4e\x4b\x2d\x54\x5f\x21\x7c\xed\x5c\xe5\x35\xb6\xfb\x46\xe4\xa1\x35\xd6\x41\xb1\x2a\xc8\xf3\x9a\x92\x49\xd1\x9d\xb4\x28\xd2\xff\x82\x3b\x73\x90\xc3\x67\xaf\x3b\x5e\xa3\x3f\x3a\x51\xfc\xb0\xad\x38\x14\x52\x17\xa4\x0c\xa9\xe0\x71\x08\x8e\xfa\x66\xc7\x75\xa5\x90\xb8\x53\x8b\xb9\x04\x52\xbb\xcf\x9f\x5e\x04\x34\xe8\x36\x79\x50\xdd\x6b\x71\x29\xd3\xf2\x95\xd1\x03\x99\xa4\xd5\xf5\x32\xdf\xa2\x31\x61\xf6\xbd\xaa\x7e\x86\xdb\xe8\xd5\xd6\xca\xaa\xc1\x1f\xc3\xee\xad\x31\x6a\x06\x1f\xca\x92\x4c\xa2\x7c\xd3\x1f\xd9\x2f\x23\xf6\x74\x54\x78\xfb\x37\x5a\xdd\xc1\x12\x22\x34\xbb\x35\x3e\x75\xb4\xf4\x2a\xde\xc5\xa2\x9b\xcd\x6c\x7e\x6b\x95\x8c\x2c\xba\xde\x6a\x48\x48\x8f\x6f\x55\x68\x23\xb8\x52\x91\xf8\x52\xa7\x89\x2f\xdb\x99\x01\xde\xa3\x76\x33\xf0\x2d\x77\x78\x74\x30\x5d\x31\xb1\xe3\xb6\x04\x9a\x56\x33\x58\xb1\x4e\xfe\xc7\x8d\x2b\x43\x91\xc6\xec\xd6\xcf\x94\xb5\x0e\x35\x9b\xd2\x69\xdc\x2b\xe9\x05\x76\x46\xb5\x1e\xb1\x3a\xe7\xed\x83\x79\x30\xa8\x35\xcd\x73\x19\xb9\xcf\xe7\x02\xfa\x30\x05\xd1\x0e\x02\x2a\x7d\x93\xc0\x62\xd7\xab\xb3\x90\x6e\x3a\xd3\x10\x72\xcf\x6d\x18\xcf\xd7\xed\x4c\x43\x35\x76\x0f\xd2\x6f\xe9\x5f\xca\x1e\xe9\xc1\x83\x65\x45\xbc\xcb\x7b\xea\xc7\xe1\xfd\x4b\xe2\x31\x5c\xe3\x3f\x1a\xde\x00\x5b\xff\xb9\xe9\x7d\xb2\x65\x18\xf6\xa1\x5d\xe1\x09\xfa\x99\x7c\x78\x24\x4f\x01\x00\x00\xff\xff\xea\xc7\x75\x4f\x95\x03\x00\x00") 93 | 94 | func moduleModuleGoBytes() ([]byte, error) { 95 | return bindataRead( 96 | _moduleModuleGo, 97 | "module/module.go", 98 | ) 99 | } 100 | 101 | func moduleModuleGo() (*asset, error) { 102 | bytes, err := moduleModuleGoBytes() 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | info := bindataFileInfo{name: "module/module.go", size: 917, mode: os.FileMode(420), modTime: time.Unix(1471798302, 0)} 108 | a := &asset{bytes: bytes, info: info} 109 | return a, nil 110 | } 111 | 112 | // Asset loads and returns the asset for the given name. 113 | // It returns an error if the asset could not be found or 114 | // could not be loaded. 115 | func Asset(name string) ([]byte, error) { 116 | cannonicalName := strings.Replace(name, "\\", "/", -1) 117 | if f, ok := _bindata[cannonicalName]; ok { 118 | a, err := f() 119 | if err != nil { 120 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) 121 | } 122 | return a.bytes, nil 123 | } 124 | return nil, fmt.Errorf("Asset %s not found", name) 125 | } 126 | 127 | // MustAsset is like Asset but panics when Asset would return an error. 128 | // It simplifies safe initialization of global variables. 129 | func MustAsset(name string) []byte { 130 | a, err := Asset(name) 131 | if err != nil { 132 | panic("asset: Asset(" + name + "): " + err.Error()) 133 | } 134 | 135 | return a 136 | } 137 | 138 | // AssetInfo loads and returns the asset info for the given name. 139 | // It returns an error if the asset could not be found or 140 | // could not be loaded. 141 | func AssetInfo(name string) (os.FileInfo, error) { 142 | cannonicalName := strings.Replace(name, "\\", "/", -1) 143 | if f, ok := _bindata[cannonicalName]; ok { 144 | a, err := f() 145 | if err != nil { 146 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) 147 | } 148 | return a.info, nil 149 | } 150 | return nil, fmt.Errorf("AssetInfo %s not found", name) 151 | } 152 | 153 | // AssetNames returns the names of the assets. 154 | func AssetNames() []string { 155 | names := make([]string, 0, len(_bindata)) 156 | for name := range _bindata { 157 | names = append(names, name) 158 | } 159 | return names 160 | } 161 | 162 | // _bindata is a table, holding each asset generator, mapped to its name. 163 | var _bindata = map[string]func() (*asset, error){ 164 | "module/module.c": moduleModuleC, 165 | "module/module.go": moduleModuleGo, 166 | } 167 | 168 | // AssetDir returns the file names below a certain 169 | // directory embedded in the file by go-bindata. 170 | // For example if you run go-bindata on data/... and data contains the 171 | // following hierarchy: 172 | // data/ 173 | // foo.txt 174 | // img/ 175 | // a.png 176 | // b.png 177 | // then AssetDir("data") would return []string{"foo.txt", "img"} 178 | // AssetDir("data/img") would return []string{"a.png", "b.png"} 179 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error 180 | // AssetDir("") will return []string{"data"}. 181 | func AssetDir(name string) ([]string, error) { 182 | node := _bintree 183 | if len(name) != 0 { 184 | cannonicalName := strings.Replace(name, "\\", "/", -1) 185 | pathList := strings.Split(cannonicalName, "/") 186 | for _, p := range pathList { 187 | node = node.Children[p] 188 | if node == nil { 189 | return nil, fmt.Errorf("Asset %s not found", name) 190 | } 191 | } 192 | } 193 | if node.Func != nil { 194 | return nil, fmt.Errorf("Asset %s not found", name) 195 | } 196 | rv := make([]string, 0, len(node.Children)) 197 | for childName := range node.Children { 198 | rv = append(rv, childName) 199 | } 200 | return rv, nil 201 | } 202 | 203 | type bintree struct { 204 | Func func() (*asset, error) 205 | Children map[string]*bintree 206 | } 207 | var _bintree = &bintree{nil, map[string]*bintree{ 208 | "module": &bintree{nil, map[string]*bintree{ 209 | "module.c": &bintree{moduleModuleC, map[string]*bintree{}}, 210 | "module.go": &bintree{moduleModuleGo, map[string]*bintree{}}, 211 | }}, 212 | }} 213 | 214 | // RestoreAsset restores an asset under the given directory 215 | func RestoreAsset(dir, name string) error { 216 | data, err := Asset(name) 217 | if err != nil { 218 | return err 219 | } 220 | info, err := AssetInfo(name) 221 | if err != nil { 222 | return err 223 | } 224 | err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) 225 | if err != nil { 226 | return err 227 | } 228 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 229 | if err != nil { 230 | return err 231 | } 232 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 233 | if err != nil { 234 | return err 235 | } 236 | return nil 237 | } 238 | 239 | // RestoreAssets restores an asset under the given directory recursively 240 | func RestoreAssets(dir, name string) error { 241 | children, err := AssetDir(name) 242 | // File 243 | if err != nil { 244 | return RestoreAsset(dir, name) 245 | } 246 | // Dir 247 | for _, child := range children { 248 | err = RestoreAssets(dir, filepath.Join(name, child)) 249 | if err != nil { 250 | return err 251 | } 252 | } 253 | return nil 254 | } 255 | 256 | func _filePath(dir, name string) string { 257 | cannonicalName := strings.Replace(name, "\\", "/", -1) 258 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) 259 | } 260 | 261 | -------------------------------------------------------------------------------- /deploy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/service/lambda" 8 | ) 9 | 10 | type deploymentConfig struct { 11 | HandlerName string 12 | LambdaName string 13 | LambdaDescription string 14 | MemorySize int 15 | Role string 16 | Timeout int 17 | SourceZip []byte 18 | } 19 | 20 | func createFunction(svc *lambda.Lambda, cfg *deploymentConfig, region string) { 21 | input := &lambda.CreateFunctionInput{ 22 | Code: &lambda.FunctionCode{ 23 | ZipFile: cfg.SourceZip, 24 | }, 25 | Description: aws.String(cfg.LambdaDescription), 26 | FunctionName: aws.String(cfg.LambdaName), 27 | Handler: aws.String(cfg.HandlerName), 28 | MemorySize: aws.Int64(int64(cfg.MemorySize)), 29 | Publish: aws.Bool(true), 30 | Role: aws.String(cfg.Role), 31 | Runtime: aws.String(lambda.RuntimePython27), 32 | Timeout: aws.Int64(int64(cfg.Timeout)), 33 | } 34 | f, err := svc.CreateFunction(input) 35 | if err != nil { 36 | log.Fatalln(err) 37 | } 38 | functionInfo(f, region) 39 | } 40 | 41 | func updateFunction(svc *lambda.Lambda, name, region string, sourceZip []byte) { 42 | input := &lambda.UpdateFunctionCodeInput{ 43 | FunctionName: aws.String(name), 44 | Publish: aws.Bool(true), 45 | ZipFile: sourceZip, 46 | } 47 | f, err := svc.UpdateFunctionCode(input) 48 | if err != nil { 49 | log.Fatalln(err) 50 | } 51 | functionInfo(f, region) 52 | } 53 | 54 | func deleteFunction(svc *lambda.Lambda, name, version string) { 55 | input := &lambda.DeleteFunctionInput{ 56 | FunctionName: aws.String(name), 57 | } 58 | if len(version) > 0 { 59 | input.Qualifier = aws.String(version) 60 | } 61 | if _, err := svc.DeleteFunction(input); err != nil { 62 | log.Fatalln(err) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "runtime" 11 | "strings" 12 | ) 13 | 14 | func mountPackageDir(pkg, dst string) string { 15 | return fmt.Sprintf("%s:%s", packageDir(pkg), dst) 16 | } 17 | 18 | func getDockerVersion(path string) (string, error) { 19 | out, err := exec.Command(path, "-v").Output() 20 | if err != nil { 21 | return "", err 22 | } 23 | return string(bytes.TrimSpace(out)), nil 24 | } 25 | 26 | func isNative() bool { 27 | return runtime.GOOS == "linux" && runtime.GOARCH == "amd64" 28 | } 29 | 30 | func runBuild(pkg, modulePath string) { 31 | if isNative() { 32 | nativeBuild(modulePath) 33 | return 34 | } 35 | dockerBuild(pkg, modulePath) 36 | } 37 | 38 | func nativeBuild(modulePath string) { 39 | if *debug { 40 | log.Println("go build native", modulePath) 41 | } 42 | 43 | path := getExecPath("go") 44 | pwd, _ := os.Getwd() 45 | args := []string{path, "build", "-buildmode", "c-shared", "-o", filepath.Join(pwd, "module.so")} 46 | if *debug { 47 | log.Println(strings.Join(args, " ")) 48 | } 49 | cmd := exec.Cmd{ 50 | Path: path, 51 | Args: args, 52 | Dir: modulePath, 53 | Stderr: os.Stderr, 54 | } 55 | if *debug { 56 | cmd.Stdout = os.Stdout 57 | } 58 | if err := cmd.Run(); err != nil { 59 | log.Fatalln(err) 60 | } 61 | } 62 | 63 | func dockerBuild(pkg, modulePath string) { 64 | if *debug { 65 | log.Println("go build via Docker", pkg) 66 | } 67 | pwd, _ := os.Getwd() 68 | dockerPath := getExecPath("docker") 69 | 70 | gopath, mounts := gopathMounts("/go") 71 | args := []string{dockerPath, 72 | "run", "-a", "stdout", "-a", "stderr", "--rm", "-e", "GOPATH=" + gopath, 73 | "-v", mountPackageDir(pkg, "/go/src/in"), "-v", fmt.Sprintf("%s:/out", pwd), 74 | "-v", fmt.Sprintf("%s:/go/src/module", modulePath), 75 | } 76 | for i, src := range mounts { 77 | args = append(args, "-v", fmt.Sprintf("%s:/go/path%d/src", src, i)) 78 | } 79 | args = append(args, "xlab/go-lambda", "go", "build", "-buildmode", "c-shared", "-o", "/out/module.so", "module") 80 | if *debug { 81 | log.Println(strings.Join(args, " ")) 82 | } 83 | 84 | cmd := exec.Cmd{ 85 | Path: dockerPath, 86 | Args: args, 87 | Stdout: os.Stdout, 88 | Stderr: os.Stderr, 89 | } 90 | // if *debug { 91 | // cmd.Stdout = os.Stdout 92 | // } 93 | if err := cmd.Run(); err != nil { 94 | log.Fatalln(err) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | // Package example shows how an AWS Lambda function can be implemented using Go. 2 | // 3 | // Build steps: `go-lambda update example-handler handler github.com/xlab/go-lambda/example` 4 | package example 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "fmt" 10 | "time" 11 | 12 | "github.com/xlab/go-lambda/lambda" 13 | ) 14 | 15 | // Handler is a bridge that will be called by a python module wrapping this package. 16 | var Handler = lambda.Use(lambda.HandlerFunc(exampleHandler)) 17 | 18 | func exampleHandler(event json.RawMessage, context *lambda.Context) []byte { 19 | buf := new(bytes.Buffer) 20 | 21 | // decode event data 22 | req := new(request) 23 | json.Unmarshal(event, &req) 24 | 25 | // read a variable from LambdaContext 26 | fmt.Fprintf(buf, "Hello from %s. Current time: %v\n", 27 | context.FunctionName, time.Now().Format(time.Kitchen)) 28 | 29 | // use some of the request data 30 | if len(req.Name) > 0 { 31 | fmt.Fprintf(buf, "Have a nice day, %s!\n", req.Name) 32 | } 33 | 34 | return buf.Bytes() 35 | } 36 | 37 | type request struct { 38 | Name string `json:"name"` 39 | } 40 | -------------------------------------------------------------------------------- /info.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | "os" 9 | "regexp" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/aws/aws-sdk-go/service/lambda" 14 | "github.com/xlab/tablewriter" 15 | ) 16 | 17 | func listFunctions(svc *lambda.Lambda, region string, rx *regexp.Regexp) { 18 | resp, err := svc.ListFunctions(nil) 19 | if err != nil { 20 | log.Fatalln(err) 21 | } else if len(resp.Functions) == 0 { 22 | fmt.Printf("0 lambda functions available in region %s.\n", region) 23 | return 24 | } 25 | table := tablewriter.CreateTable() 26 | table.UTF8Box() 27 | table.AddHeaders("#", "NAME", "UPDATED", "SIZE", "MEM LIMIT", "TIMEOUT", "DESCRIPTION") 28 | var filtered int 29 | for i, f := range resp.Functions { 30 | if rx != nil && !rx.MatchString(*f.FunctionName) { 31 | continue 32 | } 33 | filtered++ 34 | descLimited := *f.Description // wordwrap.WrapString(*f.Description, 40) 35 | table.AddRow(strconv.Itoa(i+1), *f.FunctionName, 36 | parseDate(*f.LastModified).Format(time.RFC822), 37 | fmt.Sprintf("%.2fK", float32(*f.CodeSize)/1024.0), 38 | fmt.Sprintf("%dM", *f.MemorySize), 39 | time.Second*time.Duration(*f.Timeout), descLimited) 40 | } 41 | if filtered > 0 { 42 | table.AddTitle(fmt.Sprintf("AWS LAMBDA FUNCTIONS: %d (%s)", filtered, region)) 43 | fmt.Println(table.Render()) 44 | return 45 | } 46 | fmt.Printf("0 lambda functions matched `%s` pattern.\n", rx.String()) 47 | } 48 | 49 | func parseDate(v string) time.Time { 50 | t, _ := time.ParseInLocation("2006-01-02T15:04:05.999-0700", v, time.UTC) 51 | return t 52 | } 53 | 54 | func findFuction(svc *lambda.Lambda, id int, name string) *lambda.FunctionConfiguration { 55 | listResp, err := svc.ListFunctions(nil) 56 | if err != nil { 57 | log.Fatalln(err) 58 | } else if len(listResp.Functions) == 0 { 59 | fmt.Println("error: 0 lambda functions available.") 60 | os.Exit(-1) 61 | } 62 | for i, f := range listResp.Functions { 63 | if i+1 == id || *f.FunctionName == name { 64 | return f 65 | } 66 | } 67 | switch { 68 | case id > 0: 69 | fmt.Printf("error: no such function with ID=%d found.\n", id) 70 | case len(name) > 0: 71 | fmt.Printf("error: no such function with NAME=%s found.\n", name) 72 | } 73 | os.Exit(-1) 74 | return nil 75 | } 76 | 77 | func functionSource(svc *lambda.Lambda, f *lambda.FunctionConfiguration, path string, onlyURL bool) { 78 | getResp, err := svc.GetFunction(&lambda.GetFunctionInput{ 79 | FunctionName: f.FunctionName, 80 | Qualifier: f.Version, 81 | }) 82 | if err != nil { 83 | log.Println("error:", err) 84 | return 85 | } 86 | if onlyURL { 87 | log.Println(*getResp.Code.Location) 88 | return 89 | } 90 | resp, err := http.Get(*getResp.Code.Location) 91 | if err != nil { 92 | log.Fatalln(err) 93 | } 94 | defer resp.Body.Close() 95 | out, err := os.Create(path) 96 | if err != nil { 97 | log.Fatalln(err) 98 | } 99 | // Temporarily turn off pb due to an unknown bug in master 100 | // bar := pb.New(int(resp.ContentLength)).SetUnits(pb.U_BYTES).SetRefreshRate(time.Millisecond * 10) 101 | // barproxy := bar.NewProxyReader(resp.Body) 102 | // bar.ShowSpeed = true 103 | // bar.Start() 104 | // io.Copy(out, barproxy) 105 | io.Copy(out, resp.Body) 106 | out.Close() 107 | // bar.Finish() 108 | } 109 | 110 | func functionInfo(f *lambda.FunctionConfiguration, region string) { 111 | table := tablewriter.CreateTable() 112 | table.UTF8Box() 113 | table.AddTitle(fmt.Sprintf("AWS LAMBDA FUNCTION %s (%s)", *f.FunctionName, region)) 114 | table.AddRow("SHA256 Hash", *f.CodeSha256) 115 | table.AddRow("Code Size", fmt.Sprintf("%.2fK", float32(*f.CodeSize)/1024.0)) 116 | table.AddRow("Description", *f.Description) // wordwrap.WrapString(*f.Description, 60)) 117 | table.AddRow("Amazon Resource Name", *f.FunctionArn) 118 | table.AddRow("Handler", *f.Handler) 119 | table.AddRow("Last Modified", parseDate(*f.LastModified).Format(time.RFC822)) 120 | table.AddRow("Memory Size", fmt.Sprintf("%dM", *f.MemorySize)) 121 | table.AddRow("Role", *f.Role) 122 | table.AddRow("Runtime", *f.Runtime) 123 | table.AddRow("Timeout", time.Second*time.Duration(*f.Timeout)) 124 | table.AddRow("Version", *f.Version) 125 | fmt.Println(table.Render()) 126 | } 127 | 128 | func listRegions() { 129 | table := tablewriter.CreateTable() 130 | table.UTF8Box() 131 | table.AddTitle("AWS LAMBDA REGIONS (2015-12-20)") 132 | table.AddRow("us-east-1", "US East (N. Virginia)") 133 | table.AddRow("us-west-2", "US West (Oregon)") 134 | table.AddRow("eu-west-1", "EU (Ireland)") 135 | table.AddRow("ap-northeast-1", "Asia Pacific (Tokyo)") 136 | fmt.Println(table.Render()) 137 | } 138 | -------------------------------------------------------------------------------- /lambda/lambda.go: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | import ( 4 | "encoding/json" 5 | "unsafe" 6 | ) 7 | 8 | import "C" 9 | 10 | type HandlerFunc func(event json.RawMessage, context *Context) []byte 11 | 12 | // Use the provided HandlerFunc as handler for incoming requests. This function returns 13 | // a bridge that manages values passing between the caller (python) and the target HandlerFunc. 14 | // 15 | // Note: do not pass the bridge directly to C, otherwise CGo checker will complain, 16 | // instead use a map of handles, see module/module.go for example. 17 | // 18 | // Warning: bytes of eventData should be copied explicitly if you want to use them outside 19 | // the HandlerFunc scope (e.g. in goroutines), they are valid until the Bridge func returns. 20 | func Use(fn HandlerFunc) bridge { 21 | return func(eventData, ctxData *C.char) (result *C.char, size C.size_t) { 22 | var context *Context 23 | json.Unmarshal(bytesFrom(ctxData), &context) 24 | event := json.RawMessage(bytesFrom(eventData)) 25 | 26 | buf := fn(event, context) 27 | result = C.CString(string(buf)) // TODO(xlab): optimize if needed 28 | size = (C.size_t)(len(buf)) 29 | return 30 | } 31 | } 32 | 33 | type bridge func(event, context *C.char) (result *C.char, size C.size_t) 34 | 35 | func bytesFrom(p *C.char) []byte { 36 | var slice []byte 37 | if p != nil && *p != 0 { 38 | h := (*sliceHeader)(unsafe.Pointer(&slice)) 39 | h.Data = uintptr(unsafe.Pointer(p)) 40 | for *p != 0 { 41 | p = (*C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + 1)) // p++ 42 | } 43 | h.Len = int(uintptr(unsafe.Pointer(p)) - h.Data) 44 | h.Cap = h.Len 45 | } 46 | return slice 47 | } 48 | 49 | type sliceHeader struct { 50 | Data uintptr 51 | Len int 52 | Cap int 53 | } 54 | -------------------------------------------------------------------------------- /lambda/types.go: -------------------------------------------------------------------------------- 1 | package lambda 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | ) 7 | 8 | // Context defines LambdaContext object as described on 9 | // http://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html 10 | type Context struct { 11 | FunctionName string `json:"function_name"` 12 | FunctionVersion string `json:"function_version"` 13 | InvokedFunctionARN string `json:"invoked_function_arn"` 14 | MemoryLimitString string `json:"memory_limit_in_mb"` 15 | RequestID string `json:"aws_request_id"` 16 | LogGroupName string `json:"log_group_name"` 17 | LogStreamName string `json:"log_stream_name"` 18 | Identity *CognitoIdentity `json:"identity"` 19 | ClientContext *ClientContext `json:"client_context"` 20 | } 21 | 22 | func (c *Context) MemoryLimit() int { 23 | i, _ := strconv.Atoi(c.MemoryLimitString) 24 | return i 25 | } 26 | 27 | // CognitoIdentity identity provider when invoked through the AWS Mobile SDK. 28 | type CognitoIdentity struct { 29 | ID string `json:"cognito_identity_id"` 30 | PoolID string `json:"cognito_identity_pool_id"` 31 | } 32 | 33 | // ClientContext holds information about the client application and device when invoked through the AWS Mobile SDK. 34 | type ClientContext struct { 35 | InstallationID string `json:"installation_id"` 36 | AppTitle string `json:"app_title"` 37 | AppVersionName string `json:"app_version_name"` 38 | AppVersionCode string `json:"app_version_code"` 39 | AppPackageName string `json:"app_package_name"` 40 | 41 | Custom json.RawMessage `json:"custom"` 42 | Env json.RawMessage `json:"env"` 43 | } 44 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "regexp" 10 | "strconv" 11 | 12 | "github.com/aws/aws-sdk-go/aws" 13 | "github.com/aws/aws-sdk-go/aws/session" 14 | "github.com/aws/aws-sdk-go/service/lambda" 15 | "github.com/jawher/mow.cli" 16 | ) 17 | 18 | var golambda = cli.App("go-lambda", "A tiny AWS Lambda manager for Go deployments.") 19 | var debug = golambda.BoolOpt("verbose", false, "Run in verbose mode.") 20 | 21 | func init() { 22 | log.SetFlags(log.Lshortfile) 23 | golambda.Version("v version", "0.1") 24 | } 25 | 26 | func main() { 27 | defaultRegion := os.Getenv("AWS_REGION") 28 | if "" == defaultRegion { 29 | defaultRegion = "eu-west-1" 30 | } 31 | region := golambda.StringOpt("r region", defaultRegion, "Specify the region.") 32 | golambda.Command("list", "Lists all defined AWS Lambda functions in the region.", func(cmd *cli.Cmd) { 33 | filter := cmd.StringOpt("f filter", "", "Filter names by regexp.") 34 | cmd.Action = func() { 35 | rx, err := regexp.Compile(*filter) 36 | if len(*filter) > 0 && err != nil { 37 | log.Fatalln(err) 38 | } 39 | svc := lambda.New(session.New(&aws.Config{ 40 | Region: aws.String(*region), 41 | })) 42 | listFunctions(svc, *region, rx) 43 | } 44 | }) 45 | golambda.Command("regions", "Lists regions available for AWS Lambda service.", func(cmd *cli.Cmd) { 46 | cmd.Action = func() { 47 | listRegions() 48 | } 49 | }) 50 | golambda.Command("info", "Gets info about specific AWS Lambda function (specified by ID or NAME).", 51 | func(cmd *cli.Cmd) { 52 | cmd.Spec = "(ID | NAME)" 53 | idStr := cmd.StringArg("ID", "", "Function ID (as in `list` result).") 54 | cmd.StringArg("NAME", "", "Function NAME.") 55 | cmd.Action = func() { 56 | svc := lambda.New(session.New(&aws.Config{ 57 | Region: aws.String(*region), 58 | })) 59 | id, _ := strconv.Atoi(*idStr) 60 | functionInfo(findFuction(svc, id, *idStr), *region) 61 | } 62 | }) 63 | golambda.Command("source", "Gets .zip of an AWS Lambda function source (specified by ID or NAME).", 64 | func(cmd *cli.Cmd) { 65 | cmd.Spec = "(ID | NAME)" 66 | idStr := cmd.StringArg("ID", "", "Function ID (as in `list` result).") 67 | cmd.StringArg("NAME", "", "Function NAME.") 68 | source := cmd.StringOpt("o out", "source.zip", "Specify path of output file.") 69 | onlyURL := cmd.BoolOpt("u url", false, "Show file URL only (valid for 10m), do not download.") 70 | cmd.Action = func() { 71 | svc := lambda.New(session.New(&aws.Config{ 72 | Region: aws.String(*region), 73 | })) 74 | id, _ := strconv.Atoi(*idStr) 75 | f := findFuction(svc, id, *idStr) 76 | functionSource(svc, f, *source, *onlyURL) 77 | } 78 | }) 79 | golambda.Command("delete", "Deletes an AWS Lambda function (specified by ID or NAME).", 80 | func(cmd *cli.Cmd) { 81 | cmd.Spec = "(ID | NAME)" 82 | idStr := cmd.StringArg("ID", "", "Function ID (as in `list` result).") 83 | cmd.StringArg("NAME", "", "Function NAME.") 84 | version := cmd.StringOpt("v version", "", "Function version (qualifier) to delete.") 85 | cmd.Action = func() { 86 | svc := lambda.New(session.New(&aws.Config{ 87 | Region: aws.String(*region), 88 | })) 89 | id, _ := strconv.Atoi(*idStr) 90 | f := findFuction(svc, id, *idStr) 91 | deleteFunction(svc, *f.FunctionName, *version) 92 | } 93 | }) 94 | golambda.Command("create", "Creates an AWS Lambda function and uploads specified Go package as its source.", 95 | func(cmd *cli.Cmd) { 96 | cmd.Spec = "[OPTIONS] FUNC PACKAGE [FILES...]" 97 | packageFunc := cmd.StringArg("FUNC", "", "Function name in Go package to be called from within Python wrapper.") 98 | packagePath := cmd.StringArg("PACKAGE", ".", "Fully qualified import path of a Go package.") 99 | additionalFiles := cmd.StringsArg("FILES", nil, "A list of additional static files to be included in archive.") 100 | funcName := cmd.StringOpt("n name", "package-func", "The name you want to assign to the function you are uploading, should be Amazon Resource Name (ARN) or unqualified.") 101 | funcDescription := cmd.StringOpt("d description", "", "A short, user-defined function description. Lambda does not use this value.") 102 | memorySize := cmd.IntOpt("m memsize", 128, "The amount of memory, in MB, your Lambda function is given. The value must be a multiple of 64MB.") 103 | role := cmd.StringOpt("r role", "arn:aws:iam::account-id:role/lambda_basic_execution", "The Amazon Resource Name (ARN) of the IAM role that Lambda assumes when it executes your function.") 104 | timeout := cmd.IntOpt("t timeout", 3, "The function execution time at which Lambda should terminate the function.") 105 | writeZip := cmd.StringOpt("w write-zip", "", "Path to write the produced .zip of an AWS Lambda function source.") 106 | dryRun := cmd.BoolOpt("dry", false, "Run in dry mode, do not actually upload anything, but all the processing will be done.") 107 | 108 | cmd.Action = func() { 109 | pwd, _ := os.Getwd() 110 | buildModuleBridge(filepath.Join(pwd, "_module_cgo"), *packagePath, *packageFunc) 111 | packageName := packageName(*packagePath) 112 | if *funcName == "package-func" { 113 | *funcName = fmt.Sprintf("%s-%s", packageName, *packageFunc) 114 | } 115 | module := getModuleSource(packageName, *packageFunc) 116 | modulePath := fmt.Sprintf("%s.py", *funcName) 117 | zip := makeZip(module, modulePath, "module.so", *additionalFiles...) 118 | if len(*writeZip) > 0 { 119 | if err := ioutil.WriteFile(*writeZip, zip, 0644); err != nil { 120 | log.Fatalln(err) 121 | } 122 | } 123 | 124 | if *dryRun { 125 | return 126 | } 127 | svc := lambda.New(session.New(&aws.Config{ 128 | Region: aws.String(*region), 129 | })) 130 | handler := fmt.Sprintf("%s.%s", *funcName, *packageFunc) 131 | cfg := &deploymentConfig{ 132 | HandlerName: handler, 133 | LambdaName: *funcName, 134 | LambdaDescription: *funcDescription, 135 | MemorySize: *memorySize, 136 | Role: *role, 137 | Timeout: *timeout, 138 | SourceZip: zip, 139 | } 140 | createFunction(svc, cfg, *region) 141 | } 142 | }) 143 | golambda.Command("update", "Updates source of an AWS Lambda function (specified by ID or NAME).", 144 | func(cmd *cli.Cmd) { 145 | cmd.Spec = "[OPTIONS] (ID | NAME) FUNC PACKAGE [FILES...]" 146 | idStr := cmd.StringArg("ID", "", "Function ID (as in `list` result).") 147 | cmd.StringArg("NAME", "", "Function NAME.") 148 | packageFunc := cmd.StringArg("FUNC", "", "Function name in Go package to be called from within Python wrapper.") 149 | packagePath := cmd.StringArg("PACKAGE", ".", "Fully qualified import path of a Go package.") 150 | additionalFiles := cmd.StringsArg("FILES", nil, "A list of additional static files to be included in archive.") 151 | writeZip := cmd.StringOpt("w write-zip", "", "Path to write the produced .zip of an AWS Lambda function source.") 152 | dryRun := cmd.BoolOpt("dry", false, "Run in dry mode, do not actually upload anything, but all the processing will be done.") 153 | 154 | cmd.Action = func() { 155 | svc := lambda.New(session.New(&aws.Config{ 156 | Region: aws.String(*region), 157 | })) 158 | id, _ := strconv.Atoi(*idStr) 159 | f := findFuction(svc, id, *idStr) 160 | 161 | pwd, _ := os.Getwd() 162 | buildModuleBridge(filepath.Join(pwd, "_module_cgo"), *packagePath, *packageFunc) 163 | packageName := packageName(*packagePath) 164 | module := getModuleSource(packageName, *packageFunc) 165 | modulePath := fmt.Sprintf("%s.py", *f.FunctionName) 166 | zip := makeZip(module, modulePath, "module.so", *additionalFiles...) 167 | if len(*writeZip) > 0 { 168 | if err := ioutil.WriteFile(*writeZip, zip, 0644); err != nil { 169 | log.Fatalln(err) 170 | } 171 | } 172 | if *dryRun { 173 | return 174 | } 175 | updateFunction(svc, *f.FunctionName, *region, zip) 176 | } 177 | }) 178 | if err := golambda.Run(os.Args); err != nil { 179 | log.Fatalln(err) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /misc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "crypto/rand" 7 | "fmt" 8 | "go/build" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "os/exec" 13 | "path/filepath" 14 | "strings" 15 | 16 | "github.com/jhoonb/archivex" 17 | ) 18 | 19 | func gopathMounts(dir string) (string, []string) { 20 | gopath := "/go" 21 | srcDirs := build.Default.SrcDirs() 22 | for i := range srcDirs { 23 | gopath += fmt.Sprintf(":%s/path%d", dir, i) 24 | } 25 | return gopath, srcDirs 26 | } 27 | 28 | func packageName(pkgImport string) (name string) { 29 | for _, src := range build.Default.SrcDirs() { 30 | pkg, err := build.Import(pkgImport, src, build.ImportComment) 31 | if err != nil || pkg.IsCommand() || pkg.Goroot { 32 | continue 33 | } 34 | name = pkg.Name 35 | return 36 | } 37 | log.Fatalln("package not found in $GOPATH:", pkgImport) 38 | return 39 | } 40 | 41 | func packageDir(pkgImport string) (dir string) { 42 | for _, src := range build.Default.SrcDirs() { 43 | pkg, err := build.Import(pkgImport, src, build.ImportComment) 44 | if err != nil || pkg.IsCommand() || pkg.Goroot { 45 | continue 46 | } 47 | dir = pkg.Dir 48 | return 49 | } 50 | log.Fatalln("package not found in $GOPATH:", pkgImport) 51 | return 52 | } 53 | 54 | func getExecPath(name string) string { 55 | out, err := exec.LookPath("docker") 56 | if err != nil { 57 | log.Fatalf("executable file %s not found in $PATH", name) 58 | return "" 59 | } 60 | return strings.TrimSpace(out) 61 | } 62 | 63 | func makeZip(main []byte, mainPath, libPath string, other ...string) []byte { 64 | buf := new(bytes.Buffer) 65 | zipper := &archivex.ZipFile{ 66 | Name: "source.zip", 67 | Writer: zip.NewWriter(buf), 68 | } 69 | if err := zipper.Add(mainPath, main); err != nil { 70 | log.Fatalln(err) 71 | } 72 | if err := zipper.AddFile(libPath); err != nil { 73 | log.Fatalf("error adding file '%s' to zip archive: %v", main, err) 74 | } 75 | for _, name := range other { 76 | info, err := os.Stat(name) 77 | if err != nil { 78 | log.Fatalln(err) 79 | } 80 | if info.IsDir() { 81 | err = zipper.AddAll(name, false) 82 | } else { 83 | err = zipper.AddFile(name) 84 | } 85 | if err != nil { 86 | log.Fatalln(err) 87 | } 88 | } 89 | zipper.Close() 90 | return buf.Bytes() 91 | } 92 | 93 | func getModuleSource(packageName, packageFunc string) []byte { 94 | buf := new(bytes.Buffer) 95 | fmt.Fprintln(buf, ` 96 | import module 97 | import json 98 | 99 | class Encoder(json.JSONEncoder): 100 | def default(self, o): 101 | if o.__class__.__name__ == 'CognitoIdentity': # very wow much reflection 102 | return dict(cognito_identity_id=o.cognito_identity_id, 103 | cognito_identity_pool_id=o.cognito_identity_pool_id) 104 | else: 105 | return o.__dict__ 106 | `) 107 | fmt.Fprintln(buf, "lambda_handler = module.lambda_handler()") 108 | fmt.Fprintf(buf, "def %s(event, context):\n", packageFunc) 109 | fmt.Fprintln(buf, " return lambda_handler(json.dumps(event), json.dumps(context, cls=Encoder))") 110 | return buf.Bytes() 111 | } 112 | 113 | func getTempDir() string { 114 | tag := make([]byte, 8) 115 | _, err := rand.Read(tag) 116 | if err != nil { 117 | return filepath.Join(os.TempDir(), "go-lambda") 118 | } 119 | return filepath.Join(os.TempDir(), fmt.Sprintf("go-lambda-%x", tag)) 120 | } 121 | 122 | func buildModuleBridge(tempDir, pkg, packageFunc string) { 123 | if err := os.MkdirAll(tempDir, 0755); err != nil { 124 | log.Fatalln(err) 125 | } 126 | defer os.RemoveAll(tempDir) 127 | 128 | err := ioutil.WriteFile(filepath.Join(tempDir, "module.c"), MustAsset("module/module.c"), 0644) 129 | if err != nil { 130 | log.Fatalln(err) 131 | } 132 | goAsset := MustAsset("module/module.go") 133 | funcRef := fmt.Sprintf(`&lambda.%s`, strings.Title(packageFunc)) 134 | goAsset = bytes.Replace(goAsset, []byte(`&lambda.Handler`), []byte(funcRef), -1) 135 | 136 | if isNative() { 137 | pkgRef := fmt.Sprintf(`lambda "%s"`, pkg) 138 | goAsset = bytes.Replace(goAsset, []byte(`lambda "in"`), []byte(pkgRef), -1) 139 | } 140 | 141 | err = ioutil.WriteFile(filepath.Join(tempDir, "module.go"), goAsset, 0644) 142 | if err != nil { 143 | log.Fatalln(err) 144 | } 145 | 146 | runBuild(pkg, tempDir) 147 | } 148 | -------------------------------------------------------------------------------- /module/module.c: -------------------------------------------------------------------------------- 1 | #ifdef _POSIX_C_SOURCE 2 | #undef _POSIX_C_SOURCE 3 | #endif 4 | 5 | #include "Python.h" 6 | #include "structmember.h" 7 | #include "memoryobject.h" 8 | #include "bufferobject.h" 9 | 10 | #if PY_VERSION_HEX > 0x03000000 11 | #error "python 3 is not yet supported" 12 | #endif 13 | 14 | typedef int lambda_handle; 15 | 16 | typedef struct { 17 | PyObject_HEAD 18 | lambda_handle handle; 19 | } lambda_handler; 20 | 21 | extern lambda_handle get_lambda_handle(); 22 | 23 | static PyObject* 24 | lambda_handler_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { 25 | lambda_handler *self; 26 | self = (lambda_handler *)type->tp_alloc(type, 0); 27 | self->handle = get_lambda_handle(); 28 | return (PyObject*)self; 29 | } 30 | 31 | static void 32 | lambda_handler_dealloc(lambda_handler *self) { 33 | self->ob_type->tp_free((PyObject*)self); 34 | } 35 | 36 | struct lambda_handle_call_return { 37 | char* data; 38 | size_t size; 39 | }; 40 | 41 | extern struct lambda_handle_call_return lambda_handle_call(lambda_handle h, char* event, char* context); 42 | 43 | static PyObject * 44 | lambda_handler_tp_call(lambda_handler *self, PyObject *args, PyObject *other) { 45 | char *event; 46 | char *context; 47 | if (!PyArg_ParseTuple(args, "ss", &event, &context)) { 48 | return NULL; 49 | } 50 | struct lambda_handle_call_return ret; 51 | ret = lambda_handle_call(self->handle, event, context); 52 | PyObject *result = PyString_FromStringAndSize(ret.data, ret.size); 53 | return result; 54 | } 55 | 56 | static PyTypeObject lambda_handler_type = { 57 | PyObject_HEAD_INIT(NULL) 58 | 0, /*ob_size*/ 59 | "lambda_handler", /*tp_name*/ 60 | sizeof(lambda_handler), /*tp_basicsize*/ 61 | 0, /*tp_itemsize*/ 62 | (destructor)lambda_handler_dealloc, /*tp_dealloc*/ 63 | 0, /*tp_print*/ 64 | 0, /*tp_getattr*/ 65 | 0, /*tp_setattr*/ 66 | 0, /*tp_compare*/ 67 | 0, /*tp_repr*/ 68 | 0, /*tp_as_number*/ 69 | 0, /*tp_as_sequence*/ 70 | 0, /*tp_as_mapping*/ 71 | 0, /*tp_hash */ 72 | (ternaryfunc)lambda_handler_tp_call, /*tp_call*/ 73 | 0, /*tp_str*/ 74 | 0, /*tp_getattro*/ 75 | 0, /*tp_setattro*/ 76 | 0, /*tp_as_buffer*/ 77 | Py_TPFLAGS_DEFAULT, /*tp_flags*/ 78 | "", /* tp_doc */ 79 | }; 80 | 81 | PyMODINIT_FUNC 82 | initmodule(void) 83 | { 84 | PyObject *module; 85 | 86 | lambda_handler_type.tp_new = lambda_handler_new; 87 | if (PyType_Ready(&lambda_handler_type) < 0) { 88 | return; 89 | } else { 90 | Py_INCREF(&lambda_handler_type); 91 | } 92 | 93 | module = Py_InitModule("module", NULL); 94 | PyModule_AddObject(module, "lambda_handler", (PyObject*)&lambda_handler_type); 95 | } 96 | 97 | -------------------------------------------------------------------------------- /module/module.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //#cgo pkg-config: python-2.7 --cflags --libs 4 | //#include 5 | import "C" 6 | 7 | import ( 8 | "sync" 9 | "sync/atomic" 10 | "unsafe" 11 | 12 | lambda "in" 13 | ) 14 | 15 | //export lambda_handle 16 | type lambda_handle int64 17 | 18 | //export get_lambda_handle 19 | func get_lambda_handle() lambda_handle { 20 | handle := lambda_handle(atomic.AddInt64(&bridgeHandlePool, 1)) 21 | bridgeMux.Lock() 22 | bridgeMap[handle] = unsafe.Pointer(&lambda.Handler) 23 | bridgeMux.Unlock() 24 | return handle 25 | } 26 | 27 | //export lambda_handle_call 28 | func lambda_handle_call(handle lambda_handle, event, context *C.char) (*C.char, C.size_t) { 29 | bridgeMux.RLock() 30 | fn := *(*bridge)(bridgeMap[handle]) 31 | bridgeMux.RUnlock() 32 | str, size := fn(event, context) 33 | return str, size 34 | } 35 | 36 | type bridge func(event, context *C.char) (result *C.char, size C.size_t) 37 | 38 | var ( 39 | bridgeHandlePool int64 40 | bridgeMap = make(map[lambda_handle]unsafe.Pointer, 10) 41 | bridgeMux = new(sync.RWMutex) 42 | ) 43 | 44 | func main() {} 45 | --------------------------------------------------------------------------------