mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-05-06 22:18:28 +00:00
Update Go AWS SDK to the latest version
This commit is contained in:
committed by
Andrey Smirnov
parent
d08be990ef
commit
94a72b23ff
+2
-2
@@ -22,7 +22,7 @@ Compile the plugin with:
|
||||
JSON Credentials File
|
||||
---
|
||||
|
||||
This example plugin will read the credentials from a JSON file pointed to by
|
||||
This example plugin will read the credentials from a JSON file pointed to by
|
||||
the `PLUGIN_CREDS_FILE` environment variable. The contents of the file are
|
||||
the credentials, Key, Secret, and Token. The `Token` filed does not need to be
|
||||
set if your credentials do not have one.
|
||||
@@ -38,7 +38,7 @@ set if your credentials do not have one.
|
||||
Example Application
|
||||
---
|
||||
|
||||
The `main.go` file in this folder demonstrates how you can configure the SDK to
|
||||
The `main.go` file in this folder demonstrates how you can configure the SDK to
|
||||
use a plugin to retrieve credentials with.
|
||||
|
||||
Compile and run application:
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ CLI parameters
|
||||
-p=id partition id, e.g: aws
|
||||
-r=id region id, e.g: us-west-2
|
||||
-s=id service id, e.g: s3
|
||||
|
||||
|
||||
-partitions Lists all partitions.
|
||||
-regions Lists all regions in a partition. Requires partition ID.
|
||||
If service ID is also provided will show endpoints for a service.
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Example
|
||||
|
||||
Example of using the AWS SDK for Go with an HTTPS_PROXY that requires client
|
||||
TLS certificates. The example will use the proxy configured via the environment
|
||||
variable `HTTPS_PROXY` proxy a request for the Amazon S3 `ListBuckets` API
|
||||
operation call.
|
||||
|
||||
The example assumes credentials are provided in the environment, or shared
|
||||
credentials file `~/.aws/credentials`. The `certificate` and `key` files paths
|
||||
are required to be specified when the example is run. An certificate authority
|
||||
(CA) file path can also be optionally specified.
|
||||
|
||||
Refer to [httpproxy.FromEnvironment](https://godoc.org/golang.org/x/net/http/httpproxy#FromEnvironment)
|
||||
for details using `HTTPS_PROXY` with the Go HTTP client.
|
||||
|
||||
## Usage:
|
||||
|
||||
```sh
|
||||
export HTTPS_PROXY=https://127.0.0.1:8443
|
||||
export AWS_REGION=us-west-2
|
||||
go run -cert <certfile> -key <keyfile> [-ca <cafile>]
|
||||
```
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
// +build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
// Example of creating an HTTP Client configured with a client TLS
|
||||
// certificates. Can be used with endpoints such as HTTPS_PROXY that require
|
||||
// client certificates.
|
||||
//
|
||||
// Requires a cert and key flags, and optionally takes a CA file.
|
||||
//
|
||||
// To run:
|
||||
// go run -cert <certfile> -key <keyfile> [-ca <cafile>]
|
||||
//
|
||||
// You can generate self signed cert and key pem files
|
||||
// go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -host <hostname>
|
||||
func main() {
|
||||
var clientCertFile, clientKeyFile, caFile string
|
||||
flag.StringVar(&clientCertFile, "cert", "cert.pem", "The `certificate file` to load.")
|
||||
flag.StringVar(&clientKeyFile, "key", "key.pem", "The `key file` to load.")
|
||||
flag.StringVar(&caFile, "ca", "ca.pem", "The `root CA` to load.")
|
||||
flag.Parse()
|
||||
|
||||
if len(clientCertFile) == 0 || len(clientKeyFile) == 0 {
|
||||
flag.PrintDefaults()
|
||||
log.Fatalf("client certificate and key required")
|
||||
}
|
||||
|
||||
tlsCfg, err := tlsConfigWithClientCert(clientCertFile, clientKeyFile, caFile)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load client cert, %v", err)
|
||||
}
|
||||
|
||||
// Copy of net/http.DefaultTransport with TLS config loaded
|
||||
tr := defaultHTTPTransport()
|
||||
tr.TLSClientConfig = tlsCfg
|
||||
|
||||
// re-enable HTTP/2 because modifing TLS config will prevent auto support
|
||||
// for HTTP/2.
|
||||
http2.ConfigureTransport(tr)
|
||||
|
||||
// Configure the SDK's session with the HTTP client with TLS client
|
||||
// certificate support enabled. This session will be used to create all
|
||||
// SDK's API clients.
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
HTTPClient: &http.Client{
|
||||
Transport: tr,
|
||||
},
|
||||
})
|
||||
|
||||
// Create each API client will the session configured with the client TLS
|
||||
// certificate.
|
||||
svc := s3.New(sess)
|
||||
|
||||
resp, err := svc.ListBuckets(&s3.ListBucketsInput{})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to list buckets, %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("Buckets")
|
||||
fmt.Println(resp)
|
||||
}
|
||||
|
||||
func tlsConfigWithClientCert(clientCertFile, clientKeyFile, caFile string) (*tls.Config, error) {
|
||||
clientCert, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load certificat files, %s, %s, %v",
|
||||
clientCertFile, clientKeyFile, err)
|
||||
}
|
||||
|
||||
tlsCfg := &tls.Config{
|
||||
Certificates: []tls.Certificate{
|
||||
clientCert,
|
||||
},
|
||||
}
|
||||
|
||||
if len(caFile) != 0 {
|
||||
cert, err := ioutil.ReadFile(caFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load root CA file, %s, %v",
|
||||
caFile, err)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(cert)
|
||||
tlsCfg.RootCAs = caCertPool
|
||||
}
|
||||
|
||||
tlsCfg.BuildNameToCertificate()
|
||||
|
||||
return tlsCfg, nil
|
||||
}
|
||||
|
||||
func defaultHTTPTransport() *http.Transport {
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 10, // Increased idle connections per host
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Example
|
||||
|
||||
Demonstrates how the Go standard library `httptrace` can be used with the SDK
|
||||
to collect HTTP request tracing timing using the SDK's API operation methods
|
||||
like SNS's `PublishWithContext`.
|
||||
|
||||
The `trace.go` file demonstrates how the `httptrace` package's `ClientTrace`
|
||||
can be created to gather timing information from HTTP requests made.
|
||||
|
||||
The `logger.go` file demonstrates how the trace information can be combined to
|
||||
retrieve timing data for the different stages of the request.
|
||||
|
||||
The `config.go` file provides additional configuration settings to control how
|
||||
the HTTP client and its transport is configured. Such as, timeouts, and
|
||||
keepalive.
|
||||
|
||||
## Usage
|
||||
|
||||
Run the example providing your SNS topic's ARN as the `-topic` parameter. This
|
||||
example assumes that the region is provided via the environment variable and
|
||||
the AWS shared credentials file (~/.aws/credentials)'s `default` provide
|
||||
provides credentials.
|
||||
|
||||
```sh
|
||||
AWS_REGION=us-west-2 go run -tags example . -topic arn:aws:sns:us-west-2:0123456789:mytopicname
|
||||
```
|
||||
|
||||
Once the example starts you'll be prompted with a `Message:` statement. Input
|
||||
the message that you'd like to send to the topic on a single line and hit
|
||||
`enter` to send it.
|
||||
|
||||
```
|
||||
Message: My Really cool Message
|
||||
```
|
||||
|
||||
The example will output the http trace timing information for how long the request took.
|
||||
|
||||
```
|
||||
Latency: 79.863505ms ConnectionReused: false DNSStartAt: 280.452µs DNSDoneAt: 1.526342ms DNSDur: 1.24589ms ConnectStartAt: 1.533484ms ConnectDoneAt: 11.290792ms ConnectDur: 9.757308ms TLSStatAt: 11.331066ms TLSDoneAt: 33.912968ms TLSDur: 22.581902ms RequestWritten 34.951272ms RespFirstByte: 79.534808ms WaitRespFirstByte: 44.583536ms
|
||||
```
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewClient creates a new HTTP Client using the ClientConfig values.
|
||||
func NewClient(cfg ClientConfig) *http.Client {
|
||||
tr := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: cfg.Timeouts.Connect,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: cfg.Timeouts.IdleConnection,
|
||||
|
||||
DisableKeepAlives: !cfg.KeepAlive,
|
||||
TLSHandshakeTimeout: cfg.Timeouts.TLSHandshake,
|
||||
ExpectContinueTimeout: cfg.Timeouts.ExpectContinue,
|
||||
ResponseHeaderTimeout: cfg.Timeouts.ResponseHeader,
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClientConfig provides the timeouts from CLI flags and default values for the
|
||||
// example apps's configuration.
|
||||
type ClientConfig struct {
|
||||
KeepAlive bool
|
||||
Timeouts Timeouts
|
||||
}
|
||||
|
||||
// SetupFlags initializes the CLI flags.
|
||||
func (c *ClientConfig) SetupFlags(prefix string, flagset *flag.FlagSet) {
|
||||
prefix += "client."
|
||||
|
||||
flagset.BoolVar(&c.KeepAlive, prefix+"http-keep-alive", true,
|
||||
"Specifies if HTTP keep alive is enabled.")
|
||||
|
||||
c.Timeouts.SetupFlags(prefix, flagset)
|
||||
}
|
||||
|
||||
// Timeouts collection of HTTP client timeout values.
|
||||
type Timeouts struct {
|
||||
IdleConnection time.Duration
|
||||
Connect time.Duration
|
||||
TLSHandshake time.Duration
|
||||
ExpectContinue time.Duration
|
||||
ResponseHeader time.Duration
|
||||
}
|
||||
|
||||
// SetupFlags initializes the CLI flags.
|
||||
func (c *Timeouts) SetupFlags(prefix string, flagset *flag.FlagSet) {
|
||||
prefix += "timeout."
|
||||
|
||||
flagset.DurationVar(&c.IdleConnection, prefix+"idle-conn", 90*time.Second,
|
||||
"The `timeout` of idle connects to the remote host.")
|
||||
|
||||
flagset.DurationVar(&c.Connect, prefix+"connect", 30*time.Second,
|
||||
"The `timeout` connecting to the remote host.")
|
||||
|
||||
defTR := http.DefaultTransport.(*http.Transport)
|
||||
|
||||
flagset.DurationVar(&c.TLSHandshake, prefix+"tls", defTR.TLSHandshakeTimeout,
|
||||
"The `timeout` waiting for the TLS handshake to complete.")
|
||||
|
||||
c.ExpectContinue = defTR.ExpectContinueTimeout
|
||||
|
||||
flagset.DurationVar(&c.ResponseHeader, prefix+"response-header", defTR.ResponseHeaderTimeout,
|
||||
"The `timeout` waiting for the TLS handshake to complete.")
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
// build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RecordTrace outputs the request trace as text.
|
||||
func RecordTrace(w io.Writer, trace *RequestTrace) {
|
||||
attempt := AttemptReport{
|
||||
Reused: trace.Reused,
|
||||
Latency: trace.Finish.Sub(trace.Start),
|
||||
ReqWritten: trace.RequestWritten.Sub(trace.Start),
|
||||
}
|
||||
|
||||
if !trace.FirstResponseByte.IsZero() {
|
||||
attempt.RespFirstByte = trace.FirstResponseByte.Sub(trace.Start)
|
||||
attempt.WaitRespFirstByte = trace.FirstResponseByte.Sub(trace.RequestWritten)
|
||||
}
|
||||
|
||||
if !trace.Reused {
|
||||
attempt.DNSStart = trace.DNSStart.Sub(trace.Start)
|
||||
attempt.DNSDone = trace.DNSDone.Sub(trace.Start)
|
||||
attempt.DNS = trace.DNSDone.Sub(trace.DNSStart)
|
||||
|
||||
attempt.ConnectStart = trace.ConnectStart.Sub(trace.Start)
|
||||
attempt.ConnectDone = trace.ConnectDone.Sub(trace.Start)
|
||||
attempt.Connect = trace.ConnectDone.Sub(trace.ConnectStart)
|
||||
|
||||
attempt.TLSHandshakeStart = trace.TLSHandshakeStart.Sub(trace.Start)
|
||||
attempt.TLSHandshakeDone = trace.TLSHandshakeDone.Sub(trace.Start)
|
||||
attempt.TLSHandshake = trace.TLSHandshakeDone.Sub(trace.TLSHandshakeStart)
|
||||
}
|
||||
|
||||
_, err := fmt.Fprintln(w,
|
||||
"Latency:",
|
||||
attempt.Latency,
|
||||
|
||||
"ConnectionReused:",
|
||||
fmt.Sprintf("%t", attempt.Reused),
|
||||
|
||||
"DNSStartAt:",
|
||||
fmt.Sprintf("%s", attempt.DNSStart),
|
||||
"DNSDoneAt:",
|
||||
fmt.Sprintf("%s", attempt.DNSDone),
|
||||
"DNSDur:",
|
||||
fmt.Sprintf("%s", attempt.DNS),
|
||||
|
||||
"ConnectStartAt:",
|
||||
fmt.Sprintf("%s", attempt.ConnectStart),
|
||||
"ConnectDoneAt:",
|
||||
fmt.Sprintf("%s", attempt.ConnectDone),
|
||||
"ConnectDur:",
|
||||
fmt.Sprintf("%s", attempt.Connect),
|
||||
|
||||
"TLSStatAt:",
|
||||
fmt.Sprintf("%s", attempt.TLSHandshakeStart),
|
||||
"TLSDoneAt:",
|
||||
fmt.Sprintf("%s", attempt.TLSHandshakeDone),
|
||||
"TLSDur:",
|
||||
fmt.Sprintf("%s", attempt.TLSHandshake),
|
||||
|
||||
"RequestWritten",
|
||||
fmt.Sprintf("%s", attempt.ReqWritten),
|
||||
"RespFirstByte:",
|
||||
fmt.Sprintf("%s", attempt.RespFirstByte),
|
||||
"WaitRespFirstByte:",
|
||||
fmt.Sprintf("%s", attempt.WaitRespFirstByte),
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to write request trace, %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// AttemptReport proviedes the structured timing information.
|
||||
type AttemptReport struct {
|
||||
Latency time.Duration
|
||||
Reused bool
|
||||
Err error
|
||||
|
||||
DNSStart time.Duration
|
||||
DNSDone time.Duration
|
||||
DNS time.Duration
|
||||
|
||||
ConnectStart time.Duration
|
||||
ConnectDone time.Duration
|
||||
Connect time.Duration
|
||||
|
||||
TLSHandshakeStart time.Duration
|
||||
TLSHandshakeDone time.Duration
|
||||
TLSHandshake time.Duration
|
||||
|
||||
ReqWritten time.Duration
|
||||
RespFirstByte time.Duration
|
||||
WaitRespFirstByte time.Duration
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/sns"
|
||||
)
|
||||
|
||||
var clientCfg ClientConfig
|
||||
var topicARN string
|
||||
|
||||
func init() {
|
||||
clientCfg.SetupFlags("", flag.CommandLine)
|
||||
|
||||
flag.CommandLine.StringVar(&topicARN, "topic", "",
|
||||
"The topic `ARN` to send messages to")
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := flag.CommandLine.Parse(os.Args[1:]); err != nil {
|
||||
flag.CommandLine.PrintDefaults()
|
||||
exitErrorf(err, "failed to parse CLI commands")
|
||||
}
|
||||
if len(topicARN) == 0 {
|
||||
flag.CommandLine.PrintDefaults()
|
||||
exitErrorf(errors.New("topic ARN required"), "")
|
||||
}
|
||||
|
||||
httpClient := NewClient(clientCfg)
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
HTTPClient: httpClient,
|
||||
|
||||
// Disable Retries to prevent the httptrace's getting mixed up on
|
||||
// retries.
|
||||
MaxRetries: aws.Int(0),
|
||||
})
|
||||
if err != nil {
|
||||
exitErrorf(err, "failed to load config")
|
||||
}
|
||||
|
||||
// Start making the requests.
|
||||
svc := sns.New(sess)
|
||||
ctx := context.Background()
|
||||
|
||||
fmt.Printf("Message: ")
|
||||
|
||||
// Scan messages from the input with newline separators.
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
trace, err := publishMessage(ctx, svc, topicARN, scanner.Text())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to publish message, %v", err)
|
||||
}
|
||||
RecordTrace(os.Stdout, trace)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("Message: ")
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to read input, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// publishMessage will send the message to the SNS topic returning an request
|
||||
// trace for metrics.
|
||||
func publishMessage(ctx context.Context, svc *sns.SNS, topic, msg string) (*RequestTrace, error) {
|
||||
traceCtx := NewRequestTrace(ctx)
|
||||
defer traceCtx.RequestDone()
|
||||
|
||||
_, err := svc.PublishWithContext(traceCtx, &sns.PublishInput{
|
||||
TopicArn: &topic,
|
||||
Message: &msg,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return traceCtx, nil
|
||||
}
|
||||
|
||||
func exitErrorf(err error, msg string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, "FAILED: %v\n"+msg+"\n", append([]interface{}{err}, args...)...)
|
||||
os.Exit(1)
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http/httptrace"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RequestTrace provides the trace time stamps of the HTTP request's segments.
|
||||
type RequestTrace struct {
|
||||
context.Context
|
||||
|
||||
Start, Finish time.Time
|
||||
|
||||
Reused bool
|
||||
|
||||
DNSStart, DNSDone time.Time
|
||||
ConnectStart, ConnectDone time.Time
|
||||
TLSHandshakeStart, TLSHandshakeDone time.Time
|
||||
RequestWritten time.Time
|
||||
FirstResponseByte time.Time
|
||||
}
|
||||
|
||||
// NewRequestTrace returns a initialized RequestTrace for an
|
||||
// httptrace.ClientTrace, based on the context passed.
|
||||
func NewRequestTrace(ctx context.Context) *RequestTrace {
|
||||
rt := &RequestTrace{
|
||||
Start: time.Now(),
|
||||
}
|
||||
|
||||
trace := &httptrace.ClientTrace{
|
||||
GetConn: rt.getConn,
|
||||
GotConn: rt.gotConn,
|
||||
PutIdleConn: rt.putIdleConn,
|
||||
GotFirstResponseByte: rt.gotFirstResponseByte,
|
||||
Got100Continue: rt.got100Continue,
|
||||
DNSStart: rt.dnsStart,
|
||||
DNSDone: rt.dnsDone,
|
||||
ConnectStart: rt.connectStart,
|
||||
ConnectDone: rt.connectDone,
|
||||
TLSHandshakeStart: rt.tlsHandshakeStart,
|
||||
TLSHandshakeDone: rt.tlsHandshakeDone,
|
||||
WroteHeaders: rt.wroteHeaders,
|
||||
Wait100Continue: rt.wait100Continue,
|
||||
WroteRequest: rt.wroteRequest,
|
||||
}
|
||||
|
||||
rt.Context = httptrace.WithClientTrace(ctx, trace)
|
||||
|
||||
return rt
|
||||
}
|
||||
|
||||
// TotalLatency returns the total time the request took.
|
||||
func (rt *RequestTrace) TotalLatency() time.Duration {
|
||||
return rt.Finish.Sub(rt.Start)
|
||||
}
|
||||
|
||||
// RequestDone completes the request trace.
|
||||
func (rt *RequestTrace) RequestDone() {
|
||||
rt.Finish = time.Now()
|
||||
}
|
||||
|
||||
func (rt *RequestTrace) getConn(hostPort string) {}
|
||||
func (rt *RequestTrace) gotConn(info httptrace.GotConnInfo) {
|
||||
rt.Reused = info.Reused
|
||||
}
|
||||
func (rt *RequestTrace) putIdleConn(err error) {}
|
||||
func (rt *RequestTrace) gotFirstResponseByte() {
|
||||
rt.FirstResponseByte = time.Now()
|
||||
}
|
||||
func (rt *RequestTrace) got100Continue() {}
|
||||
func (rt *RequestTrace) dnsStart(info httptrace.DNSStartInfo) {
|
||||
rt.DNSStart = time.Now()
|
||||
}
|
||||
func (rt *RequestTrace) dnsDone(info httptrace.DNSDoneInfo) {
|
||||
rt.DNSDone = time.Now()
|
||||
}
|
||||
func (rt *RequestTrace) connectStart(network, addr string) {
|
||||
rt.ConnectStart = time.Now()
|
||||
}
|
||||
func (rt *RequestTrace) connectDone(network, addr string, err error) {
|
||||
rt.ConnectDone = time.Now()
|
||||
}
|
||||
func (rt *RequestTrace) tlsHandshakeStart() {
|
||||
rt.TLSHandshakeStart = time.Now()
|
||||
}
|
||||
func (rt *RequestTrace) tlsHandshakeDone(state tls.ConnectionState, err error) {
|
||||
rt.TLSHandshakeDone = time.Now()
|
||||
}
|
||||
func (rt *RequestTrace) wroteHeaders() {}
|
||||
func (rt *RequestTrace) wait100Continue() {}
|
||||
func (rt *RequestTrace) wroteRequest(info httptrace.WroteRequestInfo) {
|
||||
rt.RequestWritten = time.Now()
|
||||
}
|
||||
Generated
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
# AWS DynamoDB Transaction Error Aware Client for Go
|
||||
The client provides a workaround for [this bug](https://github.com/aws/aws-sdk-go/issues/2318)
|
||||
|
||||
## How to use
|
||||
This example shows how to use the client to read transaction error cancellation reasons.
|
||||
```go
|
||||
sess := session.Must(session.NewSession())
|
||||
svc := NewTxErrorAwareDynamoDBClient(sess)
|
||||
|
||||
input := &dynamodb.TransactWriteItemsInput{
|
||||
//...
|
||||
}
|
||||
|
||||
if _, err := svc.TransactWriteItems(input); err != nil {
|
||||
txErr := err.(TxRequestFailure)
|
||||
fmt.Println(txErr.CancellationReasons())
|
||||
}
|
||||
```
|
||||
|
||||
Sample response of the Println statement
|
||||
```
|
||||
{com.amazonaws.dynamodb.v20120810#TransactionCanceledException Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, None] [{
|
||||
Code: "ConditionalCheckFailed",
|
||||
Item: {
|
||||
AlbumTitle: {
|
||||
S: "========== 43"
|
||||
},
|
||||
Artist: {
|
||||
S: "Acme Band 14"
|
||||
},
|
||||
Year: {
|
||||
N: "2017"
|
||||
},
|
||||
SongTitle: {
|
||||
S: "Happy Day 12"
|
||||
}
|
||||
},
|
||||
Message: "The conditional request failed"
|
||||
} {
|
||||
Code: "None"
|
||||
} {
|
||||
Code: "None"
|
||||
}]}
|
||||
[{
|
||||
Code: "ConditionalCheckFailed",
|
||||
Item: {
|
||||
AlbumTitle: {
|
||||
S: "========== 43"
|
||||
},
|
||||
Artist: {
|
||||
S: "Acme Band 14"
|
||||
},
|
||||
Year: {
|
||||
N: "2017"
|
||||
},
|
||||
SongTitle: {
|
||||
S: "Happy Day 12"
|
||||
}
|
||||
},
|
||||
Message: "The conditional request failed"
|
||||
} {
|
||||
Code: "None"
|
||||
} {
|
||||
Code: "None"
|
||||
}]
|
||||
```
|
||||
Generated
Vendored
+121
@@ -0,0 +1,121 @@
|
||||
// +build example
|
||||
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/private/protocol/jsonrpc"
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
)
|
||||
|
||||
const TxAwareErrorUnmarshallerName = "awssdk.jsonrpc.TxAwareErrorUnmarshaller"
|
||||
|
||||
// New creates a new instance of the DynamoDB client with a session.
|
||||
// The client's behaviour is same as what is returned by dynamodb.New(), except for richer error reasons.
|
||||
func NewTxErrorAwareDynamoDBClient(p client.ConfigProvider, cfgs ...*aws.Config) *dynamodb.DynamoDB {
|
||||
c := dynamodb.New(p, cfgs...)
|
||||
// NOTE: Ignore if swap failed. Returning nil might fail app startup which is worse than inadequate error details.
|
||||
c.Handlers.UnmarshalError.Swap(jsonrpc.UnmarshalErrorHandler.Name, request.NamedHandler{
|
||||
Name: TxAwareErrorUnmarshallerName,
|
||||
Fn: TxAwareUnmarshalError,
|
||||
})
|
||||
return c
|
||||
}
|
||||
|
||||
// A RequestFailure is an interface to extract request failure information from an Error.
|
||||
type TxRequestFailure interface {
|
||||
awserr.RequestFailure
|
||||
CancellationReasons() []dynamodb.CancellationReason
|
||||
}
|
||||
|
||||
// TxAwareUnmarshalError unmarshals an error response for a JSON RPC service.
|
||||
// This is exactly same as jsonrpc.UnmarshalError, except for attempt to parse CancellationReasons
|
||||
func TxAwareUnmarshalError(req *request.Request) {
|
||||
defer req.HTTPResponse.Body.Close()
|
||||
|
||||
var jsonErr jsonTxErrorResponse
|
||||
err := json.NewDecoder(req.HTTPResponse.Body).Decode(&jsonErr)
|
||||
if err == io.EOF {
|
||||
req.Error = awserr.NewRequestFailure(
|
||||
awserr.New(request.ErrCodeSerialization, req.HTTPResponse.Status, nil),
|
||||
req.HTTPResponse.StatusCode,
|
||||
req.RequestID,
|
||||
)
|
||||
return
|
||||
} else if err != nil {
|
||||
req.Error = awserr.NewRequestFailure(
|
||||
awserr.New(request.ErrCodeSerialization,
|
||||
"failed decoding JSON RPC error response", err),
|
||||
req.HTTPResponse.StatusCode,
|
||||
req.RequestID,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
codes := strings.SplitN(jsonErr.Code, "#", 2)
|
||||
req.Error = newTxRequestError(
|
||||
awserr.New(codes[len(codes)-1], jsonErr.Message, nil),
|
||||
req.HTTPResponse.StatusCode,
|
||||
req.RequestID,
|
||||
jsonErr.CancellationReasons,
|
||||
)
|
||||
}
|
||||
|
||||
type jsonTxErrorResponse struct {
|
||||
Code string `json:"__type"`
|
||||
Message string `json:"message"`
|
||||
CancellationReasons []dynamodb.CancellationReason `json:"CancellationReasons"`
|
||||
}
|
||||
|
||||
// So that the Error interface type can be included as an anonymous field
|
||||
// in the requestError struct and not conflict with the error.Error() method.
|
||||
type awsError awserr.Error
|
||||
|
||||
// A TxRequestError wraps a request or service error.
|
||||
// TxRequestError is awserr.requestError with additional cancellationReasons field
|
||||
type txRequestError struct {
|
||||
awsError
|
||||
statusCode int
|
||||
requestID string
|
||||
cancellationReasons []dynamodb.CancellationReason
|
||||
}
|
||||
|
||||
func newTxRequestError(err awserr.Error, statusCode int, requestID string, cancellationReasons []dynamodb.CancellationReason) TxRequestFailure {
|
||||
return &txRequestError{
|
||||
awsError: err,
|
||||
statusCode: statusCode,
|
||||
requestID: requestID,
|
||||
cancellationReasons: cancellationReasons,
|
||||
}
|
||||
}
|
||||
|
||||
func (r txRequestError) Error() string {
|
||||
extra := fmt.Sprintf("status code: %d, request id: %s",
|
||||
r.statusCode, r.requestID)
|
||||
return awserr.SprintError(r.Code(), r.Message(), extra, r.OrigErr())
|
||||
}
|
||||
|
||||
func (r txRequestError) String() string {
|
||||
return r.Error()
|
||||
}
|
||||
|
||||
func (r txRequestError) StatusCode() int {
|
||||
return r.statusCode
|
||||
}
|
||||
|
||||
func (r txRequestError) RequestID() string {
|
||||
return r.requestID
|
||||
}
|
||||
|
||||
func (r txRequestError) CancellationReasons() []dynamodb.CancellationReason {
|
||||
return r.cancellationReasons
|
||||
}
|
||||
|
||||
Generated
Vendored
+171
@@ -0,0 +1,171 @@
|
||||
// +build example
|
||||
|
||||
package transaction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/awstesting/unit"
|
||||
)
|
||||
|
||||
const errStatusCode = 400
|
||||
const requestId = "requestId1"
|
||||
|
||||
func TestNewTxErrorAwareDynamoDBClient(t *testing.T) {
|
||||
sess := unit.Session
|
||||
svc := NewTxErrorAwareDynamoDBClient(sess)
|
||||
|
||||
if svc.Handlers.UnmarshalError.Len() != 1 {
|
||||
t.Errorf("expected 1 UnmarshallErrorHandler, got %v", svc.Handlers.UnmarshalError.Len())
|
||||
}
|
||||
if svc.Handlers.UnmarshalError.Swap(TxAwareErrorUnmarshallerName, request.NamedHandler{}) == false {
|
||||
t.Errorf("expected to contain %s, got none", TxAwareErrorUnmarshallerName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxAwareUnmarshalError(t *testing.T) {
|
||||
input := map[string]struct {
|
||||
enc interface{}
|
||||
err TxRequestFailure
|
||||
}{
|
||||
"Error response without CancellationReasons": {
|
||||
jsonErrorResponse{
|
||||
Code: "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException",
|
||||
Message: "Requested resource not found",
|
||||
},
|
||||
txRequestError{
|
||||
awsError: awserr.New("ResourceNotFoundException", "Requested resource not found", nil),
|
||||
statusCode: errStatusCode,
|
||||
requestID: requestId,
|
||||
cancellationReasons: nil,
|
||||
},
|
||||
},
|
||||
"Error response with empty CancellationReasons": {
|
||||
jsonTxErrorResponse{
|
||||
Code: "com.amazonaws.dynamodb.v20120810#TransactionCanceledException",
|
||||
Message: "Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, None]",
|
||||
CancellationReasons: []dynamodb.CancellationReason{},
|
||||
},
|
||||
txRequestError{
|
||||
awsError: awserr.New("TransactionCanceledException", "Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, None]", nil),
|
||||
statusCode: errStatusCode,
|
||||
requestID: requestId,
|
||||
cancellationReasons: []dynamodb.CancellationReason{},
|
||||
},
|
||||
},
|
||||
"Error response with non-empty CancellationReasons": {
|
||||
jsonTxErrorResponse{
|
||||
Code: "com.amazonaws.dynamodb.v20120810#TransactionCanceledException",
|
||||
Message: "Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, None]",
|
||||
CancellationReasons: []dynamodb.CancellationReason{
|
||||
{
|
||||
Code: aws.String("ConditionalCheckFailed"),
|
||||
Item: map[string]*dynamodb.AttributeValue{
|
||||
"hk": {S: aws.String("hkVal1")},
|
||||
"attr": {S: aws.String("attrVal1")},
|
||||
},
|
||||
Message: aws.String("The conditional request failed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
txRequestError{
|
||||
awsError: awserr.New("TransactionCanceledException", "Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, None]", nil),
|
||||
statusCode: errStatusCode,
|
||||
requestID: requestId,
|
||||
cancellationReasons: []dynamodb.CancellationReason{
|
||||
{
|
||||
Code: aws.String("ConditionalCheckFailed"),
|
||||
Item: map[string]*dynamodb.AttributeValue{
|
||||
"hk": {S: aws.String("hkVal1")},
|
||||
"attr": {S: aws.String("attrVal1")},
|
||||
},
|
||||
Message: aws.String("The conditional request failed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, in := range input {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if err := validateUnmarshallError(in.enc, in.err); err != nil {
|
||||
t.Errorf("%s: expected nil, got %v", name, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func validateUnmarshallError(enc interface{}, err TxRequestFailure) error {
|
||||
req := &request.Request{
|
||||
HTTPResponse: &http.Response{
|
||||
StatusCode: errStatusCode,
|
||||
Body: newBufferCloser(encode(enc)),
|
||||
},
|
||||
RequestID: requestId,
|
||||
}
|
||||
TxAwareUnmarshalError(req)
|
||||
|
||||
if aerr, ok := req.Error.(TxRequestFailure); ok {
|
||||
if err.RequestID() != aerr.RequestID() {
|
||||
return fmt.Errorf("expected %v, got %v", err.RequestID(), aerr.RequestID())
|
||||
}
|
||||
if err.StatusCode() != aerr.StatusCode() {
|
||||
return fmt.Errorf("expected %v, got %v", err.StatusCode(), aerr.StatusCode())
|
||||
}
|
||||
if err.Message() != aerr.Message() {
|
||||
return fmt.Errorf("expected %v, got %v", err.Message(), aerr.Message())
|
||||
}
|
||||
if err.Code() != aerr.Code() {
|
||||
return fmt.Errorf("expected %v, got %v", err.Code(), aerr.Code())
|
||||
}
|
||||
if err.OrigErr() != aerr.OrigErr() {
|
||||
return fmt.Errorf("expected %v, got %v", err.OrigErr(), aerr.OrigErr())
|
||||
}
|
||||
if !reflect.DeepEqual(err.Error(), aerr.Error()) {
|
||||
return fmt.Errorf("expected %v, got %v", err.Error(), aerr.Error())
|
||||
}
|
||||
if !reflect.DeepEqual(err.CancellationReasons(), aerr.CancellationReasons()) {
|
||||
return fmt.Errorf("expected %v, got %v", err.CancellationReasons(), aerr.CancellationReasons())
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("expected type 'TxRequestFailure', got %T", req.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func encode(v interface{}) []byte {
|
||||
var buf bytes.Buffer
|
||||
json.NewEncoder(&buf).Encode(&v)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Implementation of io.ReadCloser backed by bytes.Buffer
|
||||
type bufferCloser struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func newBufferCloser(data []byte) *bufferCloser {
|
||||
return &bufferCloser{*bytes.NewBuffer(data)}
|
||||
}
|
||||
|
||||
func (b *bufferCloser) Close() error {
|
||||
b.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Define error response without the cancellation reason
|
||||
type jsonErrorResponse struct {
|
||||
Code string `json:"__type"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
+2
-2
@@ -5,10 +5,10 @@ This is an example using the AWS SDK for Go to list ec2 instances that match pro
|
||||
|
||||
# Usage
|
||||
|
||||
The example uses the bucket name provided, and lists all object keys in a bucket.
|
||||
The example uses the tag name provided, and lists all matching ec2 instances.
|
||||
|
||||
```sh
|
||||
go run -tags example filter_ec2_by_tag.go <name_filter>
|
||||
go run filter_ec2_by_tag.go <name_filter>
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
|
||||
# Example Fetch By region
|
||||
|
||||
This is an example using the AWS SDK for Go to list ec2 instances instance state By different region . By default it fetch all running and stopped instance
|
||||
This is an example using the AWS SDK for Go to list ec2 instances instance state By different region . By default it fetch all running and stopped instance
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -49,7 +49,7 @@ func main() {
|
||||
ec2Svc := ec2.New(sess)
|
||||
params := &ec2.DescribeInstancesInput{
|
||||
Filters: []*ec2.Filter{
|
||||
&ec2.Filter{
|
||||
{
|
||||
Name: aws.String("instance-state-name"),
|
||||
Values: aws.StringSlice(states),
|
||||
},
|
||||
|
||||
vendor/github.com/aws/aws-sdk-go/example/service/mediastoredata/streamingNonSeekableReader/README.md
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
# Example
|
||||
|
||||
This is an example demonstrates how you can use the AWS Elemental MediaStore
|
||||
API PutObject operation with a non-seekable io.Reader.
|
||||
|
||||
# Usage
|
||||
|
||||
The example will create an Elemental MediaStore container, and upload a
|
||||
contrived non-seekable io.Reader to that container. Using the SDK's
|
||||
[aws.ReadSeekCloser](https://docs.aws.amazon.com/sdk-for-go/api/aws/#ReadSeekCloser)
|
||||
utility for wrapping the `io.Reader` in a value the
|
||||
[mediastore#PutObjectInput](https://docs.aws.amazon.com/sdk-for-go/api/service/mediastoredata/#PutObjectInput).Body will accept.
|
||||
|
||||
The example will attempt to create the container if it does not already exist.
|
||||
|
||||
```sh
|
||||
AWS_REGION=<region> go run -tags example main.go <containerName> <object-path>
|
||||
Generated
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
// +build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/mediastore"
|
||||
"github.com/aws/aws-sdk-go/service/mediastoredata"
|
||||
)
|
||||
|
||||
func main() {
|
||||
containerName := os.Args[1]
|
||||
objectPath := os.Args[2]
|
||||
|
||||
// Create the SDK's session, and a AWS Elemental MediaStore Data client.
|
||||
sess := session.Must(session.NewSession())
|
||||
dataSvc, err := getMediaStoreDataClient(containerName, sess)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create client, %v", err)
|
||||
}
|
||||
|
||||
// Create a random reader to simulate a unseekable reader, wrap the reader
|
||||
// in an io.LimitReader to prevent uploading forever.
|
||||
randReader := rand.New(rand.NewSource(0))
|
||||
reader := io.LimitReader(randReader, 1024*1024 /* 1MB */)
|
||||
|
||||
// Wrap the unseekable reader with the SDK's RandSeekCloser. This type will
|
||||
// allow the SDK's to use the nonseekable reader.
|
||||
body := aws.ReadSeekCloser(reader)
|
||||
|
||||
// make the PutObject API call with the nonseekable reader, causing the SDK
|
||||
// to send the request body payload as chunked transfer encoding.
|
||||
_, err = dataSvc.PutObject(&mediastoredata.PutObjectInput{
|
||||
Path: &objectPath,
|
||||
Body: body,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to upload object, %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("object uploaded")
|
||||
}
|
||||
|
||||
// getMediaStoreDataClient uses the AWS Elemental MediaStore API to get the
|
||||
// endpoint for a container. If the container endpoint can be retrieved a AWS
|
||||
// Elemental MediaStore Data client will be created and returned. Otherwise
|
||||
// error is returned.
|
||||
func getMediaStoreDataClient(containerName string, sess *session.Session) (*mediastoredata.MediaStoreData, error) {
|
||||
endpoint, err := containerEndpoint(containerName, sess)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataSvc := mediastoredata.New(sess, &aws.Config{
|
||||
Endpoint: endpoint,
|
||||
})
|
||||
|
||||
return dataSvc, nil
|
||||
}
|
||||
|
||||
// ContainerEndpoint will attempt to get the endpoint for a container,
|
||||
// returning error if the container doesn't exist, or is not active within a
|
||||
// timeout.
|
||||
func containerEndpoint(name string, sess *session.Session) (*string, error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
ctrlSvc := mediastore.New(sess)
|
||||
descResp, err := ctrlSvc.DescribeContainer(&mediastore.DescribeContainerInput{
|
||||
ContainerName: &name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if status := aws.StringValue(descResp.Container.Status); status != "ACTIVE" {
|
||||
log.Println("waiting for container to be active, ", status)
|
||||
time.Sleep(10 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
return descResp.Container.Endpoint, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("container is not active")
|
||||
}
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
# Example
|
||||
|
||||
This is an example using the AWS SDK for Go to download an S3 object with a
|
||||
progress bar.
|
||||
|
||||
# Usage
|
||||
|
||||
The example uses the bucket name provided, one key for object, and output the
|
||||
progress to stdout.
|
||||
|
||||
```prompt
|
||||
AWS_PROFILE=my-profile AWS_REGION=us-west-2 go run -tags example getObjectWithProgress.go cool-bucket my/object/prefix/cool_thing.zip
|
||||
|
||||
2019/02/22 13:04:52 File size is: 35.9 MB
|
||||
2019/02/22 13:04:53 File size:35943530 downloaded:8580 percentage:0%
|
||||
2019/02/22 13:04:53 File size:35943530 downloaded:17580 percentage:0%
|
||||
2019/02/22 13:04:53 File size:35943530 downloaded:33940 percentage:0%
|
||||
2019/02/22 13:04:53 File size:35943530 downloaded:34988 percentage:0%
|
||||
2019/02/22 13:04:53 File size:35943530 downloaded:51348 percentage:0%
|
||||
2019/02/22 13:04:53 File size:35943530 downloaded:52396 percentage:0%
|
||||
...
|
||||
```
|
||||
Generated
Vendored
+133
@@ -0,0 +1,133 @@
|
||||
// +build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
)
|
||||
|
||||
// progressWriter tracks the download progress of a file from S3 to a file
|
||||
// as the writeAt method is called, the byte size is added to the written total,
|
||||
// and then a log is printed of the written percentage from the total size
|
||||
// it looks like this on the command line:
|
||||
// 2019/02/22 12:59:15 File size:35943530 downloaded:16360 percentage:0%
|
||||
// 2019/02/22 12:59:15 File size:35943530 downloaded:16988 percentage:0%
|
||||
// 2019/02/22 12:59:15 File size:35943530 downloaded:33348 percentage:0%
|
||||
type progressWriter struct {
|
||||
written int64
|
||||
writer io.WriterAt
|
||||
size int64
|
||||
}
|
||||
|
||||
func (pw *progressWriter) WriteAt(p []byte, off int64) (int, error) {
|
||||
atomic.AddInt64(&pw.written, int64(len(p)))
|
||||
|
||||
percentageDownloaded := float32(pw.written*100) / float32(pw.size)
|
||||
|
||||
fmt.Printf("File size:%d downloaded:%d percentage:%.2f%%\r", pw.size, pw.written, percentageDownloaded)
|
||||
|
||||
return pw.writer.WriteAt(p, off)
|
||||
}
|
||||
|
||||
func byteCountDecimal(b int64) string {
|
||||
const unit = 1000
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
|
||||
}
|
||||
|
||||
func getFileSize(svc *s3.S3, bucket string, prefix string) (filesize int64, error error) {
|
||||
params := &s3.HeadObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(prefix),
|
||||
}
|
||||
|
||||
resp, err := svc.HeadObject(params)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return *resp.ContentLength, nil
|
||||
}
|
||||
|
||||
func parseFilename(keyString string) (filename string) {
|
||||
ss := strings.Split(keyString, "/")
|
||||
s := ss[len(ss)-1]
|
||||
return s
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
log.Println("USAGE ERROR: AWS_REGION=us-east-1 go run getObjWithProgress.go bucket-name object-key")
|
||||
return
|
||||
}
|
||||
|
||||
bucket := os.Args[1]
|
||||
key := os.Args[2]
|
||||
|
||||
filename := parseFilename(key)
|
||||
|
||||
sess, err := session.NewSession()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s3Client := s3.New(sess)
|
||||
downloader := s3manager.NewDownloader(sess)
|
||||
size, err := getFileSize(s3Client, bucket, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Println("Starting download, size:", byteCountDecimal(size))
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
temp, err := ioutil.TempFile(cwd, "getObjWithProgress-tmp-")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tempfileName := temp.Name()
|
||||
|
||||
writer := &progressWriter{writer: temp, size: size, written: 0}
|
||||
params := &s3.GetObjectInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
}
|
||||
|
||||
if _, err := downloader.Download(writer, params); err != nil {
|
||||
log.Printf("Download failed! Deleting tempfile: %s", tempfileName)
|
||||
os.Remove(tempfileName)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := temp.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := os.Rename(temp.Name(), filename); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
log.Println("File downloaded! Avaliable at:", filename)
|
||||
}
|
||||
+6
-6
@@ -24,11 +24,11 @@ AWS credentials. See the [`Configuring Credentials`](http://docs.aws.amazon.com/
|
||||
section of the SDK's API Reference guide on how the SDK loads your AWS credentials.
|
||||
|
||||
The server requires the S3 `-b bucket` the presigned URLs will be generated for. A
|
||||
`-r region` is only needed if the bucket is in AWS China or AWS Gov Cloud. For
|
||||
`-r region` is only needed if the bucket is in AWS China or AWS Gov Cloud. For
|
||||
buckets in AWS the server will use the [`s3manager.GetBucketRegion`](http://docs.aws.amazon.com/sdk-for-go/api/service/s3/s3manager/#GetBucketRegion) utility to lookup the bucket's region.
|
||||
|
||||
You should run the service in the background or in a separate terminal tab before
|
||||
moving onto the client.
|
||||
moving onto the client.
|
||||
|
||||
|
||||
```sh
|
||||
@@ -43,7 +43,7 @@ defaults.
|
||||
|
||||
Use the client application to request a presigned URL from the server and use
|
||||
that presigned URL to download the object from S3. Calling the client with the
|
||||
`-get key` flag will do this. An optional `-f filename` flag can be provided as
|
||||
`-get key` flag will do this. An optional `-f filename` flag can be provided as
|
||||
well to write the object to. If no flag is provided the object will be written
|
||||
to `stdout`
|
||||
|
||||
@@ -63,7 +63,7 @@ URL. The `method` value can be `GET` or `PUT` for the `GetObject` or `PutObject`
|
||||
curl -v "http://127.0.0.1:8080/presign/my-object/key?method=GET"
|
||||
```
|
||||
|
||||
The server will respond with a JSON value. The value contains three pieces of
|
||||
The server will respond with a JSON value. The value contains three pieces of
|
||||
information that the client will need to correctly make the request. First is
|
||||
the presigned URL. This is the URL the client will make the request to. Second
|
||||
is the HTTP method the request should be sent as. This is included to simplify
|
||||
@@ -97,7 +97,7 @@ service
|
||||
go run -tags example client/client.go -put "my-object/key" -f filename
|
||||
```
|
||||
|
||||
Like the download case this will make a HTTP request to the server for the
|
||||
Like the download case this will make a HTTP request to the server for the
|
||||
presigned URL. The Server will respond with a presigned URL for S3's `PutObject`
|
||||
API operation. In addition the `method` query parameter the client will also
|
||||
include a `contentLength` this value instructs the server to generate the presigned
|
||||
@@ -118,7 +118,7 @@ such as additional constraints the server puts on the presigned URLs like
|
||||
`Content-Type`.
|
||||
|
||||
In addition to adding constraints to the presigned URLs the service could be
|
||||
updated to obfuscate S3 object's key. Instead of the client knowing the object's
|
||||
updated to obfuscate S3 object's key. Instead of the client knowing the object's
|
||||
key, a lookup system could be used instead. This could be substitution based,
|
||||
or lookup into an external data store such as DynamoDB.
|
||||
|
||||
|
||||
+4
-4
@@ -19,10 +19,10 @@ putBucketAcl <params>
|
||||
```
|
||||
|
||||
```sh
|
||||
go run -tags example putObjectAcl.go
|
||||
-bucket <bucket>
|
||||
-key <key>
|
||||
-owner-name <name>
|
||||
go run -tags example putObjectAcl.go
|
||||
-bucket <bucket>
|
||||
-key <key>
|
||||
-owner-name <name>
|
||||
-owner-id <id>
|
||||
-grantee-type <some type>
|
||||
-user-id <user-id>
|
||||
|
||||
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
# Example
|
||||
|
||||
This is an example using the AWS SDK for Go to upload object with progress.
|
||||
We use CustomReader to implement it
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
The example uses the bucket name provided, one key for object, and output the progress to stdout.
|
||||
The Object size should larger than 5M or your will not see the progress
|
||||
|
||||
```sh
|
||||
AWS_REGION=<region> go run putObjWithProcess.go <credential> <bucket> <key for object> <local file name>
|
||||
```
|
||||
Generated
Vendored
+102
@@ -0,0 +1,102 @@
|
||||
// +build example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
)
|
||||
|
||||
type CustomReader struct {
|
||||
fp *os.File
|
||||
size int64
|
||||
read int64
|
||||
}
|
||||
|
||||
func (r *CustomReader) Read(p []byte) (int, error) {
|
||||
return r.fp.Read(p)
|
||||
}
|
||||
|
||||
func (r *CustomReader) ReadAt(p []byte, off int64) (int, error) {
|
||||
n, err := r.fp.ReadAt(p, off)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Got the length have read( or means has uploaded), and you can construct your message
|
||||
atomic.AddInt64(&r.read, int64(n))
|
||||
|
||||
// I have no idea why the read length need to be div 2,
|
||||
// maybe the request read once when Sign and actually send call ReadAt again
|
||||
// It works for me
|
||||
log.Printf("total read:%d progress:%d%%\n", r.read/2, int(float32(r.read*100/2)/float32(r.size)))
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *CustomReader) Seek(offset int64, whence int) (int64, error) {
|
||||
return r.fp.Seek(offset, whence)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 4 {
|
||||
log.Println("USAGE ERROR: AWS_REGION=us-east-1 go run putObjWithProcess.go <credential> <bucket> <key for object> <local file name>")
|
||||
return
|
||||
}
|
||||
|
||||
credential := os.Args[1]
|
||||
bucket := os.Args[2]
|
||||
key := os.Args[3]
|
||||
fileName := os.Args[4]
|
||||
|
||||
creds := credentials.NewSharedCredentials(credential, "default")
|
||||
if _, err := creds.Get(); err != nil {
|
||||
log.Println("ERROR:", err)
|
||||
return
|
||||
}
|
||||
|
||||
sess := session.New(&aws.Config{
|
||||
Credentials: creds,
|
||||
})
|
||||
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
log.Println("ERROR:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
log.Println("ERROR:", err)
|
||||
return
|
||||
}
|
||||
|
||||
reader := &CustomReader{
|
||||
fp: file,
|
||||
size: fileInfo.Size(),
|
||||
}
|
||||
|
||||
uploader := s3manager.NewUploader(sess, func(u *s3manager.Uploader) {
|
||||
u.PartSize = 5 * 1024 * 1024
|
||||
u.LeavePartsOnError = true
|
||||
})
|
||||
|
||||
output, err := uploader.Upload(&s3manager.UploadInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Key: aws.String(key),
|
||||
Body: reader,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println("ERROR:", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println(output.Location)
|
||||
}
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
sync will upload a given directory to Amazon S3 using the upload iterator interface defined in the
|
||||
s3manager package. This example uses a path that is specified during runtime to walk and build keys
|
||||
to upload to Amazon S3. It will use the keys to upload the files/folders to Amazon S3.
|
||||
to upload to Amazon S3. It will use the keys to upload the files/folders to Amazon S3.
|
||||
|
||||
# Usage
|
||||
|
||||
@@ -14,7 +14,7 @@ sync <params>
|
||||
```
|
||||
|
||||
```sh
|
||||
go run -tags example sync.go
|
||||
go run -tags example sync.go
|
||||
-region <region> // required
|
||||
-bucket <bucket> // required
|
||||
-path <path> // required
|
||||
|
||||
+15
-7
@@ -5,6 +5,7 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -69,15 +70,22 @@ func (iter *SyncFolderIterator) UploadObject() s3manager.BatchUploadObject {
|
||||
iter.err = err
|
||||
}
|
||||
|
||||
extension := filepath.Ext(fi.key)
|
||||
mimeType := mime.TypeByExtension(extension)
|
||||
|
||||
if mimeType == "" {
|
||||
mimeType = "binary/octet-stream"
|
||||
}
|
||||
|
||||
input := s3manager.UploadInput{
|
||||
Bucket: &iter.bucket,
|
||||
Key: &fi.key,
|
||||
Body: body,
|
||||
Bucket: &iter.bucket,
|
||||
Key: &fi.key,
|
||||
Body: body,
|
||||
ContentType: &mimeType,
|
||||
}
|
||||
|
||||
return s3manager.BatchUploadObject{
|
||||
&input,
|
||||
nil,
|
||||
Object: &input,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,11 +109,11 @@ func main() {
|
||||
|
||||
iter := NewSyncFolderIterator(*pathPtr, *bucketPtr)
|
||||
if err := uploader.UploadWithIterator(aws.BackgroundContext(), iter); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unexpected error has occured: %v", err)
|
||||
fmt.Fprintf(os.Stderr, "unexpected error has occurred: %v", err)
|
||||
}
|
||||
|
||||
if err := iter.Err(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unexpected error occured during file walking: %v", err)
|
||||
fmt.Fprintf(os.Stderr, "unexpected error occurred during file walking: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("Success")
|
||||
|
||||
Reference in New Issue
Block a user