feat(aws): support lambda (#334)
This commit is contained in:
parent
b0260af17b
commit
d7ce3c077c
3
Makefile
3
Makefile
|
@ -172,6 +172,9 @@ build_linux_arm64:
|
|||
build_linux_arm:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/arm/$(DEPLOY_IMAGE)
|
||||
|
||||
build_linux_lambda:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags 'lambda' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/lambda/$(DEPLOY_IMAGE)
|
||||
|
||||
docker_image:
|
||||
docker build -t $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE) -f Dockerfile .
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// +build lambda
|
||||
|
||||
package gorush
|
||||
|
||||
import (
|
||||
"github.com/apex/gateway"
|
||||
)
|
||||
|
||||
// RunHTTPServer provide run http or https protocol.
|
||||
func RunHTTPServer() error {
|
||||
if !PushConf.Core.Enabled {
|
||||
LogAccess.Debug("httpd server is disabled.")
|
||||
return nil
|
||||
}
|
||||
|
||||
LogAccess.Debug("HTTPD server is running on " + PushConf.Core.Port + " port.")
|
||||
|
||||
return gateway.ListenAndServe(PushConf.Core.Address+":"+PushConf.Core.Port, routerEngine())
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// +build !windows
|
||||
// +build !windows,!lambda
|
||||
|
||||
package gorush
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build windows
|
||||
// +build windows,!lambda
|
||||
|
||||
package gorush
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<img src="http://tjholowaychuk.com:6000/svg/title/APEX/GATEWAY">
|
||||
|
||||
Package gateway provides a drop-in replacement for net/http's `ListenAndServe` for use in AWS Lambda & API Gateway, simply swap it out for `gateway.ListenAndServe`. Extracted from [Up](https://github.com/apex/up) which provides additional middleware features and operational functionality.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/apex/gateway"
|
||||
)
|
||||
|
||||
func main() {
|
||||
addr := ":" + os.Getenv("PORT")
|
||||
http.HandleFunc("/", hello)
|
||||
log.Fatal(gateway.ListenAndServe(addr, nil))
|
||||
}
|
||||
|
||||
func hello(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "Hello World from Go")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/apex/up-go?status.svg)](https://godoc.org/github.com/apex/gateway)
|
||||
![](https://img.shields.io/badge/license-MIT-blue.svg)
|
||||
![](https://img.shields.io/badge/status-stable-green.svg)
|
||||
|
||||
<a href="https://apex.sh"><img src="http://tjholowaychuk.com:6000/svg/sponsor"></a>
|
|
@ -0,0 +1,33 @@
|
|||
// Package gateway provides a drop-in replacement for net/http.ListenAndServe for use in AWS Lambda & API Gateway.
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
"github.com/aws/aws-lambda-go/lambda"
|
||||
)
|
||||
|
||||
// ListenAndServe is a drop-in replacement for
|
||||
// http.ListenAndServe for use within AWS Lambda.
|
||||
//
|
||||
// ListenAndServe always returns a non-nil error.
|
||||
func ListenAndServe(addr string, h http.Handler) error {
|
||||
if h == nil {
|
||||
h = http.DefaultServeMux
|
||||
}
|
||||
|
||||
lambda.Start(func(ctx context.Context, e events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
|
||||
r, err := NewRequest(ctx, e)
|
||||
if err != nil {
|
||||
return events.APIGatewayProxyResponse{}, err
|
||||
}
|
||||
|
||||
w := NewResponse()
|
||||
h.ServeHTTP(w, r)
|
||||
return w.End(), nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// NewRequest returns a new http.Request from the given Lambda event.
|
||||
func NewRequest(ctx context.Context, e events.APIGatewayProxyRequest) (*http.Request, error) {
|
||||
// path
|
||||
u, err := url.Parse(e.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parsing path")
|
||||
}
|
||||
|
||||
// querystring
|
||||
q := u.Query()
|
||||
for k, v := range e.QueryStringParameters {
|
||||
q.Set(k, v)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
// base64 encoded body
|
||||
body := e.Body
|
||||
if e.IsBase64Encoded {
|
||||
b, err := base64.StdEncoding.DecodeString(body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decoding base64 body")
|
||||
}
|
||||
body = string(b)
|
||||
}
|
||||
|
||||
// new request
|
||||
req, err := http.NewRequest(e.HTTPMethod, u.String(), strings.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating request")
|
||||
}
|
||||
|
||||
// remote addr
|
||||
req.RemoteAddr = e.RequestContext.Identity.SourceIP
|
||||
|
||||
// header fields
|
||||
for k, v := range e.Headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
// content-length
|
||||
if req.Header.Get("Content-Length") == "" && body != "" {
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(body)))
|
||||
}
|
||||
|
||||
// custom fields
|
||||
req.Header.Set("X-Request-Id", e.RequestContext.RequestID)
|
||||
req.Header.Set("X-Stage", e.RequestContext.Stage)
|
||||
|
||||
// xray support
|
||||
if traceID := ctx.Value("x-amzn-trace-id"); traceID != nil {
|
||||
req.Header.Set("X-Amzn-Trace-Id", fmt.Sprintf("%v", traceID))
|
||||
}
|
||||
|
||||
// host
|
||||
req.URL.Host = req.Header.Get("Host")
|
||||
req.Host = req.URL.Host
|
||||
|
||||
return req, nil
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package gateway
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
)
|
||||
|
||||
// ResponseWriter implements the http.ResponseWriter interface
|
||||
// in order to support the API Gateway Lambda HTTP "protocol".
|
||||
type ResponseWriter struct {
|
||||
out events.APIGatewayProxyResponse
|
||||
buf bytes.Buffer
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
// NewResponse returns a new response writer to capture http output.
|
||||
func NewResponse() *ResponseWriter {
|
||||
return &ResponseWriter{}
|
||||
}
|
||||
|
||||
// Header implementation.
|
||||
func (w *ResponseWriter) Header() http.Header {
|
||||
if w.header == nil {
|
||||
w.header = make(http.Header)
|
||||
}
|
||||
|
||||
return w.header
|
||||
}
|
||||
|
||||
// Write implementation.
|
||||
func (w *ResponseWriter) Write(b []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// TODO: HEAD? ignore
|
||||
|
||||
return w.buf.Write(b)
|
||||
}
|
||||
|
||||
// WriteHeader implementation.
|
||||
func (w *ResponseWriter) WriteHeader(status int) {
|
||||
if w.wroteHeader {
|
||||
return
|
||||
}
|
||||
|
||||
if w.Header().Get("Content-Type") == "" {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf8")
|
||||
}
|
||||
|
||||
w.out.StatusCode = status
|
||||
|
||||
h := make(map[string]string)
|
||||
|
||||
for k, v := range w.Header() {
|
||||
if len(v) > 0 {
|
||||
h[k] = v[len(v)-1]
|
||||
}
|
||||
}
|
||||
|
||||
w.out.Headers = h
|
||||
w.wroteHeader = true
|
||||
}
|
||||
|
||||
// End the request.
|
||||
func (w *ResponseWriter) End() events.APIGatewayProxyResponse {
|
||||
w.out.IsBase64Encoded = isBinary(w.header)
|
||||
|
||||
if w.out.IsBase64Encoded {
|
||||
w.out.Body = base64.StdEncoding.EncodeToString(w.buf.Bytes())
|
||||
} else {
|
||||
w.out.Body = w.buf.String()
|
||||
}
|
||||
|
||||
return w.out
|
||||
}
|
||||
|
||||
// isBinary returns true if the response reprensents binary.
|
||||
func isBinary(h http.Header) bool {
|
||||
if !isTextMime(h.Get("Content-Type")) {
|
||||
return true
|
||||
}
|
||||
|
||||
if h.Get("Content-Encoding") == "gzip" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isTextMime returns true if the content type represents textual data.
|
||||
func isTextMime(kind string) bool {
|
||||
switch {
|
||||
case strings.HasSuffix(kind, "svg+xml"):
|
||||
return true
|
||||
case strings.HasPrefix(kind, "text/"):
|
||||
return true
|
||||
case strings.HasPrefix(kind, "application/") && strings.HasSuffix(kind, "json"):
|
||||
return true
|
||||
case strings.HasPrefix(kind, "application/") && strings.HasSuffix(kind, "xml"):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
|
||||
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.
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
MIT No Attribution
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
software and associated documentation files (the "Software"), to deal in the Software
|
||||
without restriction, including without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
Lambda functions are made available under a modified MIT license.
|
||||
See LICENSE-LAMBDACODE for details.
|
||||
|
||||
The remainder of the project is made available under the terms of the
|
||||
Apache License, version 2.0. See LICENSE for details.
|
|
@ -0,0 +1,21 @@
|
|||
# Overview
|
||||
|
||||
This package provides input types for Lambda functions that process AWS events.
|
||||
|
||||
# Samples
|
||||
|
||||
[API Gateway](README_ApiGatewayEvent.md)
|
||||
|
||||
[Cognito Events](README_Cognito.md)
|
||||
|
||||
[Config Events](README_Config.md)
|
||||
|
||||
[DynamoDB Events](README_DynamoDB.md)
|
||||
|
||||
[Kinesis Events](README_Kinesis.md)
|
||||
|
||||
[Kinesis Firehose Events](README_KinesisFirehose.md)
|
||||
|
||||
[S3 Events](README_S3.md)
|
||||
|
||||
[SNS Events](README_SNS.md)
|
36
vendor/github.com/aws/aws-lambda-go/events/README_ApiGatewayEvent.md
generated
vendored
Normal file
36
vendor/github.com/aws/aws-lambda-go/events/README_ApiGatewayEvent.md
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Overview
|
||||
|
||||
API Gateway events consist of a request that was routed to a Lambda function by API Gateway. When this happens, API Gateway expects the result of the function to be the response that API Gateway should respond with.
|
||||
|
||||
# Sample Function
|
||||
|
||||
The following is a sample class and Lambda function that receives Amazon API Gateway event record data as an input, writes some of the record data to CloudWatch Logs, and responds with a 200 status and the same body as the request. (Note that by default anything written to Console will be logged as CloudWatch Logs events.)
|
||||
|
||||
```go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
"github.com/aws/aws-lambda-go/lambda"
|
||||
)
|
||||
|
||||
func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
|
||||
fmt.Printf("Processing request data for request %s.\n", request.RequestContext.RequestID)
|
||||
fmt.Printf("Body size = %d.\n", len(request.Body))
|
||||
|
||||
fmt.Println("Headers:")
|
||||
for key, value := range request.Headers {
|
||||
fmt.Printf(" %s: %s\n", key, value)
|
||||
}
|
||||
|
||||
return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
lambda.Start(handleRequest)
|
||||
}
|
||||
```
|
|
@ -0,0 +1,22 @@
|
|||
# Sample Function
|
||||
|
||||
The following is a sample Lambda function that receives Amazon Cognito event record data as an input and writes some of the record data to CloudWatch Logs. (Note that by default anything written to Console will be logged as CloudWatch Logs events.)
|
||||
|
||||
```go
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
)
|
||||
|
||||
func handleRequest(ctx context.Context, cognitoEvent events.CognitoEvent) {
|
||||
for datasetName, datasetRecord := range cognitoEvent.DatasetRecords {
|
||||
fmt.Printf("[%s -- %s] %s -> %s -> %s \n",
|
||||
cognitoEvent.EventType,
|
||||
datasetName,
|
||||
datasetRecord.OldValue,
|
||||
datasetRecord.Op,
|
||||
datasetRecord.NewValue)
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,18 @@
|
|||
# Sample Function
|
||||
|
||||
The following is a sample Lambda function that receives Amazon Config event record data as an input and writes some of the record data to CloudWatch Logs. (Note that by default anything written to Console will be logged as CloudWatch Logs events.)
|
||||
|
||||
```go
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
)
|
||||
|
||||
func handleRequest(ctx context.Context, configEvent events.ConfigEvent) {
|
||||
fmt.Printf("AWS Config rule: %s\n", configEvent.ConfigRuleName)
|
||||
fmt.Printf("Invoking event JSON: %s\n", configEvent.InvokingEvent)
|
||||
fmt.Printf("Event version: %s\n", configEvent.Version)
|
||||
}
|
||||
|
||||
```
|
|
@ -0,0 +1,79 @@
|
|||
# Sample Function
|
||||
|
||||
The following is a sample Lambda function that receives DynamoDB event data as input and writes some of the record data to CloudWatch Logs. (Note that by default anything written to Console will be logged as CloudWatch Logs.)
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
)
|
||||
|
||||
func handleRequest(ctx context.Context, e events.DynamoDBEvent) {
|
||||
|
||||
for _, record := range e.Records {
|
||||
fmt.Printf("Processing request data for event ID %s, type %s.\n", record.EventID, record.EventName)
|
||||
|
||||
// Print new values for attributes of type String
|
||||
for name, value := range record.Change.NewImage {
|
||||
if value.DataType() == events.DataTypeString {
|
||||
fmt.Printf("Attribute name: %s, value: %s\n", name, value.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Reading attribute values
|
||||
|
||||
Stream notifications are delivered to the Lambda handler whenever data in the DynamoDB table is modified.
|
||||
Depending on the Stream settings, a StreamRecord may contain the following data:
|
||||
|
||||
* Keys: key attributes of the modified item.
|
||||
* NewImage: the entire item, as it appears after it was modified.
|
||||
* OldImage: the entire item, as it appeared before it was modified.
|
||||
|
||||
The values for the attributes can be accessed using the AttributeValue type. For each type
|
||||
supported natively by DynamoDB, there is a corresponding accessor method:
|
||||
|
||||
DynamoDB type | AttributeValue accessor method | Return type | DataType constant
|
||||
---------------|--------------------------------|---------------------------|------------------
|
||||
B (Binary) | Binary() | []byte | DataTypeBinary
|
||||
BOOL (Boolean) | Boolean() | bool | DataTypeBoolean
|
||||
BS (Binary Set)| BinarySet() | [][]byte | DataTypeBinarySet
|
||||
L (List) | List() | []AttributeValue | DataTypeList
|
||||
M (Map) | Map() | map[string]AttributeValue | DataTypeMap
|
||||
N (Number) | Number() / Integer() / Float() | string / int64 / float64 | DataTypeNumber
|
||||
NS (Number Set)| NumberSet() | []string | DataTypeNumberSet
|
||||
NULL (Null) | IsNull() | bool | DataTypeNull
|
||||
S (String) | String() | string | DataTypeString
|
||||
SS (String Set)| StringSet() | []string | DataTypeStringSet
|
||||
|
||||
Calling the accessor method for the incorrect type will result in a panic. If the type needs to
|
||||
be discovered in runtime, the method DataType() can be used in order to determine the correct accessor.
|
||||
|
||||
More information about DynamoDB data types can be seen [in this documentation](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html).
|
||||
|
||||
The following example reads values of attributes name and age, for which types are known to be String and Number:
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
)
|
||||
|
||||
func handleRequest(ctx context.Context, e events.DynamoDBEvent) {
|
||||
|
||||
for _, record := range e.Records {
|
||||
fmt.Printf("Processing request data for event ID %s, type %s.\n", record.EventID, record.EventName)
|
||||
|
||||
// Print new values for attributes name and age
|
||||
name := record.Change.NewImage["name"].String()
|
||||
age, _ := record.Change.NewImage["age"].Integer()
|
||||
|
||||
fmt.Printf("Name: %s, age: %d\n", name, age)
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,21 @@
|
|||
# Sample Function
|
||||
|
||||
The following is a sample class and Lambda function that receives Amazon Kinesis event record data as an input and writes some of the record data to CloudWatch Logs. (Note that by default anything written to Console will be logged as CloudWatch Logs events.)
|
||||
|
||||
```go
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"github.com/aws/aws-lambda-go/events")
|
||||
|
||||
func handler(ctx context.Context, kinesisEvent events.KinesisEvent) {
|
||||
for _, record := range kinesisEvent.Records {
|
||||
kinesisRecord := record.Kinesis
|
||||
dataBytes := kinesisRecordData.Data
|
||||
dataText := string(dataBytes)
|
||||
|
||||
fmt.Printf("%s Data = %s \n", record.EventName, dataText)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
36
vendor/github.com/aws/aws-lambda-go/events/README_KinesisFirehose.md
generated
vendored
Normal file
36
vendor/github.com/aws/aws-lambda-go/events/README_KinesisFirehose.md
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Sample Function
|
||||
|
||||
The following is a sample Lambda function that transforms Kinesis Firehose records by doing a ToUpper on the data.
|
||||
|
||||
```go
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
)
|
||||
|
||||
func handleRequest(evnt events.KinesisFirehoseEvent) events.KinesisFirehoseResponse {
|
||||
|
||||
fmt.Printf("InvocationId: %s\n", evnt.InvocationId)
|
||||
fmt.Printf("DeliveryStreamArn: %s\n", evnt.DeliveryStreamArn)
|
||||
fmt.Printf("Region: %s\n", evnt.Region)
|
||||
|
||||
var response events.KinesisFirehoseResponse
|
||||
|
||||
for _, record := range evnt.Records {
|
||||
fmt.Printf("RecordId: %s\n", record.RecordId)
|
||||
fmt.Printf("ApproximateArrivalTimestamp: %s\n", record.ApproximateArrivalTimestamp)
|
||||
|
||||
// Transform data: ToUpper the data
|
||||
var transformedRecord kinesisfhevents.FirehoseResponseRecord
|
||||
transformedRecord.RecordId = record.RecordId
|
||||
transformedRecord.Result = kinesisfhevents.TransformedStateOk
|
||||
transformedRecord.Data = strings.ToUpper(string(record.Data))
|
||||
|
||||
response.Records = append(response.Records, transformedRecord)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
```
|
|
@ -0,0 +1,18 @@
|
|||
# Sample Function
|
||||
|
||||
The following is a sample class and Lambda function that receives Amazon S3 event record data as an input and writes some of the record data to CloudWatch Logs. (Note that by default anything written to Console will be logged as CloudWatch Logs events.)
|
||||
|
||||
```go
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"github.com/aws/aws-lambda-go/events")
|
||||
|
||||
func handler(ctx context.Context, s3Event events.S3Event) {
|
||||
for _, record := range s3Event.Records {
|
||||
s3 := record.S3
|
||||
fmt.Printf("[%s - %s] Bucket = %s, Key = %s \n", record.EventSource, record.EventTime, s3.Bucket.Name, s3.Object.Key)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
# Sample Function
|
||||
|
||||
The following is a sample class and Lambda function that receives Amazon SNS event record data as input, writes some of the record data to CloudWatch Logs, and responds with a 200 status and the same body as the request. (Note that by default anything written to Console will be logged as CloudWatch Logs events.)
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-lambda-go/events"
|
||||
)
|
||||
|
||||
func handler(ctx context.Context, snsEvent events.SNSEvent) {
|
||||
for _, record := range snsEvent.Records {
|
||||
snsRecord := record.SNS
|
||||
|
||||
fmt.Printf("[%s %s] Message = %s \n", record.EventSource, snsRecord.Timestamp, snsRecord.Message)
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package events
|
||||
|
||||
// APIGatewayProxyRequest contains data coming from the API Gateway proxy
|
||||
type APIGatewayProxyRequest struct {
|
||||
Resource string `json:"resource"` // The resource path defined in API Gateway
|
||||
Path string `json:"path"` // The url path for the caller
|
||||
HTTPMethod string `json:"httpMethod"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
QueryStringParameters map[string]string `json:"queryStringParameters"`
|
||||
PathParameters map[string]string `json:"pathParameters"`
|
||||
StageVariables map[string]string `json:"stageVariables"`
|
||||
RequestContext APIGatewayProxyRequestContext `json:"requestContext"`
|
||||
Body string `json:"body"`
|
||||
IsBase64Encoded bool `json:"isBase64Encoded,omitempty"`
|
||||
}
|
||||
|
||||
// APIGatewayProxyResponse configures the response to be returned by API Gateway for the request
|
||||
type APIGatewayProxyResponse struct {
|
||||
StatusCode int `json:"statusCode"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Body string `json:"body"`
|
||||
IsBase64Encoded bool `json:"isBase64Encoded,omitempty"`
|
||||
}
|
||||
|
||||
// APIGatewayProxyRequestContext contains the information to identify the AWS account and resources invoking the
|
||||
// Lambda function. It also includes Cognito identity information for the caller.
|
||||
type APIGatewayProxyRequestContext struct {
|
||||
AccountID string `json:"accountId"`
|
||||
ResourceID string `json:"resourceId"`
|
||||
Stage string `json:"stage"`
|
||||
RequestID string `json:"requestId"`
|
||||
Identity APIGatewayRequestIdentity `json:"identity"`
|
||||
ResourcePath string `json:"resourcePath"`
|
||||
Authorizer map[string]interface{} `json:"authorizer"`
|
||||
HTTPMethod string `json:"httpMethod"`
|
||||
APIID string `json:"apiId"` // The API Gateway rest API Id
|
||||
}
|
||||
|
||||
// APIGatewayRequestIdentity contains identity information for the request caller.
|
||||
type APIGatewayRequestIdentity struct {
|
||||
CognitoIdentityPoolID string `json:"cognitoIdentityPoolId"`
|
||||
AccountID string `json:"accountId"`
|
||||
CognitoIdentityID string `json:"cognitoIdentityId"`
|
||||
Caller string `json:"caller"`
|
||||
APIKey string `json:"apiKey"`
|
||||
SourceIP string `json:"sourceIp"`
|
||||
CognitoAuthenticationType string `json:"cognitoAuthenticationType"`
|
||||
CognitoAuthenticationProvider string `json:"cognitoAuthenticationProvider"`
|
||||
UserArn string `json:"userArn"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
User string `json:"user"`
|
||||
}
|
||||
|
||||
// APIGatewayCustomAuthorizerContext represents the expected format of an API Gateway custom authorizer response.
|
||||
// Deprecated. Code should be updated to use the Authorizer map from APIGatewayRequestIdentity. Ex: Authorizer["principalId"]
|
||||
type APIGatewayCustomAuthorizerContext struct {
|
||||
PrincipalID *string `json:"principalId"`
|
||||
StringKey *string `json:"stringKey,omitempty"`
|
||||
NumKey *int `json:"numKey,omitempty"`
|
||||
BoolKey *bool `json:"boolKey,omitempty"`
|
||||
}
|
|
@ -0,0 +1,457 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// DynamoDBAttributeValue provides convenient access for a value stored in DynamoDB.
|
||||
// For more information, please see http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html
|
||||
type DynamoDBAttributeValue struct {
|
||||
value anyValue
|
||||
dataType DynamoDBDataType
|
||||
}
|
||||
|
||||
// Binary provides access to an attribute of type Binary.
|
||||
// Method panics if the attribute is not of type Binary.
|
||||
func (av DynamoDBAttributeValue) Binary() []byte {
|
||||
av.ensureType(DataTypeBinary)
|
||||
return av.value.([]byte)
|
||||
}
|
||||
|
||||
// Boolean provides access to an attribute of type Boolean.
|
||||
// Method panics if the attribute is not of type Boolean.
|
||||
func (av DynamoDBAttributeValue) Boolean() bool {
|
||||
av.ensureType(DataTypeBoolean)
|
||||
return av.value.(bool)
|
||||
}
|
||||
|
||||
// BinarySet provides access to an attribute of type Binary Set.
|
||||
// Method panics if the attribute is not of type BinarySet.
|
||||
func (av DynamoDBAttributeValue) BinarySet() [][]byte {
|
||||
av.ensureType(DataTypeBinarySet)
|
||||
return av.value.([][]byte)
|
||||
}
|
||||
|
||||
// List provides access to an attribute of type List. Each element
|
||||
// of the list is an DynamoDBAttributeValue itself.
|
||||
// Method panics if the attribute is not of type List.
|
||||
func (av DynamoDBAttributeValue) List() []DynamoDBAttributeValue {
|
||||
av.ensureType(DataTypeList)
|
||||
return av.value.([]DynamoDBAttributeValue)
|
||||
}
|
||||
|
||||
// Map provides access to an attribute of type Map. They Keys are strings
|
||||
// and the values are DynamoDBAttributeValue instances.
|
||||
// Method panics if the attribute is not of type Map.
|
||||
func (av DynamoDBAttributeValue) Map() map[string]DynamoDBAttributeValue {
|
||||
av.ensureType(DataTypeMap)
|
||||
return av.value.(map[string]DynamoDBAttributeValue)
|
||||
}
|
||||
|
||||
// Number provides access to an attribute of type Number.
|
||||
// DynamoDB sends the values as strings. For convenience please see also
|
||||
// the methods Integer() and Float().
|
||||
// Method panics if the attribute is not of type Number.
|
||||
func (av DynamoDBAttributeValue) Number() string {
|
||||
av.ensureType(DataTypeNumber)
|
||||
return av.value.(string)
|
||||
}
|
||||
|
||||
// Integer provides access to an attribute of type Number.
|
||||
// DynamoDB sends the values as strings. For convenience this method
|
||||
// provides conversion to int. If the value cannot be represented by
|
||||
// a signed integer, err.Err = ErrRange and the returned value is the maximum magnitude integer
|
||||
// of an int64 of the appropriate sign.
|
||||
// Method panics if the attribute is not of type Number.
|
||||
func (av DynamoDBAttributeValue) Integer() (int64, error) {
|
||||
s, err := strconv.ParseFloat(av.Number(), 64)
|
||||
return int64(s), err
|
||||
}
|
||||
|
||||
// Float provides access to an attribute of type Number.
|
||||
// DynamoDB sends the values as strings. For convenience this method
|
||||
// provides conversion to float64.
|
||||
// The returned value is the nearest floating point number rounded using IEEE754 unbiased rounding.
|
||||
// If the number is more than 1/2 ULP away from the largest floating point number of the given size,
|
||||
// the value returned is ±Inf, err.Err = ErrRange.
|
||||
// Method panics if the attribute is not of type Number.
|
||||
func (av DynamoDBAttributeValue) Float() (float64, error) {
|
||||
s, err := strconv.ParseFloat(av.Number(), 64)
|
||||
return s, err
|
||||
}
|
||||
|
||||
// NumberSet provides access to an attribute of type Number Set.
|
||||
// DynamoDB sends the numbers as strings.
|
||||
// Method panics if the attribute is not of type Number.
|
||||
func (av DynamoDBAttributeValue) NumberSet() []string {
|
||||
av.ensureType(DataTypeNumberSet)
|
||||
return av.value.([]string)
|
||||
}
|
||||
|
||||
// String provides access to an attribute of type String.
|
||||
// Method panics if the attribute is not of type String.
|
||||
func (av DynamoDBAttributeValue) String() string {
|
||||
av.ensureType(DataTypeString)
|
||||
return av.value.(string)
|
||||
}
|
||||
|
||||
// StringSet provides access to an attribute of type String Set.
|
||||
// Method panics if the attribute is not of type String Set.
|
||||
func (av DynamoDBAttributeValue) StringSet() []string {
|
||||
av.ensureType(DataTypeStringSet)
|
||||
return av.value.([]string)
|
||||
}
|
||||
|
||||
// IsNull returns true if the attribute is of type Null.
|
||||
func (av DynamoDBAttributeValue) IsNull() bool {
|
||||
return av.value == nil
|
||||
}
|
||||
|
||||
// DataType provides access to the DynamoDB type of the attribute
|
||||
func (av DynamoDBAttributeValue) DataType() DynamoDBDataType {
|
||||
return av.dataType
|
||||
}
|
||||
|
||||
// NewStringAttribute creates an DynamoDBAttributeValue containing a String
|
||||
func NewStringAttribute(value string) DynamoDBAttributeValue {
|
||||
var av DynamoDBAttributeValue
|
||||
av.value = value
|
||||
av.dataType = DataTypeString
|
||||
return av
|
||||
}
|
||||
|
||||
// DynamoDBDataType specifies the type supported natively by DynamoDB for an attribute
|
||||
type DynamoDBDataType int
|
||||
|
||||
const (
|
||||
DataTypeBinary DynamoDBDataType = iota
|
||||
DataTypeBoolean
|
||||
DataTypeBinarySet
|
||||
DataTypeList
|
||||
DataTypeMap
|
||||
DataTypeNumber
|
||||
DataTypeNumberSet
|
||||
DataTypeNull
|
||||
DataTypeString
|
||||
DataTypeStringSet
|
||||
)
|
||||
|
||||
type anyValue interface{}
|
||||
|
||||
// UnsupportedDynamoDBTypeError is the error returned when trying to unmarshal a DynamoDB Attribute type not recognized by this library
|
||||
type UnsupportedDynamoDBTypeError struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
func (e UnsupportedDynamoDBTypeError) Error() string {
|
||||
return fmt.Sprintf("unsupported DynamoDB attribute type, %v", e.Type)
|
||||
}
|
||||
|
||||
// IncompatibleDynamoDBTypeError is the error passed in a panic when calling an accessor for an incompatible type
|
||||
type IncompatibleDynamoDBTypeError struct {
|
||||
Requested DynamoDBDataType
|
||||
Actual DynamoDBDataType
|
||||
}
|
||||
|
||||
func (e IncompatibleDynamoDBTypeError) Error() string {
|
||||
return fmt.Sprintf("accessor called for incompatible type, requested type %v but actual type was %v", e.Requested, e.Actual)
|
||||
}
|
||||
|
||||
func (av *DynamoDBAttributeValue) ensureType(expectedType DynamoDBDataType) {
|
||||
if av.dataType != expectedType {
|
||||
panic(IncompatibleDynamoDBTypeError{Requested: expectedType, Actual: av.dataType})
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements custom marshaling to be used by the standard json/encoding package
|
||||
func (av DynamoDBAttributeValue) MarshalJSON() ([]byte, error) {
|
||||
|
||||
var buff bytes.Buffer
|
||||
var err error
|
||||
var b []byte
|
||||
|
||||
switch av.dataType {
|
||||
case DataTypeBinary:
|
||||
buff.WriteString(`{ "B":`)
|
||||
b, err = json.Marshal(av.value.([]byte))
|
||||
buff.Write(b)
|
||||
|
||||
case DataTypeBoolean:
|
||||
buff.WriteString(`{ "BOOL":`)
|
||||
b, err = json.Marshal(av.value.(bool))
|
||||
buff.Write(b)
|
||||
|
||||
case DataTypeBinarySet:
|
||||
buff.WriteString(`{ "BS":`)
|
||||
b, err = json.Marshal(av.value.([][]byte))
|
||||
buff.Write(b)
|
||||
|
||||
case DataTypeList:
|
||||
buff.WriteString(`{ "L":`)
|
||||
b, err = json.Marshal(av.value.([]DynamoDBAttributeValue))
|
||||
buff.Write(b)
|
||||
|
||||
case DataTypeMap:
|
||||
buff.WriteString(`{ "M":`)
|
||||
b, err = json.Marshal(av.value.(map[string]DynamoDBAttributeValue))
|
||||
buff.Write(b)
|
||||
|
||||
case DataTypeNumber:
|
||||
buff.WriteString(`{ "N":`)
|
||||
b, err = json.Marshal(av.value.(string))
|
||||
buff.Write(b)
|
||||
|
||||
case DataTypeNumberSet:
|
||||
buff.WriteString(`{ "NS":`)
|
||||
b, err = json.Marshal(av.value.([]string))
|
||||
buff.Write(b)
|
||||
|
||||
case DataTypeNull:
|
||||
buff.WriteString(`{ "NULL": true `)
|
||||
|
||||
case DataTypeString:
|
||||
buff.WriteString(`{ "S":`)
|
||||
b, err = json.Marshal(av.value.(string))
|
||||
buff.Write(b)
|
||||
|
||||
case DataTypeStringSet:
|
||||
buff.WriteString(`{ "SS":`)
|
||||
b, err = json.Marshal(av.value.([]string))
|
||||
buff.Write(b)
|
||||
}
|
||||
|
||||
buff.WriteString(`}`)
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
func unmarshalNull(target *DynamoDBAttributeValue) error {
|
||||
target.value = nil
|
||||
target.dataType = DataTypeNull
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalString(target *DynamoDBAttributeValue, value interface{}) error {
|
||||
var ok bool
|
||||
target.value, ok = value.(string)
|
||||
target.dataType = DataTypeString
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: S type should contain a string")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalBinary(target *DynamoDBAttributeValue, value interface{}) error {
|
||||
stringValue, ok := value.(string)
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: B type should contain a base64 string")
|
||||
}
|
||||
|
||||
binaryValue, err := base64.StdEncoding.DecodeString(stringValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target.value = binaryValue
|
||||
target.dataType = DataTypeBinary
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalBoolean(target *DynamoDBAttributeValue, value interface{}) error {
|
||||
booleanValue, ok := value.(bool)
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: BOOL type should contain a boolean")
|
||||
}
|
||||
|
||||
target.value = booleanValue
|
||||
target.dataType = DataTypeBoolean
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalBinarySet(target *DynamoDBAttributeValue, value interface{}) error {
|
||||
list, ok := value.([]interface{})
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: BS type should contain a list of base64 strings")
|
||||
}
|
||||
|
||||
binarySet := make([][]byte, len(list))
|
||||
|
||||
for index, element := range list {
|
||||
var err error
|
||||
elementString := element.(string)
|
||||
binarySet[index], err = base64.StdEncoding.DecodeString(elementString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
target.value = binarySet
|
||||
target.dataType = DataTypeBinarySet
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalList(target *DynamoDBAttributeValue, value interface{}) error {
|
||||
list, ok := value.([]interface{})
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: L type should contain a list")
|
||||
}
|
||||
|
||||
DynamoDBAttributeValues := make([]DynamoDBAttributeValue, len(list))
|
||||
for index, element := range list {
|
||||
|
||||
elementMap, ok := element.(map[string]interface{})
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: element of a list is not an DynamoDBAttributeValue")
|
||||
}
|
||||
|
||||
var elementDynamoDBAttributeValue DynamoDBAttributeValue
|
||||
err := unmarshalDynamoDBAttributeValueMap(&elementDynamoDBAttributeValue, elementMap)
|
||||
if err != nil {
|
||||
return errors.New("DynamoDBAttributeValue: unmarshal of child DynamoDBAttributeValue failed")
|
||||
}
|
||||
DynamoDBAttributeValues[index] = elementDynamoDBAttributeValue
|
||||
}
|
||||
target.value = DynamoDBAttributeValues
|
||||
target.dataType = DataTypeList
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalMap(target *DynamoDBAttributeValue, value interface{}) error {
|
||||
m, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: M type should contain a map")
|
||||
}
|
||||
|
||||
DynamoDBAttributeValues := make(map[string]DynamoDBAttributeValue)
|
||||
for k, v := range m {
|
||||
|
||||
elementMap, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: element of a map is not an DynamoDBAttributeValue")
|
||||
}
|
||||
|
||||
var elementDynamoDBAttributeValue DynamoDBAttributeValue
|
||||
err := unmarshalDynamoDBAttributeValueMap(&elementDynamoDBAttributeValue, elementMap)
|
||||
if err != nil {
|
||||
return errors.New("DynamoDBAttributeValue: unmarshal of child DynamoDBAttributeValue failed")
|
||||
}
|
||||
DynamoDBAttributeValues[k] = elementDynamoDBAttributeValue
|
||||
}
|
||||
target.value = DynamoDBAttributeValues
|
||||
target.dataType = DataTypeMap
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalNumber(target *DynamoDBAttributeValue, value interface{}) error {
|
||||
var ok bool
|
||||
target.value, ok = value.(string)
|
||||
target.dataType = DataTypeNumber
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: N type should contain a string")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalNumberSet(target *DynamoDBAttributeValue, value interface{}) error {
|
||||
list, ok := value.([]interface{})
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: NS type should contain a list of strings")
|
||||
}
|
||||
|
||||
numberSet := make([]string, len(list))
|
||||
|
||||
for index, element := range list {
|
||||
numberSet[index], ok = element.(string)
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: NS type should contain a list of strings")
|
||||
}
|
||||
}
|
||||
|
||||
target.value = numberSet
|
||||
target.dataType = DataTypeNumberSet
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalStringSet(target *DynamoDBAttributeValue, value interface{}) error {
|
||||
list, ok := value.([]interface{})
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: SS type should contain a list of strings")
|
||||
}
|
||||
|
||||
stringSet := make([]string, len(list))
|
||||
|
||||
for index, element := range list {
|
||||
stringSet[index], ok = element.(string)
|
||||
if !ok {
|
||||
return errors.New("DynamoDBAttributeValue: SS type should contain a list of strings")
|
||||
}
|
||||
}
|
||||
|
||||
target.value = stringSet
|
||||
target.dataType = DataTypeStringSet
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalDynamoDBAttributeValue(target *DynamoDBAttributeValue, typeLabel string, jsonValue interface{}) error {
|
||||
|
||||
switch typeLabel {
|
||||
case "NULL":
|
||||
return unmarshalNull(target)
|
||||
case "B":
|
||||
return unmarshalBinary(target, jsonValue)
|
||||
case "BOOL":
|
||||
return unmarshalBoolean(target, jsonValue)
|
||||
case "BS":
|
||||
return unmarshalBinarySet(target, jsonValue)
|
||||
case "L":
|
||||
return unmarshalList(target, jsonValue)
|
||||
case "M":
|
||||
return unmarshalMap(target, jsonValue)
|
||||
case "N":
|
||||
return unmarshalNumber(target, jsonValue)
|
||||
case "NS":
|
||||
return unmarshalNumberSet(target, jsonValue)
|
||||
case "S":
|
||||
return unmarshalString(target, jsonValue)
|
||||
case "SS":
|
||||
return unmarshalStringSet(target, jsonValue)
|
||||
default:
|
||||
target.value = nil
|
||||
target.dataType = DataTypeNull
|
||||
return UnsupportedDynamoDBTypeError{typeLabel}
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals a JSON description of this DynamoDBAttributeValue
|
||||
func (av *DynamoDBAttributeValue) UnmarshalJSON(b []byte) error {
|
||||
var m map[string]interface{}
|
||||
|
||||
err := json.Unmarshal(b, &m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return unmarshalDynamoDBAttributeValueMap(av, m)
|
||||
}
|
||||
|
||||
func unmarshalDynamoDBAttributeValueMap(target *DynamoDBAttributeValue, m map[string]interface{}) error {
|
||||
if m == nil {
|
||||
return errors.New("DynamoDBAttributeValue: does not contain a map")
|
||||
}
|
||||
|
||||
if len(m) != 1 {
|
||||
return errors.New("DynamoDBAttributeValue: map must contain a single type")
|
||||
}
|
||||
|
||||
for k, v := range m {
|
||||
return unmarshalDynamoDBAttributeValue(target, k, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package events
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// CloudwatchLogsEvent represents raw data from a cloudwatch logs event
|
||||
type CloudwatchLogsEvent struct {
|
||||
AWSLogs CloudwatchLogsRawData `json:"awslogs"`
|
||||
}
|
||||
|
||||
// CloudwatchLogsRawData contains gzipped base64 json representing the bulk
|
||||
// of a cloudwatch logs event
|
||||
type CloudwatchLogsRawData struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// Parse returns a slice of structs represting a usable CloudwatchLogs event
|
||||
func (c CloudwatchLogsRawData) Parse() (d CloudwatchLogsData, err error) {
|
||||
data, err := base64.StdEncoding.DecodeString(c.Data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
zr, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
buf.ReadFrom(zr)
|
||||
|
||||
err = json.Unmarshal(buf.Bytes(), &d)
|
||||
return
|
||||
}
|
||||
|
||||
// CloudwatchLogsData is an unmarshal'd, ungzip'd, cloudwatch logs event
|
||||
type CloudwatchLogsData struct {
|
||||
Owner string `json:"owner"`
|
||||
LogGroup string `json:"logGroup"`
|
||||
LogStream string `json:"logStream"`
|
||||
SubscriptionFilters []string `json:"subscriptionFilters"`
|
||||
MessageType string `json:"messageType"`
|
||||
LogEvents []CloudwatchLogsLogEvent `json:"logEvents"`
|
||||
}
|
||||
|
||||
// LogEvent represents a log entry from cloudwatch logs
|
||||
type CloudwatchLogsLogEvent struct {
|
||||
ID string `json:"id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Message string `json:"message"`
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package events
|
||||
|
||||
// CognitoEvent contains data from an event sent from AWS Cognito
|
||||
type CognitoEvent struct {
|
||||
DatasetName string `json:"datasetName"`
|
||||
DatasetRecords map[string]CognitoDatasetRecord `json:"datasetRecords"`
|
||||
EventType string `json:"eventType"`
|
||||
IdentityID string `json:"identityId"`
|
||||
IdentityPoolID string `json:"identityPoolId"`
|
||||
Region string `json:"region"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
// CognitoDatasetRecord represents a record from an AWS Cognito event
|
||||
type CognitoDatasetRecord struct {
|
||||
NewValue string `json:"newValue"`
|
||||
OldValue string `json:"oldValue"`
|
||||
Op string `json:"op"`
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package events
|
||||
|
||||
// ConfigEvent contains data from an event sent from AWS Config
|
||||
type ConfigEvent struct {
|
||||
AccountID string `json:"accountId"` // The ID of the AWS account that owns the rule
|
||||
ConfigRuleArn string `json:"configRuleArn"` // The ARN that AWS Config assigned to the rule
|
||||
ConfigRuleID string `json:"configRuleId"`
|
||||
ConfigRuleName string `json:"configRuleName"` // The name that you assigned to the rule that caused AWS Config to publish the event
|
||||
EventLeftScope bool `json:"eventLeftScope"` // A boolean value that indicates whether the AWS resource to be evaluated has been removed from the rule's scope
|
||||
ExecutionRoleArn string `json:"executionRoleArn"`
|
||||
InvokingEvent string `json:"invokingEvent"` // If the event is published in response to a resource configuration change, this value contains a JSON configuration item
|
||||
ResultToken string `json:"resultToken"` // A token that the function must pass to AWS Config with the PutEvaluations call
|
||||
RuleParameters string `json:"ruleParameters"` // Key/value pairs that the function processes as part of its evaluation logic
|
||||
Version string `json:"version"`
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package events
|
||||
|
||||
// The DynamoDBEvent stream event handled to Lambda
|
||||
// http://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-ddb-update
|
||||
type DynamoDBEvent struct {
|
||||
Records []DynamoDBEventRecord `json:"Records"`
|
||||
}
|
||||
|
||||
// DynamoDbEventRecord stores information about each record of a DynamoDb stream event
|
||||
type DynamoDBEventRecord struct {
|
||||
// The region in which the GetRecords request was received.
|
||||
AWSRegion string `json:"awsRegion"`
|
||||
|
||||
// The main body of the stream record, containing all of the DynamoDB-specific
|
||||
// fields.
|
||||
Change DynamoDBStreamRecord `json:"dynamodb"`
|
||||
|
||||
// A globally unique identifier for the event that was recorded in this stream
|
||||
// record.
|
||||
EventID string `json:"eventID"`
|
||||
|
||||
// The type of data modification that was performed on the DynamoDB table:
|
||||
//
|
||||
// * INSERT - a new item was added to the table.
|
||||
//
|
||||
// * MODIFY - one or more of an existing item's attributes were modified.
|
||||
//
|
||||
// * REMOVE - the item was deleted from the table
|
||||
EventName string `json:"eventName"`
|
||||
|
||||
// The AWS service from which the stream record originated. For DynamoDB Streams,
|
||||
// this is aws:dynamodb.
|
||||
EventSource string `json:"eventSource"`
|
||||
|
||||
// The version number of the stream record format. This number is updated whenever
|
||||
// the structure of Record is modified.
|
||||
//
|
||||
// Client applications must not assume that eventVersion will remain at a particular
|
||||
// value, as this number is subject to change at any time. In general, eventVersion
|
||||
// will only increase as the low-level DynamoDB Streams API evolves.
|
||||
EventVersion string `json:"eventVersion"`
|
||||
|
||||
// The event source ARN of DynamoDB
|
||||
EventSourceArn string `json:"eventSourceARN"`
|
||||
}
|
||||
|
||||
// A description of a single data modification that was performed on an item
|
||||
// in a DynamoDB table.
|
||||
type DynamoDBStreamRecord struct {
|
||||
|
||||
// The approximate date and time when the stream record was created, in UNIX
|
||||
// epoch time (http://www.epochconverter.com/) format.
|
||||
ApproximateCreationDateTime SecondsEpochTime `json:"ApproximateCreationDateTime,omitempty"`
|
||||
|
||||
// The primary key attribute(s) for the DynamoDB item that was modified.
|
||||
Keys map[string]DynamoDBAttributeValue `json:"Keys,omitempty"`
|
||||
|
||||
// The item in the DynamoDB table as it appeared after it was modified.
|
||||
NewImage map[string]DynamoDBAttributeValue `json:"NewImage,omitempty"`
|
||||
|
||||
// The item in the DynamoDB table as it appeared before it was modified.
|
||||
OldImage map[string]DynamoDBAttributeValue `json:"OldImage,omitempty"`
|
||||
|
||||
// The sequence number of the stream record.
|
||||
SequenceNumber string `json:"SequenceNumber"`
|
||||
|
||||
// The size of the stream record, in bytes.
|
||||
SizeBytes int64 `json:"SizeBytes"`
|
||||
|
||||
// The type of data from the modified DynamoDB item that was captured in this
|
||||
// stream record.
|
||||
StreamViewType string `json:"StreamViewType"`
|
||||
}
|
||||
|
||||
type DynamoDBKeyType string
|
||||
|
||||
const (
|
||||
DynamoDBKeyTypeHash DynamoDBKeyType = "HASH"
|
||||
DynamoDBKeyTypeRange DynamoDBKeyType = "RANGE"
|
||||
)
|
||||
|
||||
type DynamoDBOperationType string
|
||||
|
||||
const (
|
||||
DynamoDBOperationTypeInsert DynamoDBOperationType = "INSERT"
|
||||
DynamoDBOperationTypeModify DynamoDBOperationType = "MODIFY"
|
||||
DynamoDBOperationTypeRemove DynamoDBOperationType = "REMOVE"
|
||||
)
|
||||
|
||||
type DynamoDBSharedIteratorType string
|
||||
|
||||
const (
|
||||
DynamoDBShardIteratorTypeTrimHorizon DynamoDBSharedIteratorType = "TRIM_HORIZON"
|
||||
DynamoDBShardIteratorTypeLatest DynamoDBSharedIteratorType = "LATEST"
|
||||
DynamoDBShardIteratorTypeAtSequenceNumber DynamoDBSharedIteratorType = "AT_SEQUENCE_NUMBER"
|
||||
DynamoDBShardIteratorTypeAfterSequenceNumber DynamoDBSharedIteratorType = "AFTER_SEQUENCE_NUMBER"
|
||||
)
|
||||
|
||||
type DynamoDBStreamStatus string
|
||||
|
||||
const (
|
||||
DynamoDBStreamStatusEnabling DynamoDBStreamStatus = "ENABLING"
|
||||
DynamoDBStreamStatusEnabled DynamoDBStreamStatus = "ENABLED"
|
||||
DynamoDBStreamStatusDisabling DynamoDBStreamStatus = "DISABLING"
|
||||
DynamoDBStreamStatusDisabled DynamoDBStreamStatus = "DISABLED"
|
||||
)
|
||||
|
||||
type DynamoDBStreamViewType string
|
||||
|
||||
const (
|
||||
DynamoDBStreamViewTypeNewImage DynamoDBStreamViewType = "NEW_IMAGE" // the entire item, as it appeared after it was modified.
|
||||
DynamoDBStreamViewTypeOldImage DynamoDBStreamViewType = "OLD_IMAGE" // the entire item, as it appeared before it was modified.
|
||||
DynamoDBStreamViewTypeNewAndOldImages DynamoDBStreamViewType = "NEW_AND_OLD_IMAGES" // both the new and the old item images of the item.
|
||||
DynamoDBStreamViewTypeKeysOnly DynamoDBStreamViewType = "KEYS_ONLY" // only the key attributes of the modified item.
|
||||
)
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SecondsEpochTime serializes a time.Time in JSON as a UNIX epoch time in seconds
|
||||
type SecondsEpochTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// MilliSecondsEpochTime serializes a time.Time in JSON as a UNIX epoch time in milliseconds.
|
||||
type MilliSecondsEpochTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
const secondsToNanoSecondsFactor = 1000000000
|
||||
const milliSecondsToNanoSecondsFactor = 1000000
|
||||
|
||||
func (e SecondsEpochTime) MarshalJSON() ([]byte, error) {
|
||||
// UnixNano() returns the epoch in nanoseconds
|
||||
unixTime := float64(e.UnixNano()) / float64(secondsToNanoSecondsFactor)
|
||||
return json.Marshal(unixTime)
|
||||
}
|
||||
|
||||
func (e *SecondsEpochTime) UnmarshalJSON(b []byte) error {
|
||||
var epoch float64
|
||||
err := json.Unmarshal(b, &epoch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
epochSec := int64(epoch)
|
||||
epochNano := int64((epoch - float64(epochSec)) * float64(secondsToNanoSecondsFactor))
|
||||
|
||||
// time.Unix(sec, nsec) expects the epoch integral seconds in the first parameter
|
||||
// and remaining nanoseconds in the second parameter
|
||||
*e = SecondsEpochTime{time.Unix(epochSec, epochNano)}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e MilliSecondsEpochTime) MarshalJSON() ([]byte, error) {
|
||||
// UnixNano() returns the epoch in nanoseconds
|
||||
unixTimeMs := e.UnixNano() / milliSecondsToNanoSecondsFactor
|
||||
return json.Marshal(unixTimeMs)
|
||||
}
|
||||
|
||||
func (e *MilliSecondsEpochTime) UnmarshalJSON(b []byte) error {
|
||||
var epoch int64
|
||||
err := json.Unmarshal(b, &epoch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*e = MilliSecondsEpochTime{time.Unix(epoch/1000, (epoch%1000)*1000000)}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package events
|
||||
|
||||
// KinesisFirehoseEvent represents the input event from Amazon Kinesis Firehose. It is used as the input parameter.
|
||||
type KinesisFirehoseEvent struct {
|
||||
InvocationID string `json:"invocationId"`
|
||||
DeliveryStreamArn string `json:"deliveryStreamArn"`
|
||||
Region string `json:"region"`
|
||||
Records []KinesisFirehoseEventRecord `json:"records"`
|
||||
}
|
||||
|
||||
type KinesisFirehoseEventRecord struct {
|
||||
RecordID string `json:"recordId"`
|
||||
ApproximateArrivalTimestamp MilliSecondsEpochTime `json:"approximateArrivalTimestamp"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
// Constants used for describing the transformation result
|
||||
const (
|
||||
KinesisFirehoseTransformedStateOk = "TRANSFORMED_STATE_OK"
|
||||
KinesisFirehoseTransformedStateDropped = "TRANSFORMED_STATE_DROPPED"
|
||||
KinesisFirehoseTransformedStateProcessingFailed = "TRANSFORMED_STATE_PROCESSINGFAILED"
|
||||
)
|
||||
|
||||
type KinesisFirehoseResponse struct {
|
||||
Records []KinesisFirehoseResponseRecord `json:"records"`
|
||||
}
|
||||
|
||||
type KinesisFirehoseResponseRecord struct {
|
||||
RecordID string `json:"recordId"`
|
||||
Result string `json:"result"` // The status of the transformation. May be TransformedStateOk, TransformedStateDropped or TransformedStateProcessingFailed
|
||||
Data []byte `json:"data"`
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package events
|
||||
|
||||
type KinesisEvent struct {
|
||||
Records []KinesisEventRecord `json:"Records"`
|
||||
}
|
||||
|
||||
type KinesisEventRecord struct {
|
||||
AwsRegion string `json:"awsRegion"`
|
||||
EventID string `json:"eventID"`
|
||||
EventName string `json:"eventName"`
|
||||
EventSource string `json:"eventSource"`
|
||||
EventSourceArn string `json:"eventSourceARN"`
|
||||
EventVersion string `json:"eventVersion"`
|
||||
InvokeIdentityArn string `json:"invokeIdentityArn"`
|
||||
Kinesis KinesisRecord `json:"kinesis"`
|
||||
}
|
||||
|
||||
type KinesisRecord struct {
|
||||
ApproximateArrivalTimestamp SecondsEpochTime `json:"approximateArrivalTimestamp"`
|
||||
Data []byte `json:"data"`
|
||||
EncryptionType string `json:"encryptionType,omitempty"`
|
||||
PartitionKey string `json:"partitionKey"`
|
||||
SequenceNumber string `json:"sequenceNumber"`
|
||||
KinesisSchemaVersion string `json:"kinesisSchemaVersion"`
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type S3Event struct {
|
||||
Records []S3EventRecord `json:"Records"`
|
||||
}
|
||||
|
||||
type S3EventRecord struct {
|
||||
EventVersion string `json:"eventVersion"`
|
||||
EventSource string `json:"eventSource"`
|
||||
AWSRegion string `json:"awsRegion"`
|
||||
EventTime time.Time `json:"eventTime"`
|
||||
EventName string `json:"eventName"`
|
||||
PrincipalID S3UserIdentity `json:"userIdentity"`
|
||||
RequestParameters S3RequestParameters `json:"requestParameters"`
|
||||
ResponseElements map[string]string `json:"responseElements"`
|
||||
S3 S3Entity `json:"s3"`
|
||||
}
|
||||
|
||||
type S3UserIdentity struct {
|
||||
PrincipalID string `json:"principalId"`
|
||||
}
|
||||
|
||||
type S3RequestParameters struct {
|
||||
SourceIPAddress string `json:"sourceIPAddress"`
|
||||
}
|
||||
|
||||
type S3Entity struct {
|
||||
SchemaVersion string `json:"s3SchemaVersion"`
|
||||
ConfigurationID string `json:"configurationId"`
|
||||
Bucket S3Bucket `json:"bucket"`
|
||||
Object S3Object `json:"object"`
|
||||
}
|
||||
|
||||
type S3Bucket struct {
|
||||
Name string `json:"name"`
|
||||
OwnerIdentity S3UserIdentity `json:"ownerIdentity"`
|
||||
Arn string `json:"arn"`
|
||||
}
|
||||
|
||||
type S3Object struct {
|
||||
Key string `json:"key"`
|
||||
Size int64 `json:"size"`
|
||||
URLDecodedKey string `json:"urlDecodedKey"`
|
||||
VersionID string `json:"versionId"`
|
||||
ETag string `json:"eTag"`
|
||||
Sequencer string `json:"sequencer"`
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type SNSEvent struct {
|
||||
Records []SNSEventRecord `json:"Records"`
|
||||
}
|
||||
|
||||
type SNSEventRecord struct {
|
||||
EventVersion string `json:"EventVersion"`
|
||||
EventSubscriptionArn string `json:"EventSubscriptionArn"`
|
||||
EventSource string `json:"EventSource"`
|
||||
SNS SNSEntity `json:"Sns"`
|
||||
}
|
||||
|
||||
type SNSEntity struct {
|
||||
Signature string `json:"Signature"`
|
||||
MessageID string `json:"MessageId"`
|
||||
Type string `json:"Type"`
|
||||
TopicArn string `json:"TopicArn"`
|
||||
MessageAttributes map[string]interface{} `json:"MessageAttributes"`
|
||||
SignatureVersion string `json:"SignatureVersion"`
|
||||
Timestamp time.Time `json:"Timestamp"`
|
||||
SigningCertURL string `json:"SigningCertUrl"`
|
||||
Message string `json:"Message"`
|
||||
UnsubscribeURL string `json:"UnsubscribeUrl"`
|
||||
Subject string `json:"Subject"`
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package lambda
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Start takes a handler, and talks to and internal Lambda endpoint to pass Invoke requests to the handler. If a
|
||||
// handler does not match one of the supported types, the lambda package will respond to new invokes served by in
|
||||
// internal endpoint with an appropriate error message. Start blocks, and does not return after being called.
|
||||
//
|
||||
// Rules:
|
||||
// * handler must be a function
|
||||
// * handler may take between 0 and two arguments.
|
||||
// * If there are two arguments, the first argument must implement "context.Context".
|
||||
// * handler may return between 0 and two arguments.
|
||||
// * If there are two return values, the second argument must implement "error".
|
||||
// * If there is one return value it must implement "error".
|
||||
//
|
||||
// func ()
|
||||
// func () error
|
||||
// func (TIn) error
|
||||
// func () (TOut, error)
|
||||
// func (TIn) (TOut, error)
|
||||
// func (context.Context) error
|
||||
// func (context.Context, TIn) error
|
||||
// func (context.Context) (TOut, error)
|
||||
// func (context.Context, TIn) (TOut, error)
|
||||
//
|
||||
// Where '''TIn''' and '''TOut''' are types compatible with the ''encoding/json'' standard library.
|
||||
// See https://golang.org/pkg/encoding/json/#Unmarshal for how deserialization behaves
|
||||
func Start(handler interface{}) {
|
||||
port := os.Getenv("_LAMBDA_SERVER_PORT")
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%s", port))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
wrappedHandler := newHandler(handler)
|
||||
function := new(Function)
|
||||
function.handler = wrappedHandler
|
||||
err = rpc.Register(function)
|
||||
if err != nil {
|
||||
log.Fatal("failed to register handler function")
|
||||
}
|
||||
rpc.Accept(lis)
|
||||
log.Fatal("accept should not have returned")
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package lambda
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-lambda-go/lambda/messages"
|
||||
"github.com/aws/aws-lambda-go/lambdacontext"
|
||||
)
|
||||
|
||||
type Function struct {
|
||||
handler lambdaHandler
|
||||
}
|
||||
|
||||
func (fn *Function) Ping(req *messages.PingRequest, response *messages.PingResponse) error {
|
||||
*response = messages.PingResponse{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fn *Function) Invoke(req *messages.InvokeRequest, response *messages.InvokeResponse) error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
panicInfo := getPanicInfo(err)
|
||||
response.Error = &messages.InvokeResponse_Error{
|
||||
Message: panicInfo.Message,
|
||||
Type: getErrorType(err),
|
||||
StackTrace: panicInfo.StackTrace,
|
||||
ShouldExit: true,
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
deadline := time.Unix(req.Deadline.Seconds, req.Deadline.Nanos).UTC()
|
||||
invokeContext, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel()
|
||||
|
||||
lc := &lambdacontext.LambdaContext{
|
||||
AwsRequestID: req.RequestId,
|
||||
InvokedFunctionArn: req.InvokedFunctionArn,
|
||||
}
|
||||
if len(req.ClientContext) > 0 {
|
||||
if err := json.Unmarshal(req.ClientContext, &lc.ClientContext); err != nil {
|
||||
response.Error = lambdaErrorResponse(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
invokeContext = lambdacontext.NewContext(invokeContext, lc)
|
||||
|
||||
invokeContext = context.WithValue(invokeContext, "x-amzn-trace-id", req.XAmznTraceId)
|
||||
|
||||
payload, err := fn.handler.Invoke(invokeContext, req.Payload)
|
||||
if err != nil {
|
||||
response.Error = lambdaErrorResponse(err)
|
||||
return nil
|
||||
}
|
||||
response.Payload = payload
|
||||
return nil
|
||||
}
|
||||
|
||||
func getErrorType(err interface{}) string {
|
||||
errorType := reflect.TypeOf(err)
|
||||
if errorType.Kind() == reflect.Ptr {
|
||||
return errorType.Elem().Name()
|
||||
}
|
||||
return errorType.Name()
|
||||
}
|
||||
|
||||
func lambdaErrorResponse(invokeError error) *messages.InvokeResponse_Error {
|
||||
var errorName string
|
||||
if errorType := reflect.TypeOf(invokeError); errorType.Kind() == reflect.Ptr {
|
||||
errorName = errorType.Elem().Name()
|
||||
} else {
|
||||
errorName = errorType.Name()
|
||||
}
|
||||
return &messages.InvokeResponse_Error{
|
||||
Message: invokeError.Error(),
|
||||
Type: errorName,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package lambda
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// lambdaHandler is the generic function type
|
||||
type lambdaHandler func(context.Context, []byte) (interface{}, error)
|
||||
|
||||
// Invoke calls the handler, and serializes the response.
|
||||
// If the underlying handler returned an error, or an error occurs during serialization, error is returned.
|
||||
func (handler lambdaHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) {
|
||||
response, err := handler(ctx, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responseBytes, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return responseBytes, nil
|
||||
}
|
||||
|
||||
func errorHandler(e error) lambdaHandler {
|
||||
return func(ctx context.Context, event []byte) (interface{}, error) {
|
||||
return nil, e
|
||||
}
|
||||
}
|
||||
|
||||
func validateArguments(handler reflect.Type) (bool, error) {
|
||||
handlerTakesContext := false
|
||||
if handler.NumIn() > 2 {
|
||||
return false, fmt.Errorf("handlers may not take more than two arguments, but handler takes %d", handler.NumIn())
|
||||
} else if handler.NumIn() > 0 {
|
||||
contextType := reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
argumentType := handler.In(0)
|
||||
handlerTakesContext = argumentType.Implements(contextType)
|
||||
if handler.NumIn() > 1 && !handlerTakesContext {
|
||||
return false, fmt.Errorf("handler takes two arguments, but the first is not Context. got %s", argumentType.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
return handlerTakesContext, nil
|
||||
}
|
||||
|
||||
func validateReturns(handler reflect.Type) error {
|
||||
errorType := reflect.TypeOf((*error)(nil)).Elem()
|
||||
if handler.NumOut() > 2 {
|
||||
return fmt.Errorf("handler may not return more than two values")
|
||||
} else if handler.NumOut() > 1 {
|
||||
if !handler.Out(1).Implements(errorType) {
|
||||
return fmt.Errorf("handler returns two values, but the second does not implement error")
|
||||
}
|
||||
} else if handler.NumOut() == 1 {
|
||||
if !handler.Out(0).Implements(errorType) {
|
||||
return fmt.Errorf("handler returns a single value, but it does not implement error")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newHandler Creates the base lambda handler, which will do basic payload unmarshaling before defering to handlerSymbol.
|
||||
// If handlerSymbol is not a valid handler, the returned function will be a handler that just reports the validation error.
|
||||
func newHandler(handlerSymbol interface{}) lambdaHandler {
|
||||
if handlerSymbol == nil {
|
||||
return errorHandler(fmt.Errorf("handler is nil"))
|
||||
}
|
||||
handler := reflect.ValueOf(handlerSymbol)
|
||||
handlerType := reflect.TypeOf(handlerSymbol)
|
||||
if handlerType.Kind() != reflect.Func {
|
||||
return errorHandler(fmt.Errorf("handler kind %s is not %s", handlerType.Kind(), reflect.Func))
|
||||
}
|
||||
|
||||
takesContext, err := validateArguments(handlerType)
|
||||
if err != nil {
|
||||
return errorHandler(err)
|
||||
}
|
||||
|
||||
if err := validateReturns(handlerType); err != nil {
|
||||
return errorHandler(err)
|
||||
}
|
||||
|
||||
return func(ctx context.Context, payload []byte) (interface{}, error) {
|
||||
// construct arguments
|
||||
var args []reflect.Value
|
||||
if takesContext {
|
||||
args = append(args, reflect.ValueOf(ctx))
|
||||
}
|
||||
if (handlerType.NumIn() == 1 && !takesContext) || handlerType.NumIn() == 2 {
|
||||
eventType := handlerType.In(handlerType.NumIn() - 1)
|
||||
event := reflect.New(eventType)
|
||||
|
||||
if err := json.Unmarshal(payload, event.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
args = append(args, event.Elem())
|
||||
}
|
||||
|
||||
response := handler.Call(args)
|
||||
|
||||
// convert return values into (interface{}, error)
|
||||
var err error
|
||||
if len(response) > 0 {
|
||||
if errVal, ok := response[len(response)-1].Interface().(error); ok {
|
||||
err = errVal
|
||||
}
|
||||
}
|
||||
var val interface{}
|
||||
if len(response) > 1 {
|
||||
val = response[0].Interface()
|
||||
}
|
||||
|
||||
return val, err
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package messages
|
||||
|
||||
type PingRequest struct {
|
||||
}
|
||||
|
||||
type PingResponse struct {
|
||||
}
|
||||
|
||||
type InvokeRequest_Timestamp struct {
|
||||
Seconds int64
|
||||
Nanos int64
|
||||
}
|
||||
|
||||
type InvokeRequest struct {
|
||||
Payload []byte
|
||||
RequestId string
|
||||
XAmznTraceId string
|
||||
Deadline InvokeRequest_Timestamp
|
||||
InvokedFunctionArn string
|
||||
CognitoIdentityId string
|
||||
CognitoIdentityPoolId string
|
||||
ClientContext []byte
|
||||
}
|
||||
|
||||
type InvokeResponse struct {
|
||||
Payload []byte
|
||||
Error *InvokeResponse_Error
|
||||
}
|
||||
|
||||
type InvokeResponse_Error struct {
|
||||
Message string
|
||||
Type string
|
||||
StackTrace []*InvokeResponse_Error_StackFrame
|
||||
ShouldExit bool
|
||||
}
|
||||
|
||||
type InvokeResponse_Error_StackFrame struct {
|
||||
Path string `json:"path"`
|
||||
Line int32 `json:"line"`
|
||||
Label string `json:"label"`
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
package lambda
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-lambda-go/lambda/messages"
|
||||
)
|
||||
|
||||
type panicInfo struct {
|
||||
Message string // Value passed to panic call, converted to string
|
||||
StackTrace []*messages.InvokeResponse_Error_StackFrame // Stack trace of the panic
|
||||
}
|
||||
|
||||
func getPanicInfo(value interface{}) panicInfo {
|
||||
message := getPanicMessage(value)
|
||||
stack := getPanicStack()
|
||||
|
||||
return panicInfo{Message: message, StackTrace: stack}
|
||||
}
|
||||
|
||||
func getPanicMessage(value interface{}) string {
|
||||
return fmt.Sprintf("%v", value)
|
||||
}
|
||||
|
||||
var defaultErrorFrameCount = 32
|
||||
|
||||
func getPanicStack() []*messages.InvokeResponse_Error_StackFrame {
|
||||
s := make([]uintptr, defaultErrorFrameCount)
|
||||
const framesToHide = 3 // this (getPanicStack) -> getPanicInfo -> handler defer func
|
||||
n := runtime.Callers(framesToHide, s)
|
||||
if n == 0 {
|
||||
return make([]*messages.InvokeResponse_Error_StackFrame, 0)
|
||||
}
|
||||
|
||||
s = s[:n]
|
||||
|
||||
return convertStack(s)
|
||||
}
|
||||
|
||||
func convertStack(s []uintptr) []*messages.InvokeResponse_Error_StackFrame {
|
||||
var converted []*messages.InvokeResponse_Error_StackFrame
|
||||
frames := runtime.CallersFrames(s)
|
||||
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
|
||||
formattedFrame := formatFrame(frame)
|
||||
converted = append(converted, formattedFrame)
|
||||
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func formatFrame(inputFrame runtime.Frame) *messages.InvokeResponse_Error_StackFrame {
|
||||
path := inputFrame.File
|
||||
line := int32(inputFrame.Line)
|
||||
label := inputFrame.Function
|
||||
|
||||
// Strip GOPATH from path by counting the number of seperators in label & path
|
||||
//
|
||||
// For example given this:
|
||||
// GOPATH = /home/user
|
||||
// path = /home/user/src/pkg/sub/file.go
|
||||
// label = pkg/sub.Type.Method
|
||||
//
|
||||
// We want to set:
|
||||
// path = pkg/sub/file.go
|
||||
// label = Type.Method
|
||||
|
||||
i := len(path)
|
||||
for n, g := 0, strings.Count(label, "/")+2; n < g; n++ {
|
||||
i = strings.LastIndex(path[:i], "/")
|
||||
if i == -1 {
|
||||
// Something went wrong and path has less seperators than we expected
|
||||
// Abort and leave i as -1 to counteract the +1 below
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
path = path[i+1:] // Trim the initial /
|
||||
|
||||
// Strip the path from the function name as it's already in the path
|
||||
label = label[strings.LastIndex(label, "/")+1:]
|
||||
// Likewise strip the package name
|
||||
label = label[strings.Index(label, ".")+1:]
|
||||
|
||||
return &messages.InvokeResponse_Error_StackFrame{
|
||||
Path: path,
|
||||
Line: line,
|
||||
Label: label,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Helpers for accessing context information from an Invoke request. Context information
|
||||
// is stored in a https://golang.org/pkg/context/#Context. The functions FromContext and NewContext
|
||||
// are used to retrieving and inserting an isntance of LambdaContext.
|
||||
|
||||
package lambdacontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// LogGroupName is the name of the log group that contains the log streams of the current Lambda Function
|
||||
var LogGroupName string
|
||||
|
||||
// LogStreamName name of the log stream that the current Lambda Function's logs will be sent to
|
||||
var LogStreamName string
|
||||
|
||||
// FunctionName the name of the current Lambda Function
|
||||
var FunctionName string
|
||||
|
||||
// MemoryLimitInMB is the configured memory limit for the current instance of the Lambda Function
|
||||
var MemoryLimitInMB int
|
||||
|
||||
// FunctionVersion is the published version of the current instance of the Lambda Function
|
||||
var FunctionVersion string
|
||||
|
||||
func init() {
|
||||
LogGroupName = os.Getenv("AWS_LAMBDA_LOG_GROUP_NAME")
|
||||
LogStreamName = os.Getenv("AWS_LAMBDA_LOG_STREAM_NAME")
|
||||
FunctionName = os.Getenv("AWS_LAMBDA_FUNCTION_NAME")
|
||||
if limit, err := strconv.Atoi(os.Getenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")); err != nil {
|
||||
MemoryLimitInMB = 0
|
||||
} else {
|
||||
MemoryLimitInMB = limit
|
||||
}
|
||||
FunctionVersion = os.Getenv("AWS_LAMBDA_FUNCTION_VERSION")
|
||||
}
|
||||
|
||||
// ClientApplication is metadata about the calling application.
|
||||
type ClientApplication struct {
|
||||
InstallationID string `json:"installation_id"`
|
||||
AppTitle string `json:"app_title"`
|
||||
AppVersionCode string `json:"app_version_code"`
|
||||
AppPackageName string `json:"app_package_name"`
|
||||
}
|
||||
|
||||
// ClientContext is information about the client application passed by the calling application.
|
||||
type ClientContext struct {
|
||||
Client ClientApplication
|
||||
Env map[string]string `json:"env"`
|
||||
Custom map[string]string `json:"custom"`
|
||||
}
|
||||
|
||||
// CognitoIdentity is the cognito identity used by the calling application.
|
||||
type CognitoIdentity struct {
|
||||
CognitoIdentityID string
|
||||
CognitoIdentityPoolID string
|
||||
}
|
||||
|
||||
// LambdaContext is the set of metadata that is passed for every Invoke.
|
||||
type LambdaContext struct {
|
||||
AwsRequestID string
|
||||
InvokedFunctionArn string
|
||||
Identity CognitoIdentity
|
||||
ClientContext ClientContext
|
||||
}
|
||||
|
||||
// An unexported type to be used as the key for types in this package.
|
||||
// This prevents collisions with keys defined in other packages.
|
||||
type key struct{}
|
||||
|
||||
// The key for a LambdaContext in Contexts.
|
||||
// Users of this package must use lambdacontext.NewContext and lambdacontext.FromContext
|
||||
// instead of using this key directly.
|
||||
var contextKey = &key{}
|
||||
|
||||
// NewContext returns a new Context that carries value lc.
|
||||
func NewContext(parent context.Context, lc *LambdaContext) context.Context {
|
||||
return context.WithValue(parent, contextKey, lc)
|
||||
}
|
||||
|
||||
// FromContext returns the LambdaContext value stored in ctx, if any.
|
||||
func FromContext(ctx context.Context) (*LambdaContext, bool) {
|
||||
lc, ok := ctx.Value(contextKey).(*LambdaContext)
|
||||
return lc, ok
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,52 @@
|
|||
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge)
|
||||
|
||||
Package errors provides simple error handling primitives.
|
||||
|
||||
`go get github.com/pkg/errors`
|
||||
|
||||
The traditional error handling idiom in Go is roughly akin to
|
||||
```go
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||
|
||||
## Adding context to an error
|
||||
|
||||
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||
```go
|
||||
_, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read failed")
|
||||
}
|
||||
```
|
||||
## Retrieving the cause of an error
|
||||
|
||||
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||
```go
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
```
|
||||
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||
```go
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *MyError:
|
||||
// handle specifically
|
||||
default:
|
||||
// unknown error
|
||||
}
|
||||
```
|
||||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
|
||||
## License
|
||||
|
||||
BSD-2-Clause
|
|
@ -0,0 +1,32 @@
|
|||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||
shallow_clone: true # for startup speed
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# http://www.appveyor.com/docs/installed-software
|
||||
install:
|
||||
# some helpful output for debugging builds
|
||||
- go version
|
||||
- go env
|
||||
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||
# but MSYS2 at C:\msys64 has mingw64
|
||||
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||
- gcc --version
|
||||
- g++ --version
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- set PATH=C:\gopath\bin;%PATH%
|
||||
- go test -v ./...
|
||||
|
||||
#artifacts:
|
||||
# - path: '%GOPATH%\bin\*.exe'
|
||||
deploy: off
|
|
@ -0,0 +1,269 @@
|
|||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// and the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||
// functions destructure errors.Wrap into its component operations of annotating
|
||||
// an error with a stack trace and an a message, respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error which does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// causer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface.
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// Where errors.StackTrace is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// stackTracer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is call, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s function name and path of source file relative to the compile time
|
||||
// GOPATH separated by \n\t (<funcname>\n\t<path>)
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
// Format formats the stack of Frames according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s lists source files for each Frame in the stack
|
||||
// %v lists the source file and line number for each Frame in the stack
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+v Prints filename, function, and line number for each Frame in the stack.
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
}
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
||||
|
||||
func trimGOPATH(name, file string) string {
|
||||
// Here we want to get the source file path relative to the compile time
|
||||
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||
// the import path, which does not include the GOPATH. Thus we can trim
|
||||
// segments from the beginning of the file path until the number of path
|
||||
// separators remaining is one more than the number of path separators in
|
||||
// the function name. For example, given:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// file /home/user/src/pkg/sub/file.go
|
||||
// fn.Name() pkg/sub.Type.Method
|
||||
//
|
||||
// We want to produce:
|
||||
//
|
||||
// pkg/sub/file.go
|
||||
//
|
||||
// From this we can easily see that fn.Name() has one less path separator
|
||||
// than our desired output. We count separators from the end of the file
|
||||
// path until it finds two more than in the function name and then move
|
||||
// one character forward to preserve the initial path segment without a
|
||||
// leading separator.
|
||||
const sep = "/"
|
||||
goal := strings.Count(name, sep) + 2
|
||||
i := len(file)
|
||||
for n := 0; n < goal; n++ {
|
||||
i = strings.LastIndex(file[:i], sep)
|
||||
if i == -1 {
|
||||
// not enough separators found, set i so that the slice expression
|
||||
// below leaves file unmodified
|
||||
i = -len(sep)
|
||||
break
|
||||
}
|
||||
}
|
||||
// get back to 0 or trim the leading separator
|
||||
file = file[i+len(sep):]
|
||||
return file
|
||||
}
|
|
@ -2,6 +2,12 @@
|
|||
"comment": "",
|
||||
"ignore": "test",
|
||||
"package": [
|
||||
{
|
||||
"checksumSHA1": "DBuGaMCW5qcB56KbHNMdfpF797Y=",
|
||||
"path": "github.com/apex/gateway",
|
||||
"revision": "e9c6ccec8851cbed57b52e96858f3dc0572921dc",
|
||||
"revisionTime": "2018-01-21T23:10:47Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "1Jql7x7zDOmbDxGr4gsa8rFEC5g=",
|
||||
"path": "github.com/appleboy/go-fcm",
|
||||
|
@ -46,6 +52,30 @@
|
|||
"revision": "de95a2ead13a0ec4cff5e37d21decd48b54a078f",
|
||||
"revisionTime": "2017-01-29T11:39:06Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "PJFBQGoSr7QkbNtr3XqFdkXuHTs=",
|
||||
"path": "github.com/aws/aws-lambda-go/events",
|
||||
"revision": "6736675908bded5127345825f653c88937222091",
|
||||
"revisionTime": "2018-01-21T10:36:08Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "otXwJhbwKYBj3v0viFbP9Mogqnw=",
|
||||
"path": "github.com/aws/aws-lambda-go/lambda",
|
||||
"revision": "6736675908bded5127345825f653c88937222091",
|
||||
"revisionTime": "2018-01-21T10:36:08Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "FEIW+8VDruQZu0Cv9j7r+Km8r8o=",
|
||||
"path": "github.com/aws/aws-lambda-go/lambda/messages",
|
||||
"revision": "6736675908bded5127345825f653c88937222091",
|
||||
"revisionTime": "2018-01-21T10:36:08Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "+3R+gHl3aC1ZBNfg0dEVBv1roaQ=",
|
||||
"path": "github.com/aws/aws-lambda-go/lambdacontext",
|
||||
"revision": "6736675908bded5127345825f653c88937222091",
|
||||
"revisionTime": "2018-01-21T10:36:08Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "spyv5/YFBjYyZLZa1U2LBfDR8PM=",
|
||||
"path": "github.com/beorn7/perks/quantile",
|
||||
|
@ -298,6 +328,12 @@
|
|||
"revision": "8c31c2ec65b208cc2ad1608bf25a3ff91adf1944",
|
||||
"revisionTime": "2017-10-22T02:23:38Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "xCv4GBFyw07vZkVtKF/XrUnkHRk=",
|
||||
"path": "github.com/pkg/errors",
|
||||
"revision": "e881fd58d78e04cf6d0de1217f8707c8cc2249bc",
|
||||
"revisionTime": "2017-12-16T07:03:16Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
||||
"path": "github.com/pmezard/go-difflib/difflib",
|
||||
|
|
Loading…
Reference in New Issue