Merge branch 'master' into master
This commit is contained in:
commit
1ea5a547e2
|
@ -0,0 +1,71 @@
|
||||||
|
// component_acc_test.go should contain acceptance tests for plugin components
|
||||||
|
// to make sure all component types can be discovered and started.
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
amazonacc "github.com/hashicorp/packer-plugin-amazon/builder/ebs/acceptance"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||||
|
"github.com/hashicorp/packer/hcl2template/addrs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed test-fixtures/basic-amazon-ami-datasource.pkr.hcl
|
||||||
|
var basicAmazonAmiDatasourceHCL2Template string
|
||||||
|
|
||||||
|
func TestAccInitAndBuildBasicAmazonAmiDatasource(t *testing.T) {
|
||||||
|
plugin := addrs.Plugin{
|
||||||
|
Hostname: "github.com",
|
||||||
|
Namespace: "hashicorp",
|
||||||
|
Type: "amazon",
|
||||||
|
}
|
||||||
|
testCase := &acctest.PluginTestCase{
|
||||||
|
Name: "amazon-ami_basic_datasource_test",
|
||||||
|
Setup: func() error {
|
||||||
|
return cleanupPluginInstallation(plugin)
|
||||||
|
},
|
||||||
|
Teardown: func() error {
|
||||||
|
helper := amazonacc.AWSHelper{
|
||||||
|
Region: "us-west-2",
|
||||||
|
AMIName: "packer-amazon-ami-test",
|
||||||
|
}
|
||||||
|
return helper.CleanUpAmi()
|
||||||
|
},
|
||||||
|
Template: basicAmazonAmiDatasourceHCL2Template,
|
||||||
|
Type: "amazon-ami",
|
||||||
|
Init: true,
|
||||||
|
CheckInit: func(initCommand *exec.Cmd, logfile string) error {
|
||||||
|
if initCommand.ProcessState != nil {
|
||||||
|
if initCommand.ProcessState.ExitCode() != 0 {
|
||||||
|
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logs, err := os.Open(logfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable find %s", logfile)
|
||||||
|
}
|
||||||
|
defer logs.Close()
|
||||||
|
|
||||||
|
logsBytes, err := ioutil.ReadAll(logs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to read %s", logfile)
|
||||||
|
}
|
||||||
|
initOutput := string(logsBytes)
|
||||||
|
return checkPluginInstallation(initOutput, plugin)
|
||||||
|
},
|
||||||
|
Check: func(buildCommand *exec.Cmd, logfile string) error {
|
||||||
|
if buildCommand.ProcessState != nil {
|
||||||
|
if buildCommand.ProcessState.ExitCode() != 0 {
|
||||||
|
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
acctest.TestPlugin(t, testCase)
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
// plugin_acc_test.go should contain acceptance tests for features related to
|
||||||
|
// installing, discovering and running plugins.
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
amazonacc "github.com/hashicorp/packer-plugin-amazon/builder/ebs/acceptance"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/acctest/testutils"
|
||||||
|
"github.com/hashicorp/packer/hcl2template/addrs"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed test-fixtures/basic-amazon-ebs.pkr.hcl
|
||||||
|
var basicAmazonEbsHCL2Template string
|
||||||
|
|
||||||
|
func TestAccInitAndBuildBasicAmazonEbs(t *testing.T) {
|
||||||
|
plugin := addrs.Plugin{
|
||||||
|
Hostname: "github.com",
|
||||||
|
Namespace: "hashicorp",
|
||||||
|
Type: "amazon",
|
||||||
|
}
|
||||||
|
testCase := &acctest.PluginTestCase{
|
||||||
|
Name: "amazon-ebs_basic_plugin_init_and_build_test",
|
||||||
|
Setup: func() error {
|
||||||
|
return cleanupPluginInstallation(plugin)
|
||||||
|
},
|
||||||
|
Teardown: func() error {
|
||||||
|
helper := amazonacc.AWSHelper{
|
||||||
|
Region: "us-east-1",
|
||||||
|
AMIName: "packer-plugin-amazon-ebs-test",
|
||||||
|
}
|
||||||
|
return helper.CleanUpAmi()
|
||||||
|
},
|
||||||
|
Template: basicAmazonEbsHCL2Template,
|
||||||
|
Type: "amazon-ebs",
|
||||||
|
Init: true,
|
||||||
|
CheckInit: func(initCommand *exec.Cmd, logfile string) error {
|
||||||
|
if initCommand.ProcessState != nil {
|
||||||
|
if initCommand.ProcessState.ExitCode() != 0 {
|
||||||
|
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logs, err := os.Open(logfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable find %s", logfile)
|
||||||
|
}
|
||||||
|
defer logs.Close()
|
||||||
|
|
||||||
|
logsBytes, err := ioutil.ReadAll(logs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to read %s", logfile)
|
||||||
|
}
|
||||||
|
initOutput := string(logsBytes)
|
||||||
|
return checkPluginInstallation(initOutput, plugin)
|
||||||
|
},
|
||||||
|
Check: func(buildCommand *exec.Cmd, logfile string) error {
|
||||||
|
if buildCommand.ProcessState != nil {
|
||||||
|
if buildCommand.ProcessState.ExitCode() != 0 {
|
||||||
|
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
acctest.TestPlugin(t, testCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupPluginInstallation(plugin addrs.Plugin) error {
|
||||||
|
home, err := homedir.Dir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pluginPath := filepath.Join(home,
|
||||||
|
".packer.d",
|
||||||
|
"plugins",
|
||||||
|
plugin.Hostname,
|
||||||
|
plugin.Namespace,
|
||||||
|
plugin.Type)
|
||||||
|
testutils.CleanupFiles(pluginPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPluginInstallation(initOutput string, plugin addrs.Plugin) error {
|
||||||
|
expectedInitLog := "Installed plugin " + plugin.String()
|
||||||
|
if matched, _ := regexp.MatchString(expectedInitLog+".*", initOutput); !matched {
|
||||||
|
return fmt.Errorf("logs doesn't contain expected foo value %q", initOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
home, err := homedir.Dir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pluginPath := filepath.Join(home,
|
||||||
|
".packer.d",
|
||||||
|
"plugins",
|
||||||
|
plugin.Hostname,
|
||||||
|
plugin.Namespace,
|
||||||
|
plugin.Type)
|
||||||
|
if !testutils.FileExists(pluginPath) {
|
||||||
|
return fmt.Errorf("%s plugin installation not found", plugin.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
amazon = {
|
||||||
|
version = ">= 0.0.1"
|
||||||
|
source = "github.com/hashicorp/amazon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data "amazon-ami" "test" {
|
||||||
|
filters = {
|
||||||
|
name = "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*"
|
||||||
|
root-device-type = "ebs"
|
||||||
|
virtualization-type = "hvm"
|
||||||
|
}
|
||||||
|
most_recent = true
|
||||||
|
owners = ["099720109477"]
|
||||||
|
}
|
||||||
|
|
||||||
|
source "amazon-ebs" "basic-example" {
|
||||||
|
region = "us-west-2"
|
||||||
|
source_ami = data.amazon-ami.test.id
|
||||||
|
ami_name = "packer-amazon-ami-test"
|
||||||
|
communicator = "ssh"
|
||||||
|
instance_type = "t2.micro"
|
||||||
|
ssh_username = "ubuntu"
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
sources = [
|
||||||
|
"source.amazon-ebs.basic-example"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
packer {
|
||||||
|
required_plugins {
|
||||||
|
amazon = {
|
||||||
|
version = ">= 0.0.1"
|
||||||
|
source = "github.com/hashicorp/amazon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source "amazon-ebs" "basic-test" {
|
||||||
|
region = "us-east-1"
|
||||||
|
instance_type = "m3.medium"
|
||||||
|
source_ami = "ami-76b2a71e"
|
||||||
|
ssh_username = "ubuntu"
|
||||||
|
ami_name = "packer-plugin-amazon-ebs-test"
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
sources = ["source.amazon-ebs.basic-test"]
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package acctest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testTesting = true
|
||||||
|
|
||||||
|
if err := os.Setenv(TestEnvVar, "1"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTest_noEnv(t *testing.T) {
|
||||||
|
// Unset the variable
|
||||||
|
if err := os.Setenv(TestEnvVar, ""); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Setenv(TestEnvVar, "1")
|
||||||
|
|
||||||
|
mt := new(mockT)
|
||||||
|
Test(mt, TestCase{})
|
||||||
|
|
||||||
|
if !mt.SkipCalled {
|
||||||
|
t.Fatal("skip not called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTest_preCheck(t *testing.T) {
|
||||||
|
called := false
|
||||||
|
|
||||||
|
mt := new(mockT)
|
||||||
|
Test(mt, TestCase{
|
||||||
|
PreCheck: func() { called = true },
|
||||||
|
})
|
||||||
|
|
||||||
|
if !called {
|
||||||
|
t.Fatal("precheck should be called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mockT implements TestT for testing
|
||||||
|
type mockT struct {
|
||||||
|
ErrorCalled bool
|
||||||
|
ErrorArgs []interface{}
|
||||||
|
FatalCalled bool
|
||||||
|
FatalArgs []interface{}
|
||||||
|
SkipCalled bool
|
||||||
|
SkipArgs []interface{}
|
||||||
|
|
||||||
|
f bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mockT) Error(args ...interface{}) {
|
||||||
|
t.ErrorCalled = true
|
||||||
|
t.ErrorArgs = args
|
||||||
|
t.f = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mockT) Fatal(args ...interface{}) {
|
||||||
|
t.FatalCalled = true
|
||||||
|
t.FatalArgs = args
|
||||||
|
t.f = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mockT) Skip(args ...interface{}) {
|
||||||
|
t.SkipCalled = true
|
||||||
|
t.SkipArgs = args
|
||||||
|
t.f = true
|
||||||
|
}
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultTestRegion = "cn-beijing"
|
const defaultTestRegion = "cn-beijing"
|
||||||
|
|
|
@ -30,8 +30,8 @@ import (
|
||||||
|
|
||||||
"github.com/Azure/go-autorest/autorest"
|
"github.com/Azure/go-autorest/autorest"
|
||||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
||||||
|
|
|
@ -181,7 +181,6 @@ func NewAzureClient(subscriptionID, resourceGroupName string,
|
||||||
azureClient.GalleryImageVersionsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
azureClient.GalleryImageVersionsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
|
||||||
azureClient.GalleryImageVersionsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.GalleryImageVersionsClient.UserAgent)
|
azureClient.GalleryImageVersionsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(version.AzurePluginVersion.FormattedVersion()), azureClient.GalleryImageVersionsClient.UserAgent)
|
||||||
azureClient.GalleryImageVersionsClient.Client.PollingDuration = SharedGalleryTimeout
|
azureClient.GalleryImageVersionsClient.Client.PollingDuration = SharedGalleryTimeout
|
||||||
azureClient.GalleryImageVersionsClient.Client.PollingDuration = PollingDuration
|
|
||||||
|
|
||||||
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||||
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||||
|
|
|
@ -28,7 +28,7 @@ package dtl
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
||||||
|
|
|
@ -80,10 +80,17 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
|
||||||
|
|
||||||
// Build the steps
|
// Build the steps
|
||||||
steps := []multistep.Step{
|
steps := []multistep.Step{
|
||||||
&stepCreateSSHKey{
|
&communicator.StepSSHKeyGen{
|
||||||
Debug: b.config.PackerDebug,
|
CommConf: &b.config.Comm,
|
||||||
DebugKeyPath: fmt.Sprintf("do_%s.pem", b.config.PackerBuildName),
|
SSHTemporaryKeyPair: b.config.Comm.SSH.SSHTemporaryKeyPair,
|
||||||
},
|
},
|
||||||
|
multistep.If(b.config.PackerDebug && b.config.Comm.SSHPrivateKeyFile == "",
|
||||||
|
&communicator.StepDumpSSHKey{
|
||||||
|
Path: fmt.Sprintf("do_%s.pem", b.config.PackerBuildName),
|
||||||
|
SSH: &b.config.Comm.SSH,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
&stepCreateSSHKey{},
|
||||||
new(stepCreateDroplet),
|
new(stepCreateDroplet),
|
||||||
new(stepDropletInfo),
|
new(stepDropletInfo),
|
||||||
&communicator.StepConnect{
|
&communicator.StepConnect{
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
"github.com/digitalocean/godo"
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,26 +2,16 @@ package digitalocean
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/digitalocean/godo"
|
"github.com/digitalocean/godo"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type stepCreateSSHKey struct {
|
type stepCreateSSHKey struct {
|
||||||
Debug bool
|
|
||||||
DebugKeyPath string
|
|
||||||
|
|
||||||
keyId int
|
keyId int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,31 +20,12 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
ui := state.Get("ui").(packersdk.Ui)
|
||||||
c := state.Get("config").(*Config)
|
c := state.Get("config").(*Config)
|
||||||
|
|
||||||
ui.Say("Creating temporary ssh key for droplet...")
|
if c.Comm.SSHPublicKey == nil {
|
||||||
|
ui.Say("No public SSH key found; skipping SSH public key import...")
|
||||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
return multistep.ActionContinue
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("error generating RSA key: %s", err)
|
|
||||||
state.Put("error", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ASN.1 DER encoded form
|
ui.Say("Importing SSH public key...")
|
||||||
priv_der := x509.MarshalPKCS1PrivateKey(priv)
|
|
||||||
priv_blk := pem.Block{
|
|
||||||
Type: "RSA PRIVATE KEY",
|
|
||||||
Headers: nil,
|
|
||||||
Bytes: priv_der,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the private key in the config for later
|
|
||||||
c.Comm.SSHPrivateKey = pem.EncodeToMemory(&priv_blk)
|
|
||||||
|
|
||||||
// Marshal the public key into SSH compatible format
|
|
||||||
// TODO properly handle the public key error
|
|
||||||
pub, _ := ssh.NewPublicKey(&priv.PublicKey)
|
|
||||||
pub_sshformat := string(ssh.MarshalAuthorizedKey(pub))
|
|
||||||
|
|
||||||
// The name of the public key on DO
|
// The name of the public key on DO
|
||||||
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
name := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||||
|
@ -62,7 +33,7 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
|
||||||
// Create the key!
|
// Create the key!
|
||||||
key, _, err := client.Keys.Create(context.TODO(), &godo.KeyCreateRequest{
|
key, _, err := client.Keys.Create(context.TODO(), &godo.KeyCreateRequest{
|
||||||
Name: name,
|
Name: name,
|
||||||
PublicKey: pub_sshformat,
|
PublicKey: string(c.Comm.SSHPublicKey),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
err := fmt.Errorf("Error creating temporary SSH key: %s", err)
|
||||||
|
@ -79,31 +50,6 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
|
||||||
// Remember some state for the future
|
// Remember some state for the future
|
||||||
state.Put("ssh_key_id", key.ID)
|
state.Put("ssh_key_id", key.ID)
|
||||||
|
|
||||||
// If we're in debug mode, output the private key to the working directory.
|
|
||||||
if s.Debug {
|
|
||||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
|
||||||
f, err := os.Create(s.DebugKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// Write the key out
|
|
||||||
if _, err := f.Write(pem.EncodeToMemory(&priv_blk)); err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chmod it so that it is SSH ready
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
if err := f.Chmod(0600); err != nil {
|
|
||||||
state.Put("error", fmt.Errorf("Error setting permissions of debug key: %s", err))
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilder_implBuilder(t *testing.T) {
|
func TestBuilder_implBuilder(t *testing.T) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
func TestBuilderAcc_basic(t *testing.T) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
func TestBuilderAcc_basic(t *testing.T) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
func TestBuilderAcc_basic(t *testing.T) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
func TestBuilderAcc_basic(t *testing.T) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ package bsu
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
func TestBuilderAcc_basic(t *testing.T) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package bsusurrogate
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
func TestBuilderAcc_basic(t *testing.T) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ package bsuvolume
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
func TestBuilderAcc_basic(t *testing.T) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
func TestBuilderAcc_basic(t *testing.T) {
|
||||||
|
|
|
@ -6,10 +6,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
|
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_validateRegion(t *testing.T) {
|
func TestBuilderAcc_validateRegion(t *testing.T) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
func TestBuilderAcc_basic(t *testing.T) {
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package iso
|
package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
"github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||||
|
"github.com/hashicorp/packer-plugin-sdk/acctest/testutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderAcc_basic(t *testing.T) {
|
func TestBuilderAcc_basic(t *testing.T) {
|
||||||
|
@ -15,8 +18,25 @@ func TestBuilderAcc_basic(t *testing.T) {
|
||||||
t.Fatalf("failed to load template file %s", templatePath)
|
t.Fatalf("failed to load template file %s", templatePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
builderT.Test(t, builderT.TestCase{
|
testCase := &acctest.PluginTestCase{
|
||||||
Builder: &Builder{},
|
Name: "vmware-iso_builder_basic_test",
|
||||||
|
Setup: func() error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Teardown: func() error {
|
||||||
|
testutils.CleanupFiles("output-vmware-iso", "packer_cache")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
Template: string(bytes),
|
Template: string(bytes),
|
||||||
})
|
Type: "vmware-iso",
|
||||||
|
Check: func(buildCommand *exec.Cmd, logfile string) error {
|
||||||
|
if buildCommand.ProcessState != nil {
|
||||||
|
if buildCommand.ProcessState.ExitCode() != 0 {
|
||||||
|
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
acctest.TestPlugin(t, testCase)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/tmp"
|
"github.com/hashicorp/packer-plugin-sdk/tmp"
|
||||||
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const vmxTestTemplate string = `{"builders":[{%s}],"provisioners":[{%s}]}`
|
const vmxTestTemplate string = `{"builders":[{%s}],"provisioners":[{%s}]}`
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"builders": [
|
"builders": [
|
||||||
{
|
{
|
||||||
"type": "test",
|
"type": "vmware-iso",
|
||||||
"boot_command": [
|
"boot_command": [
|
||||||
"<esc><wait>",
|
"<esc><wait>",
|
||||||
"<esc><wait>",
|
"<esc><wait>",
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
"github.com/hashicorp/packer/builder/vsphere/common"
|
"github.com/hashicorp/packer/builder/vsphere/common"
|
||||||
commonT "github.com/hashicorp/packer/builder/vsphere/common/testing"
|
commonT "github.com/hashicorp/packer/builder/vsphere/common/testing"
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
"github.com/vmware/govmomi/vim25/types"
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
commonT "github.com/hashicorp/packer/builder/vsphere/common/testing"
|
commonT "github.com/hashicorp/packer/builder/vsphere/common/testing"
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
"github.com/vmware/govmomi/vim25/types"
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const InstanceMetadataAddr = "169.254.169.254"
|
const InstanceMetadataAddr = "169.254.169.254"
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -25,7 +25,7 @@ require (
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f
|
github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f
|
||||||
github.com/digitalocean/godo v1.11.1
|
github.com/digitalocean/godo v1.11.1
|
||||||
github.com/exoscale/packer-plugin-exoscale v0.1.0
|
github.com/exoscale/packer-plugin-exoscale v0.1.1
|
||||||
github.com/fatih/camelcase v1.0.0
|
github.com/fatih/camelcase v1.0.0
|
||||||
github.com/fatih/structtag v1.0.0
|
github.com/fatih/structtag v1.0.0
|
||||||
github.com/go-ini/ini v1.25.4
|
github.com/go-ini/ini v1.25.4
|
||||||
|
@ -51,7 +51,7 @@ require (
|
||||||
github.com/hashicorp/hcl/v2 v2.9.1
|
github.com/hashicorp/hcl/v2 v2.9.1
|
||||||
github.com/hashicorp/packer-plugin-amazon v0.0.1
|
github.com/hashicorp/packer-plugin-amazon v0.0.1
|
||||||
github.com/hashicorp/packer-plugin-docker v0.0.7
|
github.com/hashicorp/packer-plugin-docker v0.0.7
|
||||||
github.com/hashicorp/packer-plugin-sdk v0.1.1
|
github.com/hashicorp/packer-plugin-sdk v0.1.3-0.20210407132324-af39c7839daf
|
||||||
github.com/hashicorp/vault/api v1.0.4
|
github.com/hashicorp/vault/api v1.0.4
|
||||||
github.com/hetznercloud/hcloud-go v1.15.1
|
github.com/hetznercloud/hcloud-go v1.15.1
|
||||||
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4
|
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4
|
||||||
|
|
40
go.sum
40
go.sum
|
@ -83,6 +83,7 @@ github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.7/go.mod h1:P+3VS0ETiQPyWOx3
|
||||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||||
github.com/Telmate/proxmox-api-go v0.0.0-20200715182505-ec97c70ba887/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ=
|
github.com/Telmate/proxmox-api-go v0.0.0-20200715182505-ec97c70ba887/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ=
|
||||||
|
github.com/Telmate/proxmox-api-go v0.0.0-20210320143302-fea68269e6b0/go.mod h1:ayPkdmEKnlssqLQ9K1BE1jlsaYhXVwkoduXI30oQF0I=
|
||||||
github.com/Telmate/proxmox-api-go v0.0.0-20210331182840-ff89a0cebcfa h1:n4g0+o4DDX6WGTRfdj1Ux+49vSwtxtqFGB5XtxoDphI=
|
github.com/Telmate/proxmox-api-go v0.0.0-20210331182840-ff89a0cebcfa h1:n4g0+o4DDX6WGTRfdj1Ux+49vSwtxtqFGB5XtxoDphI=
|
||||||
github.com/Telmate/proxmox-api-go v0.0.0-20210331182840-ff89a0cebcfa/go.mod h1:ayPkdmEKnlssqLQ9K1BE1jlsaYhXVwkoduXI30oQF0I=
|
github.com/Telmate/proxmox-api-go v0.0.0-20210331182840-ff89a0cebcfa/go.mod h1:ayPkdmEKnlssqLQ9K1BE1jlsaYhXVwkoduXI30oQF0I=
|
||||||
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
|
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
|
||||||
|
@ -127,6 +128,30 @@ github.com/aws/aws-sdk-go v1.36.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zK
|
||||||
github.com/aws/aws-sdk-go v1.36.5/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.36.5/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/aws/aws-sdk-go v1.38.0 h1:mqnmtdW8rGIQmp2d0WRFLua0zW0Pel0P6/vd3gJuViY=
|
github.com/aws/aws-sdk-go v1.38.0 h1:mqnmtdW8rGIQmp2d0WRFLua0zW0Pel0P6/vd3gJuViY=
|
||||||
github.com/aws/aws-sdk-go v1.38.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.38.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.2.1 h1:055XAi+MtmhyYX161p+jWRibkCb9YpI2ymXZiW1dwVY=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.2.1/go.mod h1:hTQc/9pYq5bfFACIUY9tc/2SYWd9Vnmw+testmuQeRY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.1.2 h1:H2r6cwMvvINFpEC55Y7jcNaR/oc7zYIChrG2497wmBI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.1.2/go.mod h1:77yIk+qmCS/94JlxbwV1d+YEyu6Z8FBlCGcSz3TdM6A=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.1.2 h1:YoNqfhxAJGZI+lStIbqgx30UcCqQ86fr7FjTLUvrFOc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.1.2/go.mod h1:hofjw//lM0XLplgvzPPMA7oD0doQU1QpaIK1nweEEWg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.3 h1:d3bKAGy4XdJyK8hz3Nx3WJJ4TCmYp2498G4mFY5wly0=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.3/go.mod h1:Zr1Mj+KUMGVQ+WJvTT68EZJxqhjiie2PWSPGEUPaNY0=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.0.3 h1:vhRq0752KGBMmLnVessDOpt+5XEdzM87hhiuwGiEpqc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.0.3/go.mod h1:9tFvXNMet5TrBa2bMLhZBvenXs4qKMqiG1n0MNR4FFA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.0.2 h1:GO0pL4QvQmA0fXJe3MHVO+emtg31MYq5/8sebSWgE6A=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.0.2/go.mod h1:bYl7lGFQQdHia3uMQH4p6ImnuOeDNeUoydoXM5x8Yzw=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.3 h1:dST4y8pZKZdTPs4uwXmGCJmpycz1SHKmCSIhf3GqHEo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.3/go.mod h1:C50Z41fJaJ7WgaeeCulOGAU3q4+4se4B3uOPFdhBi2I=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.1.1 h1:+WCVceRPiUsrui55mDByXOVremK1n3Hm8GnB4ZD3eco=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.1.1/go.mod h1:B+fb+BFbja6obFOHYmYE4iUMdej9aM2VGSpgdU1pn0M=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.2.1 h1:3qn6YVXpOCK9seQ8ZilDyMrhpEUaZNaJG8SXNiCvk+c=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.2.1/go.mod h1:3xGOyhtPPD/WXJUljmb5+ZXhNyHa4h6wgL6mWOF6S0c=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.1.2 h1:9BnjX/ALn5uLo2DbgkwMpUkPL1VLQVBXcjZxqJBhf44=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.1.2/go.mod h1:5yU1oE3+CVYYLUsaHt2AVU3CJJZ6ER4pwsrRD1L2KSc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.1.2 h1:7Kxqov7uQeP8WUEO0iHz3j9Bh0E1rJrn6cf/OGfcDds=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.1.2/go.mod h1:zu7rotIY9P4Aoc6ytqLP9jeYrECDHUODB5Gbp+BSHl8=
|
||||||
|
github.com/aws/smithy-go v1.2.0 h1:0PoGBWXkXDIyVdPaZW9gMhaGzj3UOAgTdiVoHuuZAFA=
|
||||||
|
github.com/aws/smithy-go v1.2.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||||
|
@ -186,8 +211,9 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||||
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
||||||
github.com/exoscale/egoscale v0.43.1 h1:Lhr0UOfg3t3Y56yh1DsYCjQuUHqFvsC8iUVqvub8+0Q=
|
github.com/exoscale/egoscale v0.43.1 h1:Lhr0UOfg3t3Y56yh1DsYCjQuUHqFvsC8iUVqvub8+0Q=
|
||||||
github.com/exoscale/egoscale v0.43.1/go.mod h1:mpEXBpROAa/2i5GC0r33rfxG+TxSEka11g1PIXt9+zc=
|
github.com/exoscale/egoscale v0.43.1/go.mod h1:mpEXBpROAa/2i5GC0r33rfxG+TxSEka11g1PIXt9+zc=
|
||||||
github.com/exoscale/packer-plugin-exoscale v0.1.0 h1:p4ymqF1tNiTuxgSdnEjGqXehMdDQbV7BPaLsMxGav24=
|
|
||||||
github.com/exoscale/packer-plugin-exoscale v0.1.0/go.mod h1:ZmJRkxsAlmEsVYOMxYPupDkax54uZ+ph0h3W59aIMZ8=
|
github.com/exoscale/packer-plugin-exoscale v0.1.0/go.mod h1:ZmJRkxsAlmEsVYOMxYPupDkax54uZ+ph0h3W59aIMZ8=
|
||||||
|
github.com/exoscale/packer-plugin-exoscale v0.1.1 h1:NJ9UvMvSe3LK3H50hJv9nMG2reqgWKBAUhAEs4JJNso=
|
||||||
|
github.com/exoscale/packer-plugin-exoscale v0.1.1/go.mod h1:5S07HizadGVKST/m0a5+aNmDiFfY7EbPvnkU4rJWRE8=
|
||||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
@ -271,6 +297,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
|
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
|
||||||
|
@ -402,6 +429,7 @@ github.com/hashicorp/packer v1.6.7-0.20210126105722-aef4ced967ec/go.mod h1:2+Vo/
|
||||||
github.com/hashicorp/packer v1.6.7-0.20210208125835-f616955ebcb6/go.mod h1:7f5ZpTTRG53rQ58BcTADuTnpiBcB3wapuxl4sF2sGMM=
|
github.com/hashicorp/packer v1.6.7-0.20210208125835-f616955ebcb6/go.mod h1:7f5ZpTTRG53rQ58BcTADuTnpiBcB3wapuxl4sF2sGMM=
|
||||||
github.com/hashicorp/packer v1.6.7-0.20210217093213-201869d627bf/go.mod h1:+EWPPcqee4h8S/y913Dnta1eJkgiqsGXBQgB75A2qV0=
|
github.com/hashicorp/packer v1.6.7-0.20210217093213-201869d627bf/go.mod h1:+EWPPcqee4h8S/y913Dnta1eJkgiqsGXBQgB75A2qV0=
|
||||||
github.com/hashicorp/packer v1.7.0/go.mod h1:3KRJcwOctl2JaAGpQMI1bWQRArfWNWqcYjO6AOsVVGQ=
|
github.com/hashicorp/packer v1.7.0/go.mod h1:3KRJcwOctl2JaAGpQMI1bWQRArfWNWqcYjO6AOsVVGQ=
|
||||||
|
github.com/hashicorp/packer v1.7.1/go.mod h1:ApnmMINvuhhnfPyTVqZu6jznDWPVYDJUw7e188DFCmo=
|
||||||
github.com/hashicorp/packer-plugin-amazon v0.0.1 h1:EuyjNK9bL7WhQeIJzhBJxOx8nyc61ai5UbOsb1PIVwI=
|
github.com/hashicorp/packer-plugin-amazon v0.0.1 h1:EuyjNK9bL7WhQeIJzhBJxOx8nyc61ai5UbOsb1PIVwI=
|
||||||
github.com/hashicorp/packer-plugin-amazon v0.0.1/go.mod h1:12c9msibyHdId+Mk/pCbdRb1KaLIhaNyxeJ6n8bZt30=
|
github.com/hashicorp/packer-plugin-amazon v0.0.1/go.mod h1:12c9msibyHdId+Mk/pCbdRb1KaLIhaNyxeJ6n8bZt30=
|
||||||
github.com/hashicorp/packer-plugin-docker v0.0.7 h1:hMTrH7vrkFIjphtbbtpuzffTzSjMNgxayo2DPLz9y+c=
|
github.com/hashicorp/packer-plugin-docker v0.0.7 h1:hMTrH7vrkFIjphtbbtpuzffTzSjMNgxayo2DPLz9y+c=
|
||||||
|
@ -415,8 +443,16 @@ github.com/hashicorp/packer-plugin-sdk v0.0.11/go.mod h1:GNb0WNs7zibb8vzUZce1As6
|
||||||
github.com/hashicorp/packer-plugin-sdk v0.0.12/go.mod h1:hs82OYeufirGG6KRENMpjBWomnIlte99X6wXAPThJ5I=
|
github.com/hashicorp/packer-plugin-sdk v0.0.12/go.mod h1:hs82OYeufirGG6KRENMpjBWomnIlte99X6wXAPThJ5I=
|
||||||
github.com/hashicorp/packer-plugin-sdk v0.0.14/go.mod h1:tNb3XzJPnjMl3QuUdKmF47B5ImerdTakalHzUAvW0aw=
|
github.com/hashicorp/packer-plugin-sdk v0.0.14/go.mod h1:tNb3XzJPnjMl3QuUdKmF47B5ImerdTakalHzUAvW0aw=
|
||||||
github.com/hashicorp/packer-plugin-sdk v0.1.0/go.mod h1:CFsC20uZjtER/EnTn/CSMKD0kEdkqOVev8mtOmfnZiI=
|
github.com/hashicorp/packer-plugin-sdk v0.1.0/go.mod h1:CFsC20uZjtER/EnTn/CSMKD0kEdkqOVev8mtOmfnZiI=
|
||||||
github.com/hashicorp/packer-plugin-sdk v0.1.1 h1:foqSy6m+2MsEf9ygNoBlIoi3SPvlkInS+yT0Uyj3yvw=
|
|
||||||
github.com/hashicorp/packer-plugin-sdk v0.1.1/go.mod h1:1d3nqB9LUsXMQaNUiL67Q+WYEtjsVcLNTX8ikVlpBrc=
|
github.com/hashicorp/packer-plugin-sdk v0.1.1/go.mod h1:1d3nqB9LUsXMQaNUiL67Q+WYEtjsVcLNTX8ikVlpBrc=
|
||||||
|
github.com/hashicorp/packer-plugin-sdk v0.1.2/go.mod h1:KRjczE1/c9NV5Re+PXt3myJsVTI/FxEHpZjRjOH0Fug=
|
||||||
|
github.com/hashicorp/packer-plugin-sdk v0.1.3-0.20210407090040-d1eff9fe99e8 h1:pkB+Y15/ck/NRUBFF9DrdPYQwmnHsEvnNwmgMfl/8hA=
|
||||||
|
github.com/hashicorp/packer-plugin-sdk v0.1.3-0.20210407090040-d1eff9fe99e8/go.mod h1:xePpgQgQYv/bamiypx3hH9ukidxDdcN8q0R0wLi8IEQ=
|
||||||
|
github.com/hashicorp/packer-plugin-sdk v0.1.3-0.20210407130359-85b84b1d6060 h1:uRrDQYiP3pFn5W17Bvj9If2taHB/DqIP7uuPQGnLDFM=
|
||||||
|
github.com/hashicorp/packer-plugin-sdk v0.1.3-0.20210407130359-85b84b1d6060/go.mod h1:xePpgQgQYv/bamiypx3hH9ukidxDdcN8q0R0wLi8IEQ=
|
||||||
|
github.com/hashicorp/packer-plugin-sdk v0.1.3-0.20210407130906-826d4f395a10 h1:VlcHJEpR99eeZi7uujdQKFOIK8rE5ditXGqpBWiGjc4=
|
||||||
|
github.com/hashicorp/packer-plugin-sdk v0.1.3-0.20210407130906-826d4f395a10/go.mod h1:xePpgQgQYv/bamiypx3hH9ukidxDdcN8q0R0wLi8IEQ=
|
||||||
|
github.com/hashicorp/packer-plugin-sdk v0.1.3-0.20210407132324-af39c7839daf h1:0DBlIExTDefzbfkOl213QtgJsVJXWdgW/aIQIvYUMzs=
|
||||||
|
github.com/hashicorp/packer-plugin-sdk v0.1.3-0.20210407132324-af39c7839daf/go.mod h1:xePpgQgQYv/bamiypx3hH9ukidxDdcN8q0R0wLi8IEQ=
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
github.com/hashicorp/serf v0.9.2 h1:yJoyfZXo4Pk2p/M/viW+YLibBFiIbKoP79gu7kDAFP0=
|
github.com/hashicorp/serf v0.9.2 h1:yJoyfZXo4Pk2p/M/viW+YLibBFiIbKoP79gu7kDAFP0=
|
||||||
github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||||
|
|
|
@ -59,6 +59,8 @@ type Config struct {
|
||||||
ImageLabels map[string]string `mapstructure:"image_labels"`
|
ImageLabels map[string]string `mapstructure:"image_labels"`
|
||||||
//The unique name of the resulting image.
|
//The unique name of the resulting image.
|
||||||
ImageName string `mapstructure:"image_name" required:"true"`
|
ImageName string `mapstructure:"image_name" required:"true"`
|
||||||
|
//Specifies a Cloud Storage location, either regional or multi-regional, where image content is to be stored. If not specified, the multi-region location closest to the source is chosen automatically.
|
||||||
|
ImageStorageLocations []string `mapstructure:"image_storage_locations"`
|
||||||
//Skip removing the TAR file uploaded to the GCS
|
//Skip removing the TAR file uploaded to the GCS
|
||||||
//bucket after the import process has completed. "true" means that we should
|
//bucket after the import process has completed. "true" means that we should
|
||||||
//leave it in the GCS bucket, "false" means to clean it out. Defaults to
|
//leave it in the GCS bucket, "false" means to clean it out. Defaults to
|
||||||
|
@ -182,7 +184,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifa
|
||||||
return nil, false, false, err
|
return nil, false, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
gceImageArtifact, err := CreateGceImage(opts, ui, p.config.ProjectId, rawImageGcsPath, p.config.ImageName, p.config.ImageDescription, p.config.ImageFamily, p.config.ImageLabels, p.config.ImageGuestOsFeatures, shieldedVMStateConfig)
|
gceImageArtifact, err := CreateGceImage(opts, ui, p.config.ProjectId, rawImageGcsPath, p.config.ImageName, p.config.ImageDescription, p.config.ImageFamily, p.config.ImageLabels, p.config.ImageGuestOsFeatures, shieldedVMStateConfig, p.config.ImageStorageLocations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, false, err
|
return nil, false, false, err
|
||||||
}
|
}
|
||||||
|
@ -295,7 +297,7 @@ func UploadToBucket(opts option.ClientOption, ui packersdk.Ui, artifact packersd
|
||||||
return storageObject.SelfLink, nil
|
return storageObject.SelfLink, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateGceImage(opts option.ClientOption, ui packersdk.Ui, project string, rawImageURL string, imageName string, imageDescription string, imageFamily string, imageLabels map[string]string, imageGuestOsFeatures []string, shieldedVMStateConfig *compute.InitialStateConfig) (packersdk.Artifact, error) {
|
func CreateGceImage(opts option.ClientOption, ui packersdk.Ui, project string, rawImageURL string, imageName string, imageDescription string, imageFamily string, imageLabels map[string]string, imageGuestOsFeatures []string, shieldedVMStateConfig *compute.InitialStateConfig, imageStorageLocations []string) (packersdk.Artifact, error) {
|
||||||
service, err := compute.NewService(context.TODO(), opts)
|
service, err := compute.NewService(context.TODO(), opts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -319,6 +321,7 @@ func CreateGceImage(opts option.ClientOption, ui packersdk.Ui, project string, r
|
||||||
RawDisk: &compute.ImageRawDisk{Source: rawImageURL},
|
RawDisk: &compute.ImageRawDisk{Source: rawImageURL},
|
||||||
SourceType: "RAW",
|
SourceType: "RAW",
|
||||||
ShieldedInstanceInitialState: shieldedVMStateConfig,
|
ShieldedInstanceInitialState: shieldedVMStateConfig,
|
||||||
|
StorageLocations: imageStorageLocations,
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.Say(fmt.Sprintf("Creating GCE image %v...", imageName))
|
ui.Say(fmt.Sprintf("Creating GCE image %v...", imageName))
|
||||||
|
|
|
@ -29,6 +29,7 @@ type FlatConfig struct {
|
||||||
ImageGuestOsFeatures []string `mapstructure:"image_guest_os_features" cty:"image_guest_os_features" hcl:"image_guest_os_features"`
|
ImageGuestOsFeatures []string `mapstructure:"image_guest_os_features" cty:"image_guest_os_features" hcl:"image_guest_os_features"`
|
||||||
ImageLabels map[string]string `mapstructure:"image_labels" cty:"image_labels" hcl:"image_labels"`
|
ImageLabels map[string]string `mapstructure:"image_labels" cty:"image_labels" hcl:"image_labels"`
|
||||||
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
|
ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"`
|
||||||
|
ImageStorageLocations []string `mapstructure:"image_storage_locations" cty:"image_storage_locations" hcl:"image_storage_locations"`
|
||||||
SkipClean *bool `mapstructure:"skip_clean" cty:"skip_clean" hcl:"skip_clean"`
|
SkipClean *bool `mapstructure:"skip_clean" cty:"skip_clean" hcl:"skip_clean"`
|
||||||
VaultGCPOauthEngine *string `mapstructure:"vault_gcp_oauth_engine" cty:"vault_gcp_oauth_engine" hcl:"vault_gcp_oauth_engine"`
|
VaultGCPOauthEngine *string `mapstructure:"vault_gcp_oauth_engine" cty:"vault_gcp_oauth_engine" hcl:"vault_gcp_oauth_engine"`
|
||||||
ImagePlatformKey *string `mapstructure:"image_platform_key" cty:"image_platform_key" hcl:"image_platform_key"`
|
ImagePlatformKey *string `mapstructure:"image_platform_key" cty:"image_platform_key" hcl:"image_platform_key"`
|
||||||
|
@ -68,6 +69,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"image_guest_os_features": &hcldec.AttrSpec{Name: "image_guest_os_features", Type: cty.List(cty.String), Required: false},
|
"image_guest_os_features": &hcldec.AttrSpec{Name: "image_guest_os_features", Type: cty.List(cty.String), Required: false},
|
||||||
"image_labels": &hcldec.AttrSpec{Name: "image_labels", Type: cty.Map(cty.String), Required: false},
|
"image_labels": &hcldec.AttrSpec{Name: "image_labels", Type: cty.Map(cty.String), Required: false},
|
||||||
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
"image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false},
|
||||||
|
"image_storage_locations": &hcldec.AttrSpec{Name: "image_storage_locations", Type: cty.List(cty.String), Required: false},
|
||||||
"skip_clean": &hcldec.AttrSpec{Name: "skip_clean", Type: cty.Bool, Required: false},
|
"skip_clean": &hcldec.AttrSpec{Name: "skip_clean", Type: cty.Bool, Required: false},
|
||||||
"vault_gcp_oauth_engine": &hcldec.AttrSpec{Name: "vault_gcp_oauth_engine", Type: cty.String, Required: false},
|
"vault_gcp_oauth_engine": &hcldec.AttrSpec{Name: "vault_gcp_oauth_engine", Type: cty.String, Required: false},
|
||||||
"image_platform_key": &hcldec.AttrSpec{Name: "image_platform_key", Type: cty.String, Required: false},
|
"image_platform_key": &hcldec.AttrSpec{Name: "image_platform_key", Type: cty.String, Required: false},
|
||||||
|
|
|
@ -112,7 +112,7 @@ func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.config.VagrantCloudUrl == VAGRANT_CLOUD_URL && p.config.AccessToken == "" {
|
if p.config.VagrantCloudUrl == VAGRANT_CLOUD_URL && p.config.AccessToken == "" {
|
||||||
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("access_token must be set if vagrant_cloud_url has not been overriden"))
|
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("access_token must be set if vagrant_cloud_url has not been overridden"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the HTTP client
|
// Create the HTTP client
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//go:generate mapstructure-to-hcl2 -type Config
|
//go:generate mapstructure-to-hcl2 -type Config
|
||||||
|
//go:generate struct-markdown
|
||||||
|
|
||||||
package ansiblelocal
|
package ansiblelocal
|
||||||
|
|
||||||
|
@ -23,51 +24,126 @@ const DefaultStagingDir = "/tmp/packer-provisioner-ansible-local"
|
||||||
type Config struct {
|
type Config struct {
|
||||||
common.PackerConfig `mapstructure:",squash"`
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
ctx interpolate.Context
|
ctx interpolate.Context
|
||||||
|
// The command to invoke ansible. Defaults to
|
||||||
// The command to run ansible
|
// `ansible-playbook`. If you would like to provide a more complex command,
|
||||||
Command string
|
// for example, something that sets up a virtual environment before calling
|
||||||
|
// ansible, take a look at the ansible wrapper guide below for inspiration.
|
||||||
// Extra options to pass to the ansible command
|
// Please note that Packer expects Command to be a path to an executable.
|
||||||
|
// Arbitrary bash scripting will not work and needs to go inside an
|
||||||
|
// executable script.
|
||||||
|
Command string `mapstructure:"command"`
|
||||||
|
// Extra arguments to pass to Ansible.
|
||||||
|
// These arguments _will not_ be passed through a shell and arguments should
|
||||||
|
// not be quoted. Usage example:
|
||||||
|
//
|
||||||
|
// ```json
|
||||||
|
// "extra_arguments": [ "--extra-vars", "Region={{user `Region`}} Stage={{user `Stage`}}" ]
|
||||||
|
// ```
|
||||||
|
// In certain scenarios where you want to pass ansible command line arguments
|
||||||
|
// that include parameter and value (for example `--vault-password-file pwfile`),
|
||||||
|
// from ansible documentation this is correct format but that is NOT accepted here.
|
||||||
|
// Instead you need to do it like `--vault-password-file=pwfile`.
|
||||||
|
//
|
||||||
|
// If you are running a Windows build on AWS, Azure, Google Compute, or OpenStack
|
||||||
|
// and would like to access the auto-generated password that Packer uses to
|
||||||
|
// connect to a Windows instance via WinRM, you can use the template variable
|
||||||
|
// `{{.WinRMPassword}}` in this option. For example:
|
||||||
|
//
|
||||||
|
// ```json
|
||||||
|
// "extra_arguments": [
|
||||||
|
// "--extra-vars", "winrm_password={{ .WinRMPassword }}"
|
||||||
|
// ]
|
||||||
|
// ```
|
||||||
ExtraArguments []string `mapstructure:"extra_arguments"`
|
ExtraArguments []string `mapstructure:"extra_arguments"`
|
||||||
|
// A path to the directory containing ansible group
|
||||||
// Path to group_vars directory
|
// variables on your local system to be copied to the remote machine. By
|
||||||
|
// default, this is empty.
|
||||||
GroupVars string `mapstructure:"group_vars"`
|
GroupVars string `mapstructure:"group_vars"`
|
||||||
|
// A path to the directory containing ansible host variables on your local
|
||||||
// Path to host_vars directory
|
// system to be copied to the remote machine. By default, this is empty.
|
||||||
HostVars string `mapstructure:"host_vars"`
|
HostVars string `mapstructure:"host_vars"`
|
||||||
|
// A path to the complete ansible directory structure on your local system
|
||||||
// The playbook dir to upload.
|
// to be copied to the remote machine as the `staging_directory` before all
|
||||||
|
// other files and directories.
|
||||||
PlaybookDir string `mapstructure:"playbook_dir"`
|
PlaybookDir string `mapstructure:"playbook_dir"`
|
||||||
|
// The playbook file to be executed by ansible. This file must exist on your
|
||||||
// The main playbook file to execute.
|
// local system and will be uploaded to the remote machine. This option is
|
||||||
|
// exclusive with `playbook_files`.
|
||||||
PlaybookFile string `mapstructure:"playbook_file"`
|
PlaybookFile string `mapstructure:"playbook_file"`
|
||||||
|
// The playbook files to be executed by ansible. These files must exist on
|
||||||
// The playbook files to execute.
|
// your local system. If the files don't exist in the `playbook_dir` or you
|
||||||
|
// don't set `playbook_dir` they will be uploaded to the remote machine. This
|
||||||
|
// option is exclusive with `playbook_file`.
|
||||||
PlaybookFiles []string `mapstructure:"playbook_files"`
|
PlaybookFiles []string `mapstructure:"playbook_files"`
|
||||||
|
// An array of directories of playbook files on your local system. These
|
||||||
// An array of local paths of playbook files to upload.
|
// will be uploaded to the remote machine under `staging_directory`/playbooks.
|
||||||
|
// By default, this is empty.
|
||||||
PlaybookPaths []string `mapstructure:"playbook_paths"`
|
PlaybookPaths []string `mapstructure:"playbook_paths"`
|
||||||
|
// An array of paths to role directories on your local system. These will be
|
||||||
// An array of local paths of roles to upload.
|
// uploaded to the remote machine under `staging_directory`/roles. By default,
|
||||||
|
// this is empty.
|
||||||
RolePaths []string `mapstructure:"role_paths"`
|
RolePaths []string `mapstructure:"role_paths"`
|
||||||
|
// The directory where all the configuration of Ansible by Packer will be placed.
|
||||||
// The directory where files will be uploaded. Packer requires write
|
// By default this is `/tmp/packer-provisioner-ansible-local/<uuid>`, where
|
||||||
// permissions in this directory.
|
// `<uuid>` is replaced with a unique ID so that this provisioner can be run more
|
||||||
|
// than once. If you'd like to know the location of the staging directory in
|
||||||
|
// advance, you should set this to a known location. This directory doesn't need
|
||||||
|
// to exist but must have proper permissions so that the SSH user that Packer uses
|
||||||
|
// is able to create directories and write into this folder. If the permissions
|
||||||
|
// are not correct, use a shell provisioner prior to this to configure it
|
||||||
|
// properly.
|
||||||
StagingDir string `mapstructure:"staging_directory"`
|
StagingDir string `mapstructure:"staging_directory"`
|
||||||
|
// If set to `true`, the content of the `staging_directory` will be removed after
|
||||||
// If true, staging directory is removed after executing ansible.
|
// executing ansible. By default this is set to `false`.
|
||||||
CleanStagingDir bool `mapstructure:"clean_staging_directory"`
|
CleanStagingDir bool `mapstructure:"clean_staging_directory"`
|
||||||
|
// The inventory file to be used by ansible. This
|
||||||
// The optional inventory file
|
// file must exist on your local system and will be uploaded to the remote
|
||||||
|
// machine.
|
||||||
|
//
|
||||||
|
// When using an inventory file, it's also required to `--limit` the hosts to the
|
||||||
|
// specified host you're building. The `--limit` argument can be provided in the
|
||||||
|
// `extra_arguments` option.
|
||||||
|
//
|
||||||
|
// An example inventory file may look like:
|
||||||
|
//
|
||||||
|
// ```text
|
||||||
|
// [chi-dbservers]
|
||||||
|
// db-01 ansible_connection=local
|
||||||
|
// db-02 ansible_connection=local
|
||||||
|
//
|
||||||
|
// [chi-appservers]
|
||||||
|
// app-01 ansible_connection=local
|
||||||
|
// app-02 ansible_connection=local
|
||||||
|
//
|
||||||
|
// [chi:children]
|
||||||
|
// chi-dbservers
|
||||||
|
// chi-appservers
|
||||||
|
//
|
||||||
|
// [dbservers:children]
|
||||||
|
// chi-dbservers
|
||||||
|
//
|
||||||
|
// [appservers:children]
|
||||||
|
// chi-appservers
|
||||||
|
// ```
|
||||||
InventoryFile string `mapstructure:"inventory_file"`
|
InventoryFile string `mapstructure:"inventory_file"`
|
||||||
|
// `inventory_groups` (string) - A comma-separated list of groups to which
|
||||||
// The optional inventory groups
|
// packer will assign the host `127.0.0.1`. A value of `my_group_1,my_group_2`
|
||||||
|
// will generate an Ansible inventory like:
|
||||||
|
//
|
||||||
|
// ```text
|
||||||
|
// [my_group_1]
|
||||||
|
// 127.0.0.1
|
||||||
|
// [my_group_2]
|
||||||
|
// 127.0.0.1
|
||||||
|
// ```
|
||||||
InventoryGroups []string `mapstructure:"inventory_groups"`
|
InventoryGroups []string `mapstructure:"inventory_groups"`
|
||||||
|
// A requirements file which provides a way to
|
||||||
// The optional ansible-galaxy requirements file
|
// install roles or collections with the [ansible-galaxy
|
||||||
|
// cli](https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#the-ansible-galaxy-command-line-tool)
|
||||||
|
// on the local machine before executing `ansible-playbook`. By default, this is empty.
|
||||||
GalaxyFile string `mapstructure:"galaxy_file"`
|
GalaxyFile string `mapstructure:"galaxy_file"`
|
||||||
|
// The command to invoke ansible-galaxy. By default, this is
|
||||||
// The command to run ansible-galaxy
|
// `ansible-galaxy`.
|
||||||
GalaxyCommand string `mapstructure:"galaxy_command"`
|
GalaxyCommand string `mapstructure:"galaxy_command"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ type FlatConfig struct {
|
||||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"`
|
||||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"`
|
||||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"`
|
||||||
Command *string `cty:"command" hcl:"command"`
|
Command *string `mapstructure:"command" cty:"command" hcl:"command"`
|
||||||
ExtraArguments []string `mapstructure:"extra_arguments" cty:"extra_arguments" hcl:"extra_arguments"`
|
ExtraArguments []string `mapstructure:"extra_arguments" cty:"extra_arguments" hcl:"extra_arguments"`
|
||||||
GroupVars *string `mapstructure:"group_vars" cty:"group_vars" hcl:"group_vars"`
|
GroupVars *string `mapstructure:"group_vars" cty:"group_vars" hcl:"group_vars"`
|
||||||
HostVars *string `mapstructure:"host_vars" cty:"host_vars" hcl:"host_vars"`
|
HostVars *string `mapstructure:"host_vars" cty:"host_vars" hcl:"host_vars"`
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/hashicorp/packer-plugin-docker/builder/docker"
|
"github.com/hashicorp/packer-plugin-docker/builder/docker"
|
||||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
|
builderT "github.com/hashicorp/packer/acctest"
|
||||||
"github.com/hashicorp/packer/provisioner/file"
|
"github.com/hashicorp/packer/provisioner/file"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
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,3 @@
|
||||||
|
AWS SDK for Go
|
||||||
|
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
Copyright 2014-2015 Stripe, Inc.
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Package arn provides a parser for interacting with Amazon Resource Names.
|
||||||
|
package arn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
arnDelimiter = ":"
|
||||||
|
arnSections = 6
|
||||||
|
arnPrefix = "arn:"
|
||||||
|
|
||||||
|
// zero-indexed
|
||||||
|
sectionPartition = 1
|
||||||
|
sectionService = 2
|
||||||
|
sectionRegion = 3
|
||||||
|
sectionAccountID = 4
|
||||||
|
sectionResource = 5
|
||||||
|
|
||||||
|
// errors
|
||||||
|
invalidPrefix = "arn: invalid prefix"
|
||||||
|
invalidSections = "arn: not enough sections"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ARN captures the individual fields of an Amazon Resource Name.
|
||||||
|
// See http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html for more information.
|
||||||
|
type ARN struct {
|
||||||
|
// The partition that the resource is in. For standard AWS regions, the partition is "aws". If you have resources in
|
||||||
|
// other partitions, the partition is "aws-partitionname". For example, the partition for resources in the China
|
||||||
|
// (Beijing) region is "aws-cn".
|
||||||
|
Partition string
|
||||||
|
|
||||||
|
// The service namespace that identifies the AWS product (for example, Amazon S3, IAM, or Amazon RDS). For a list of
|
||||||
|
// namespaces, see
|
||||||
|
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces.
|
||||||
|
Service string
|
||||||
|
|
||||||
|
// The region the resource resides in. Note that the ARNs for some resources do not require a region, so this
|
||||||
|
// component might be omitted.
|
||||||
|
Region string
|
||||||
|
|
||||||
|
// The ID of the AWS account that owns the resource, without the hyphens. For example, 123456789012. Note that the
|
||||||
|
// ARNs for some resources don't require an account number, so this component might be omitted.
|
||||||
|
AccountID string
|
||||||
|
|
||||||
|
// The content of this part of the ARN varies by service. It often includes an indicator of the type of resource —
|
||||||
|
// for example, an IAM user or Amazon RDS database - followed by a slash (/) or a colon (:), followed by the
|
||||||
|
// resource name itself. Some services allows paths for resource names, as described in
|
||||||
|
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arns-paths.
|
||||||
|
Resource string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses an ARN into its constituent parts.
|
||||||
|
//
|
||||||
|
// Some example ARNs:
|
||||||
|
// arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/My App/MyEnvironment
|
||||||
|
// arn:aws:iam::123456789012:user/David
|
||||||
|
// arn:aws:rds:eu-west-1:123456789012:db:mysql-db
|
||||||
|
// arn:aws:s3:::my_corporate_bucket/exampleobject.png
|
||||||
|
func Parse(arn string) (ARN, error) {
|
||||||
|
if !strings.HasPrefix(arn, arnPrefix) {
|
||||||
|
return ARN{}, errors.New(invalidPrefix)
|
||||||
|
}
|
||||||
|
sections := strings.SplitN(arn, arnDelimiter, arnSections)
|
||||||
|
if len(sections) != arnSections {
|
||||||
|
return ARN{}, errors.New(invalidSections)
|
||||||
|
}
|
||||||
|
return ARN{
|
||||||
|
Partition: sections[sectionPartition],
|
||||||
|
Service: sections[sectionService],
|
||||||
|
Region: sections[sectionRegion],
|
||||||
|
AccountID: sections[sectionAccountID],
|
||||||
|
Resource: sections[sectionResource],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsARN returns whether the given string is an arn
|
||||||
|
// by looking for whether the string starts with arn:
|
||||||
|
func IsARN(arn string) bool {
|
||||||
|
return strings.HasPrefix(arn, arnPrefix) && strings.Count(arn, ":") >= arnSections-1
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the canonical representation of the ARN
|
||||||
|
func (arn ARN) String() string {
|
||||||
|
return arnPrefix +
|
||||||
|
arn.Partition + arnDelimiter +
|
||||||
|
arn.Service + arnDelimiter +
|
||||||
|
arn.Region + arnDelimiter +
|
||||||
|
arn.AccountID + arnDelimiter +
|
||||||
|
arn.Resource
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/aws/smithy-go/logging"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPClient provides the interface to provide custom HTTPClients. Generally
|
||||||
|
// *http.Client is sufficient for most use cases. The HTTPClient should not
|
||||||
|
// follow redirects.
|
||||||
|
type HTTPClient interface {
|
||||||
|
Do(*http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Config provides service configuration for service clients.
|
||||||
|
type Config struct {
|
||||||
|
// The region to send requests to. This parameter is required and must
|
||||||
|
// be configured globally or on a per-client basis unless otherwise
|
||||||
|
// noted. A full list of regions is found in the "Regions and Endpoints"
|
||||||
|
// document.
|
||||||
|
//
|
||||||
|
// See http://docs.aws.amazon.com/general/latest/gr/rande.html for
|
||||||
|
// information on AWS regions.
|
||||||
|
Region string
|
||||||
|
|
||||||
|
// The credentials object to use when signing requests. Defaults to a
|
||||||
|
// chain of credential providers to search for credentials in environment
|
||||||
|
// variables, shared credential file, and EC2 Instance Roles.
|
||||||
|
Credentials CredentialsProvider
|
||||||
|
|
||||||
|
// The HTTP Client the SDK's API clients will use to invoke HTTP requests.
|
||||||
|
// The SDK defaults to a BuildableClient allowing API clients to create
|
||||||
|
// copies of the HTTP Client for service specific customizations.
|
||||||
|
//
|
||||||
|
// Use a (*http.Client) for custom behavior. Using a custom http.Client
|
||||||
|
// will prevent the SDK from modifying the HTTP client.
|
||||||
|
HTTPClient HTTPClient
|
||||||
|
|
||||||
|
// An endpoint resolver that can be used to provide or override an endpoint for the given
|
||||||
|
// service and region Please see the `aws.EndpointResolver` documentation on usage.
|
||||||
|
EndpointResolver EndpointResolver
|
||||||
|
|
||||||
|
// Retryer is a function that provides a Retryer implementation. A Retryer guides how HTTP requests should be
|
||||||
|
// retried in case of recoverable failures. When nil the API client will use a default
|
||||||
|
// retryer.
|
||||||
|
//
|
||||||
|
// In general, the provider function should return a new instance of a Retyer if you are attempting
|
||||||
|
// to provide a consistent Retryer configuration across all clients. This will ensure that each client will be
|
||||||
|
// provided a new instance of the Retryer implementation, and will avoid issues such as sharing the same retry token
|
||||||
|
// bucket across services.
|
||||||
|
Retryer func() Retryer
|
||||||
|
|
||||||
|
// ConfigSources are the sources that were used to construct the Config.
|
||||||
|
// Allows for additional configuration to be loaded by clients.
|
||||||
|
ConfigSources []interface{}
|
||||||
|
|
||||||
|
// APIOptions provides the set of middleware mutations modify how the API
|
||||||
|
// client requests will be handled. This is useful for adding additional
|
||||||
|
// tracing data to a request, or changing behavior of the SDK's client.
|
||||||
|
APIOptions []func(*middleware.Stack) error
|
||||||
|
|
||||||
|
// The logger writer interface to write logging messages to. Defaults to
|
||||||
|
// standard error.
|
||||||
|
Logger logging.Logger
|
||||||
|
|
||||||
|
// Configures the events that will be sent to the configured logger.
|
||||||
|
// This can be used to configure the logging of signing, retries, request, and responses
|
||||||
|
// of the SDK clients.
|
||||||
|
//
|
||||||
|
// See the ClientLogMode type documentation for the complete set of logging modes and available
|
||||||
|
// configuration.
|
||||||
|
ClientLogMode ClientLogMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfig returns a new Config pointer that can be chained with builder
|
||||||
|
// methods to set multiple configuration values inline without using pointers.
|
||||||
|
func NewConfig() *Config {
|
||||||
|
return &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy will return a shallow copy of the Config object. If any additional
|
||||||
|
// configurations are provided they will be merged into the new config returned.
|
||||||
|
func (c Config) Copy() Config {
|
||||||
|
cp := c
|
||||||
|
return cp
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type suppressedContext struct {
|
||||||
|
context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *suppressedContext) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *suppressedContext) Done() <-chan struct{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *suppressedContext) Err() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdkrand "github.com/aws/aws-sdk-go-v2/internal/rand"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/sync/singleflight"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CredentialsCacheOptions are the options
|
||||||
|
type CredentialsCacheOptions struct {
|
||||||
|
|
||||||
|
// ExpiryWindow will allow the credentials to trigger refreshing prior to
|
||||||
|
// the credentials actually expiring. This is beneficial so race conditions
|
||||||
|
// with expiring credentials do not cause request to fail unexpectedly
|
||||||
|
// due to ExpiredTokenException exceptions.
|
||||||
|
//
|
||||||
|
// An ExpiryWindow of 10s would cause calls to IsExpired() to return true
|
||||||
|
// 10 seconds before the credentials are actually expired. This can cause an
|
||||||
|
// increased number of requests to refresh the credentials to occur.
|
||||||
|
//
|
||||||
|
// If ExpiryWindow is 0 or less it will be ignored.
|
||||||
|
ExpiryWindow time.Duration
|
||||||
|
|
||||||
|
// ExpiryWindowJitterFrac provides a mechanism for randomizing the expiration of credentials
|
||||||
|
// within the configured ExpiryWindow by a random percentage. Valid values are between 0.0 and 1.0.
|
||||||
|
//
|
||||||
|
// As an example if ExpiryWindow is 60 seconds and ExpiryWindowJitterFrac is 0.5 then credentials will be set to
|
||||||
|
// expire between 30 to 60 seconds prior to their actual expiration time.
|
||||||
|
//
|
||||||
|
// If ExpiryWindow is 0 or less then ExpiryWindowJitterFrac is ignored.
|
||||||
|
// If ExpiryWindowJitterFrac is 0 then no randomization will be applied to the window.
|
||||||
|
// If ExpiryWindowJitterFrac < 0 the value will be treated as 0.
|
||||||
|
// If ExpiryWindowJitterFrac > 1 the value will be treated as 1.
|
||||||
|
ExpiryWindowJitterFrac float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CredentialsCache provides caching and concurrency safe credentials retrieval
|
||||||
|
// via the provider's retrieve method.
|
||||||
|
type CredentialsCache struct {
|
||||||
|
// provider is the CredentialProvider implementation to be wrapped by the CredentialCache.
|
||||||
|
provider CredentialsProvider
|
||||||
|
|
||||||
|
options CredentialsCacheOptions
|
||||||
|
creds atomic.Value
|
||||||
|
sf singleflight.Group
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCredentialsCache returns a CredentialsCache that wraps provider. Provider is expected to not be nil. A variadic
|
||||||
|
// list of one or more functions can be provided to modify the CredentialsCache configuration. This allows for
|
||||||
|
// configuration of credential expiry window and jitter.
|
||||||
|
func NewCredentialsCache(provider CredentialsProvider, optFns ...func(options *CredentialsCacheOptions)) *CredentialsCache {
|
||||||
|
options := CredentialsCacheOptions{}
|
||||||
|
|
||||||
|
for _, fn := range optFns {
|
||||||
|
fn(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.ExpiryWindow < 0 {
|
||||||
|
options.ExpiryWindow = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.ExpiryWindowJitterFrac < 0 {
|
||||||
|
options.ExpiryWindowJitterFrac = 0
|
||||||
|
} else if options.ExpiryWindowJitterFrac > 1 {
|
||||||
|
options.ExpiryWindowJitterFrac = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CredentialsCache{
|
||||||
|
provider: provider,
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve returns the credentials. If the credentials have already been
|
||||||
|
// retrieved, and not expired the cached credentials will be returned. If the
|
||||||
|
// credentials have not been retrieved yet, or expired the provider's Retrieve
|
||||||
|
// method will be called.
|
||||||
|
//
|
||||||
|
// Returns and error if the provider's retrieve method returns an error.
|
||||||
|
func (p *CredentialsCache) Retrieve(ctx context.Context) (Credentials, error) {
|
||||||
|
if creds := p.getCreds(); creds != nil {
|
||||||
|
return *creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resCh := p.sf.DoChan("", func() (interface{}, error) {
|
||||||
|
return p.singleRetrieve(&suppressedContext{ctx})
|
||||||
|
})
|
||||||
|
select {
|
||||||
|
case res := <-resCh:
|
||||||
|
return res.Val.(Credentials), res.Err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return Credentials{}, &RequestCanceledError{Err: ctx.Err()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CredentialsCache) singleRetrieve(ctx context.Context) (interface{}, error) {
|
||||||
|
if creds := p.getCreds(); creds != nil {
|
||||||
|
return *creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
creds, err := p.provider.Retrieve(ctx)
|
||||||
|
if err == nil {
|
||||||
|
if creds.CanExpire {
|
||||||
|
randFloat64, err := sdkrand.CryptoRandFloat64()
|
||||||
|
if err != nil {
|
||||||
|
return Credentials{}, err
|
||||||
|
}
|
||||||
|
jitter := time.Duration(randFloat64 * p.options.ExpiryWindowJitterFrac * float64(p.options.ExpiryWindow))
|
||||||
|
creds.Expires = creds.Expires.Add(-(p.options.ExpiryWindow - jitter))
|
||||||
|
}
|
||||||
|
|
||||||
|
p.creds.Store(&creds)
|
||||||
|
}
|
||||||
|
|
||||||
|
return creds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CredentialsCache) getCreds() *Credentials {
|
||||||
|
v := p.creds.Load()
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c := v.(*Credentials)
|
||||||
|
if c != nil && c.HasKeys() && !c.Expired() {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate will invalidate the cached credentials. The next call to Retrieve
|
||||||
|
// will cause the provider's Retrieve method to be called.
|
||||||
|
func (p *CredentialsCache) Invalidate() {
|
||||||
|
p.creds.Store((*Credentials)(nil))
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnonymousCredentials provides a sentinel CredentialsProvider that should be
|
||||||
|
// used to instruct the SDK's signing middleware to not sign the request.
|
||||||
|
//
|
||||||
|
// Using `nil` credentials when configuring an API client will achieve the same
|
||||||
|
// result. The AnonymousCredentials type allows you to configure the SDK's
|
||||||
|
// external config loading to not attempt to source credentials from the shared
|
||||||
|
// config or environment.
|
||||||
|
//
|
||||||
|
// For example you can use this CredentialsProvider with an API client's
|
||||||
|
// Options to instruct the client not to sign a request for accessing public
|
||||||
|
// S3 bucket objects.
|
||||||
|
//
|
||||||
|
// The following example demonstrates using the AnonymousCredentials to prevent
|
||||||
|
// SDK's external config loading attempt to resolve credentials.
|
||||||
|
//
|
||||||
|
// cfg, err := config.LoadDefaultConfig(context.TODO(),
|
||||||
|
// config.WithCredentialsProvider(aws.AnonymousCredentials{}),
|
||||||
|
// )
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("failed to load config, %v", err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// client := s3.NewFromConfig(cfg)
|
||||||
|
//
|
||||||
|
// Alternatively you can leave the API client Option's `Credential` member to
|
||||||
|
// nil. If using the `NewFromConfig` constructor you'll need to explicitly set
|
||||||
|
// the `Credentials` member to nil, if the external config resolved a
|
||||||
|
// credential provider.
|
||||||
|
//
|
||||||
|
// client := s3.New(s3.Options{
|
||||||
|
// // Credentials defaults to a nil value.
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// This can also be configured for specific operations calls too.
|
||||||
|
//
|
||||||
|
// cfg, err := config.LoadDefaultConfig(context.TODO())
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("failed to load config, %v", err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// client := s3.NewFromConfig(config)
|
||||||
|
//
|
||||||
|
// result, err := client.GetObject(context.TODO(), s3.GetObject{
|
||||||
|
// Bucket: aws.String("example-bucket"),
|
||||||
|
// Key: aws.String("example-key"),
|
||||||
|
// }, func(o *s3.Options) {
|
||||||
|
// o.Credentials = nil
|
||||||
|
// // Or
|
||||||
|
// o.Credentials = aws.AnonymousCredentials{}
|
||||||
|
// })
|
||||||
|
type AnonymousCredentials struct{}
|
||||||
|
|
||||||
|
// Retrieve implements the CredentialsProvider interface, but will always
|
||||||
|
// return error, and cannot be used to sign a request. The AnonymousCredentials
|
||||||
|
// type is used as a sentinel type instructing the AWS request signing
|
||||||
|
// middleware to not sign a request.
|
||||||
|
func (AnonymousCredentials) Retrieve(context.Context) (Credentials, error) {
|
||||||
|
return Credentials{Source: "AnonymousCredentials"},
|
||||||
|
fmt.Errorf("the AnonymousCredentials is not a valid credential provider, and cannot be used to sign AWS requests with")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Credentials is the AWS credentials value for individual credential fields.
|
||||||
|
type Credentials struct {
|
||||||
|
// AWS Access key ID
|
||||||
|
AccessKeyID string
|
||||||
|
|
||||||
|
// AWS Secret Access Key
|
||||||
|
SecretAccessKey string
|
||||||
|
|
||||||
|
// AWS Session Token
|
||||||
|
SessionToken string
|
||||||
|
|
||||||
|
// Source of the credentials
|
||||||
|
Source string
|
||||||
|
|
||||||
|
// Time the credentials will expire.
|
||||||
|
CanExpire bool
|
||||||
|
Expires time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expired returns if the credentials have expired.
|
||||||
|
func (v Credentials) Expired() bool {
|
||||||
|
if v.CanExpire {
|
||||||
|
// Calling Round(0) on the current time will truncate the monotonic reading only. Ensures credential expiry
|
||||||
|
// time is always based on reported wall-clock time.
|
||||||
|
return !v.Expires.After(sdk.NowTime().Round(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasKeys returns if the credentials keys are set.
|
||||||
|
func (v Credentials) HasKeys() bool {
|
||||||
|
return len(v.AccessKeyID) > 0 && len(v.SecretAccessKey) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// A CredentialsProvider is the interface for any component which will provide
|
||||||
|
// credentials Credentials. A CredentialsProvider is required to manage its own
|
||||||
|
// Expired state, and what to be expired means.
|
||||||
|
//
|
||||||
|
// A credentials provider implementation can be wrapped with a CredentialCache
|
||||||
|
// to cache the credential value retrieved. Without the cache the SDK will
|
||||||
|
// attempt to retrieve the credentials for every request.
|
||||||
|
type CredentialsProvider interface {
|
||||||
|
// Retrieve returns nil if it successfully retrieved the value.
|
||||||
|
// Error is returned if the value were not obtainable, or empty.
|
||||||
|
Retrieve(ctx context.Context) (Credentials, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CredentialsProviderFunc provides a helper wrapping a function value to
|
||||||
|
// satisfy the CredentialsProvider interface.
|
||||||
|
type CredentialsProviderFunc func(context.Context) (Credentials, error)
|
||||||
|
|
||||||
|
// Retrieve delegates to the function value the CredentialsProviderFunc wraps.
|
||||||
|
func (fn CredentialsProviderFunc) Retrieve(ctx context.Context) (Credentials, error) {
|
||||||
|
return fn(ctx)
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Package aws provides the core SDK's utilities and shared types. Use this package's
|
||||||
|
// utilities to simplify setting and reading API operations parameters.
|
||||||
|
//
|
||||||
|
// Value and Pointer Conversion Utilities
|
||||||
|
//
|
||||||
|
// This package includes a helper conversion utility for each scalar type the SDK's
|
||||||
|
// API use. These utilities make getting a pointer of the scalar, and dereferencing
|
||||||
|
// a pointer easier.
|
||||||
|
//
|
||||||
|
// Each conversion utility comes in two forms. Value to Pointer and Pointer to Value.
|
||||||
|
// The Pointer to value will safely dereference the pointer and return its value.
|
||||||
|
// If the pointer was nil, the scalar's zero value will be returned.
|
||||||
|
//
|
||||||
|
// The value to pointer functions will be named after the scalar type. So get a
|
||||||
|
// *string from a string value use the "String" function. This makes it easy to
|
||||||
|
// to get pointer of a literal string value, because getting the address of a
|
||||||
|
// literal requires assigning the value to a variable first.
|
||||||
|
//
|
||||||
|
// var strPtr *string
|
||||||
|
//
|
||||||
|
// // Without the SDK's conversion functions
|
||||||
|
// str := "my string"
|
||||||
|
// strPtr = &str
|
||||||
|
//
|
||||||
|
// // With the SDK's conversion functions
|
||||||
|
// strPtr = aws.String("my string")
|
||||||
|
//
|
||||||
|
// // Convert *string to string value
|
||||||
|
// str = aws.ToString(strPtr)
|
||||||
|
//
|
||||||
|
// In addition to scalars the aws package also includes conversion utilities for
|
||||||
|
// map and slice for commonly types used in API parameters. The map and slice
|
||||||
|
// conversion functions use similar naming pattern as the scalar conversion
|
||||||
|
// functions.
|
||||||
|
//
|
||||||
|
// var strPtrs []*string
|
||||||
|
// var strs []string = []string{"Go", "Gophers", "Go"}
|
||||||
|
//
|
||||||
|
// // Convert []string to []*string
|
||||||
|
// strPtrs = aws.StringSlice(strs)
|
||||||
|
//
|
||||||
|
// // Convert []*string to []string
|
||||||
|
// strs = aws.ToStringSlice(strPtrs)
|
||||||
|
//
|
||||||
|
// SDK Default HTTP Client
|
||||||
|
//
|
||||||
|
// The SDK will use the http.DefaultClient if a HTTP client is not provided to
|
||||||
|
// the SDK's Session, or service client constructor. This means that if the
|
||||||
|
// http.DefaultClient is modified by other components of your application the
|
||||||
|
// modifications will be picked up by the SDK as well.
|
||||||
|
//
|
||||||
|
// In some cases this might be intended, but it is a better practice to create
|
||||||
|
// a custom HTTP Client to share explicitly through your application. You can
|
||||||
|
// configure the SDK to use the custom HTTP Client by setting the HTTPClient
|
||||||
|
// value of the SDK's Config type when creating a Session or service client.
|
||||||
|
package aws
|
||||||
|
|
||||||
|
// generate.go uses a build tag of "ignore", go run doesn't need to specify
|
||||||
|
// this because go run ignores all build flags when running a go file directly.
|
||||||
|
//go:generate go run -tags codegen generate.go
|
||||||
|
//go:generate go run -tags codegen logging_generate.go
|
||||||
|
//go:generate gofmt -w -s .
|
|
@ -0,0 +1,113 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint represents the endpoint a service client should make API operation
|
||||||
|
// calls to.
|
||||||
|
//
|
||||||
|
// The SDK will automatically resolve these endpoints per API client using an
|
||||||
|
// internal endpoint resolvers. If you'd like to provide custom endpoint
|
||||||
|
// resolving behavior you can implement the EndpointResolver interface.
|
||||||
|
type Endpoint struct {
|
||||||
|
// The base URL endpoint the SDK API clients will use to make API calls to.
|
||||||
|
// The SDK will suffix URI path and query elements to this endpoint.
|
||||||
|
URL string
|
||||||
|
|
||||||
|
// Specifies if the endpoint's hostname can be modified by the SDK's API
|
||||||
|
// client.
|
||||||
|
//
|
||||||
|
// If the hostname is mutable the SDK API clients may modify any part of
|
||||||
|
// the hostname based on the requirements of the API, (e.g. adding, or
|
||||||
|
// removing content in the hostname). Such as, Amazon S3 API client
|
||||||
|
// prefixing "bucketname" to the hostname, or changing the
|
||||||
|
// hostname service name component from "s3." to "s3-accesspoint.dualstack."
|
||||||
|
// for the dualstack endpoint of an S3 Accesspoint resource.
|
||||||
|
//
|
||||||
|
// Care should be taken when providing a custom endpoint for an API. If the
|
||||||
|
// endpoint hostname is mutable, and the client cannot modify the endpoint
|
||||||
|
// correctly, the operation call will most likely fail, or have undefined
|
||||||
|
// behavior.
|
||||||
|
//
|
||||||
|
// If hostname is immutable, the SDK API clients will not modify the
|
||||||
|
// hostname of the URL. This may cause the API client not to function
|
||||||
|
// correctly if the API requires the operation specific hostname values
|
||||||
|
// to be used by the client.
|
||||||
|
//
|
||||||
|
// This flag does not modify the API client's behavior if this endpoint
|
||||||
|
// will be used instead of Endpoint Discovery, or if the endpoint will be
|
||||||
|
// used to perform Endpoint Discovery. That behavior is configured via the
|
||||||
|
// API Client's Options.
|
||||||
|
HostnameImmutable bool
|
||||||
|
|
||||||
|
// The AWS partition the endpoint belongs to.
|
||||||
|
PartitionID string
|
||||||
|
|
||||||
|
// The service name that should be used for signing the requests to the
|
||||||
|
// endpoint.
|
||||||
|
SigningName string
|
||||||
|
|
||||||
|
// The region that should be used for signing the request to the endpoint.
|
||||||
|
SigningRegion string
|
||||||
|
|
||||||
|
// The signing method that should be used for signing the requests to the
|
||||||
|
// endpoint.
|
||||||
|
SigningMethod string
|
||||||
|
|
||||||
|
// The source of the Endpoint. By default, this will be EndpointSourceServiceMetadata.
|
||||||
|
// When providing a custom endpoint, you should set the source as EndpointSourceCustom.
|
||||||
|
// If source is not provided when providing a custom endpoint, the SDK may not
|
||||||
|
// perform required host mutations correctly. Source should be used along with
|
||||||
|
// HostnameImmutable property as per the usage requirement.
|
||||||
|
Source EndpointSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointSource is the endpoint source type.
|
||||||
|
type EndpointSource int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EndpointSourceServiceMetadata denotes service modeled endpoint metadata is used as Endpoint Source.
|
||||||
|
EndpointSourceServiceMetadata EndpointSource = iota
|
||||||
|
|
||||||
|
// EndpointSourceCustom denotes endpoint is a custom endpoint. This source should be used when
|
||||||
|
// user provides a custom endpoint to be used by the SDK.
|
||||||
|
EndpointSourceCustom
|
||||||
|
)
|
||||||
|
|
||||||
|
// EndpointNotFoundError is a sentinel error to indicate that the
|
||||||
|
// EndpointResolver implementation was unable to resolve an endpoint for the
|
||||||
|
// given service and region. Resolvers should use this to indicate that an API
|
||||||
|
// client should fallback and attempt to use it's internal default resolver to
|
||||||
|
// resolve the endpoint.
|
||||||
|
type EndpointNotFoundError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is the error message.
|
||||||
|
func (e *EndpointNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("endpoint not found, %v", e.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying error.
|
||||||
|
func (e *EndpointNotFoundError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointResolver is an endpoint resolver that can be used to provide or
|
||||||
|
// override an endpoint for the given service and region. API clients will
|
||||||
|
// attempt to use the EndpointResolver first to resolve an endpoint if
|
||||||
|
// available. If the EndpointResolver returns an EndpointNotFoundError error,
|
||||||
|
// API clients will fallback to attempting to resolve the endpoint using its
|
||||||
|
// internal default endpoint resolver.
|
||||||
|
type EndpointResolver interface {
|
||||||
|
ResolveEndpoint(service, region string) (Endpoint, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointResolverFunc wraps a function to satisfy the EndpointResolver interface.
|
||||||
|
type EndpointResolverFunc func(service, region string) (Endpoint, error)
|
||||||
|
|
||||||
|
// ResolveEndpoint calls the wrapped function and returns the results.
|
||||||
|
func (e EndpointResolverFunc) ResolveEndpoint(service, region string) (Endpoint, error) {
|
||||||
|
return e(service, region)
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
// MissingRegionError is an error that is returned if region configuration
|
||||||
|
// value was not found.
|
||||||
|
type MissingRegionError struct{}
|
||||||
|
|
||||||
|
func (*MissingRegionError) Error() string {
|
||||||
|
return "an AWS region is required, but was not found"
|
||||||
|
}
|
|
@ -0,0 +1,344 @@
|
||||||
|
// Code generated by aws/generate.go DO NOT EDIT.
|
||||||
|
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/smithy-go/ptr"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToBool returns bool value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a bool zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToBool(p *bool) (v bool) {
|
||||||
|
return ptr.ToBool(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBoolSlice returns a slice of bool values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a bool
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToBoolSlice(vs []*bool) []bool {
|
||||||
|
return ptr.ToBoolSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToBoolMap returns a map of bool values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The bool
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToBoolMap(vs map[string]*bool) map[string]bool {
|
||||||
|
return ptr.ToBoolMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToByte returns byte value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a byte zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToByte(p *byte) (v byte) {
|
||||||
|
return ptr.ToByte(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToByteSlice returns a slice of byte values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a byte
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToByteSlice(vs []*byte) []byte {
|
||||||
|
return ptr.ToByteSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToByteMap returns a map of byte values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The byte
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToByteMap(vs map[string]*byte) map[string]byte {
|
||||||
|
return ptr.ToByteMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString returns string value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a string zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToString(p *string) (v string) {
|
||||||
|
return ptr.ToString(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStringSlice returns a slice of string values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a string
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToStringSlice(vs []*string) []string {
|
||||||
|
return ptr.ToStringSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStringMap returns a map of string values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The string
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToStringMap(vs map[string]*string) map[string]string {
|
||||||
|
return ptr.ToStringMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt returns int value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a int zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToInt(p *int) (v int) {
|
||||||
|
return ptr.ToInt(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToIntSlice returns a slice of int values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a int
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToIntSlice(vs []*int) []int {
|
||||||
|
return ptr.ToIntSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToIntMap returns a map of int values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The int
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToIntMap(vs map[string]*int) map[string]int {
|
||||||
|
return ptr.ToIntMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt8 returns int8 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a int8 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToInt8(p *int8) (v int8) {
|
||||||
|
return ptr.ToInt8(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt8Slice returns a slice of int8 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a int8
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToInt8Slice(vs []*int8) []int8 {
|
||||||
|
return ptr.ToInt8Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt8Map returns a map of int8 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The int8
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToInt8Map(vs map[string]*int8) map[string]int8 {
|
||||||
|
return ptr.ToInt8Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt16 returns int16 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a int16 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToInt16(p *int16) (v int16) {
|
||||||
|
return ptr.ToInt16(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt16Slice returns a slice of int16 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a int16
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToInt16Slice(vs []*int16) []int16 {
|
||||||
|
return ptr.ToInt16Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt16Map returns a map of int16 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The int16
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToInt16Map(vs map[string]*int16) map[string]int16 {
|
||||||
|
return ptr.ToInt16Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt32 returns int32 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a int32 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToInt32(p *int32) (v int32) {
|
||||||
|
return ptr.ToInt32(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt32Slice returns a slice of int32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a int32
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToInt32Slice(vs []*int32) []int32 {
|
||||||
|
return ptr.ToInt32Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt32Map returns a map of int32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The int32
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToInt32Map(vs map[string]*int32) map[string]int32 {
|
||||||
|
return ptr.ToInt32Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt64 returns int64 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a int64 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToInt64(p *int64) (v int64) {
|
||||||
|
return ptr.ToInt64(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt64Slice returns a slice of int64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a int64
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToInt64Slice(vs []*int64) []int64 {
|
||||||
|
return ptr.ToInt64Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt64Map returns a map of int64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The int64
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToInt64Map(vs map[string]*int64) map[string]int64 {
|
||||||
|
return ptr.ToInt64Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint returns uint value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a uint zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToUint(p *uint) (v uint) {
|
||||||
|
return ptr.ToUint(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUintSlice returns a slice of uint values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a uint
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToUintSlice(vs []*uint) []uint {
|
||||||
|
return ptr.ToUintSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUintMap returns a map of uint values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The uint
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToUintMap(vs map[string]*uint) map[string]uint {
|
||||||
|
return ptr.ToUintMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint8 returns uint8 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a uint8 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToUint8(p *uint8) (v uint8) {
|
||||||
|
return ptr.ToUint8(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint8Slice returns a slice of uint8 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a uint8
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToUint8Slice(vs []*uint8) []uint8 {
|
||||||
|
return ptr.ToUint8Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint8Map returns a map of uint8 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The uint8
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToUint8Map(vs map[string]*uint8) map[string]uint8 {
|
||||||
|
return ptr.ToUint8Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint16 returns uint16 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a uint16 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToUint16(p *uint16) (v uint16) {
|
||||||
|
return ptr.ToUint16(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint16Slice returns a slice of uint16 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a uint16
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToUint16Slice(vs []*uint16) []uint16 {
|
||||||
|
return ptr.ToUint16Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint16Map returns a map of uint16 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The uint16
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToUint16Map(vs map[string]*uint16) map[string]uint16 {
|
||||||
|
return ptr.ToUint16Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint32 returns uint32 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a uint32 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToUint32(p *uint32) (v uint32) {
|
||||||
|
return ptr.ToUint32(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint32Slice returns a slice of uint32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a uint32
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToUint32Slice(vs []*uint32) []uint32 {
|
||||||
|
return ptr.ToUint32Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint32Map returns a map of uint32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The uint32
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToUint32Map(vs map[string]*uint32) map[string]uint32 {
|
||||||
|
return ptr.ToUint32Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint64 returns uint64 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a uint64 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToUint64(p *uint64) (v uint64) {
|
||||||
|
return ptr.ToUint64(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint64Slice returns a slice of uint64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a uint64
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToUint64Slice(vs []*uint64) []uint64 {
|
||||||
|
return ptr.ToUint64Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToUint64Map returns a map of uint64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The uint64
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToUint64Map(vs map[string]*uint64) map[string]uint64 {
|
||||||
|
return ptr.ToUint64Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat32 returns float32 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a float32 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToFloat32(p *float32) (v float32) {
|
||||||
|
return ptr.ToFloat32(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat32Slice returns a slice of float32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a float32
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToFloat32Slice(vs []*float32) []float32 {
|
||||||
|
return ptr.ToFloat32Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat32Map returns a map of float32 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The float32
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToFloat32Map(vs map[string]*float32) map[string]float32 {
|
||||||
|
return ptr.ToFloat32Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat64 returns float64 value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a float64 zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToFloat64(p *float64) (v float64) {
|
||||||
|
return ptr.ToFloat64(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat64Slice returns a slice of float64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a float64
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToFloat64Slice(vs []*float64) []float64 {
|
||||||
|
return ptr.ToFloat64Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFloat64Map returns a map of float64 values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The float64
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToFloat64Map(vs map[string]*float64) map[string]float64 {
|
||||||
|
return ptr.ToFloat64Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTime returns time.Time value dereferenced if the passed
|
||||||
|
// in pointer was not nil. Returns a time.Time zero value if the
|
||||||
|
// pointer was nil.
|
||||||
|
func ToTime(p *time.Time) (v time.Time) {
|
||||||
|
return ptr.ToTime(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTimeSlice returns a slice of time.Time values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. Returns a time.Time
|
||||||
|
// zero value if the pointer was nil.
|
||||||
|
func ToTimeSlice(vs []*time.Time) []time.Time {
|
||||||
|
return ptr.ToTimeSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToTimeMap returns a map of time.Time values, that are
|
||||||
|
// dereferenced if the passed in pointer was not nil. The time.Time
|
||||||
|
// zero value is used if the pointer was nil.
|
||||||
|
func ToTimeMap(vs map[string]*time.Time) map[string]time.Time {
|
||||||
|
return ptr.ToTimeMap(vs)
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Code generated by aws/logging_generate.go DO NOT EDIT.
|
||||||
|
|
||||||
|
package aws
|
||||||
|
|
||||||
|
// ClientLogMode represents the logging mode of SDK clients. The client logging mode is a bit-field where
|
||||||
|
// each bit is a flag that describes the logging behavior for one or more client components.
|
||||||
|
// The entire 64-bit group is reserved for later expansion by the SDK.
|
||||||
|
//
|
||||||
|
// Example: Setting ClientLogMode to enable logging of retries and requests
|
||||||
|
// clientLogMode := aws.LogRetries | aws.LogRequest
|
||||||
|
//
|
||||||
|
// Example: Adding an additional log mode to an existing ClientLogMode value
|
||||||
|
// clientLogMode |= aws.LogResponse
|
||||||
|
type ClientLogMode uint64
|
||||||
|
|
||||||
|
// Supported ClientLogMode bits that can be configured to toggle logging of specific SDK events.
|
||||||
|
const (
|
||||||
|
LogSigning ClientLogMode = 1 << (64 - 1 - iota)
|
||||||
|
LogRetries
|
||||||
|
LogRequest
|
||||||
|
LogRequestWithBody
|
||||||
|
LogResponse
|
||||||
|
LogResponseWithBody
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsSigning returns whether the Signing logging mode bit is set
|
||||||
|
func (m ClientLogMode) IsSigning() bool {
|
||||||
|
return m&LogSigning != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRetries returns whether the Retries logging mode bit is set
|
||||||
|
func (m ClientLogMode) IsRetries() bool {
|
||||||
|
return m&LogRetries != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequest returns whether the Request logging mode bit is set
|
||||||
|
func (m ClientLogMode) IsRequest() bool {
|
||||||
|
return m&LogRequest != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequestWithBody returns whether the RequestWithBody logging mode bit is set
|
||||||
|
func (m ClientLogMode) IsRequestWithBody() bool {
|
||||||
|
return m&LogRequestWithBody != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsResponse returns whether the Response logging mode bit is set
|
||||||
|
func (m ClientLogMode) IsResponse() bool {
|
||||||
|
return m&LogResponse != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsResponseWithBody returns whether the ResponseWithBody logging mode bit is set
|
||||||
|
func (m ClientLogMode) IsResponseWithBody() bool {
|
||||||
|
return m&LogResponseWithBody != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearSigning clears the Signing logging mode bit
|
||||||
|
func (m *ClientLogMode) ClearSigning() {
|
||||||
|
*m &^= LogSigning
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRetries clears the Retries logging mode bit
|
||||||
|
func (m *ClientLogMode) ClearRetries() {
|
||||||
|
*m &^= LogRetries
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRequest clears the Request logging mode bit
|
||||||
|
func (m *ClientLogMode) ClearRequest() {
|
||||||
|
*m &^= LogRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRequestWithBody clears the RequestWithBody logging mode bit
|
||||||
|
func (m *ClientLogMode) ClearRequestWithBody() {
|
||||||
|
*m &^= LogRequestWithBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearResponse clears the Response logging mode bit
|
||||||
|
func (m *ClientLogMode) ClearResponse() {
|
||||||
|
*m &^= LogResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearResponseWithBody clears the ResponseWithBody logging mode bit
|
||||||
|
func (m *ClientLogMode) ClearResponseWithBody() {
|
||||||
|
*m &^= LogResponseWithBody
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// +build clientlogmode
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var config = struct {
|
||||||
|
ModeBits []string
|
||||||
|
}{
|
||||||
|
// Items should be appended only to keep bit-flag positions stable
|
||||||
|
ModeBits: []string{
|
||||||
|
"Signing",
|
||||||
|
"Retries",
|
||||||
|
"Request",
|
||||||
|
"RequestWithBody",
|
||||||
|
"Response",
|
||||||
|
"ResponseWithBody",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmpl = template.Must(template.New("ClientLogMode").Funcs(map[string]interface{}{
|
||||||
|
"symbolName": func(name string) string {
|
||||||
|
return "Log" + name
|
||||||
|
},
|
||||||
|
}).Parse(`// Code generated by aws/logging_generate.go DO NOT EDIT.
|
||||||
|
|
||||||
|
package aws
|
||||||
|
|
||||||
|
// ClientLogMode represents the logging mode of SDK clients. The client logging mode is a bit-field where
|
||||||
|
// each bit is a flag that describes the logging behavior for one or more client components.
|
||||||
|
// The entire 64-bit group is reserved for later expansion by the SDK.
|
||||||
|
//
|
||||||
|
// Example: Setting ClientLogMode to enable logging of retries and requests
|
||||||
|
// clientLogMode := aws.LogRetries | aws.LogRequest
|
||||||
|
//
|
||||||
|
// Example: Adding an additional log mode to an existing ClientLogMode value
|
||||||
|
// clientLogMode |= aws.LogResponse
|
||||||
|
type ClientLogMode uint64
|
||||||
|
|
||||||
|
// Supported ClientLogMode bits that can be configured to toggle logging of specific SDK events.
|
||||||
|
const (
|
||||||
|
{{- range $index, $field := .ModeBits }}
|
||||||
|
{{ (symbolName $field) }}{{- if (eq 0 $index) }} ClientLogMode = 1 << (64 - 1 - iota){{- end }}
|
||||||
|
{{- end }}
|
||||||
|
)
|
||||||
|
|
||||||
|
{{ range $_, $field := .ModeBits }}
|
||||||
|
// Is{{- $field }} returns whether the {{ $field }} logging mode bit is set
|
||||||
|
func (m ClientLogMode) Is{{- $field }}() bool {
|
||||||
|
return m&{{- (symbolName $field) }} != 0
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ range $_, $field := .ModeBits }}
|
||||||
|
// Clear{{- $field }} clears the {{ $field }} logging mode bit
|
||||||
|
func (m *ClientLogMode) Clear{{- $field }}() {
|
||||||
|
*m &^= {{- (symbolName $field) }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
`))
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
file, err := os.Create("logging.go")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
err = tmpl.Execute(file, config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterServiceMetadata registers metadata about the service and operation into the middleware context
|
||||||
|
// so that it is available at runtime for other middleware to introspect.
|
||||||
|
type RegisterServiceMetadata struct {
|
||||||
|
ServiceID string
|
||||||
|
SigningName string
|
||||||
|
Region string
|
||||||
|
OperationName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the middleware identifier.
|
||||||
|
func (s *RegisterServiceMetadata) ID() string {
|
||||||
|
return "RegisterServiceMetadata"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleInitialize registers service metadata information into the middleware context, allowing for introspection.
|
||||||
|
func (s RegisterServiceMetadata) HandleInitialize(
|
||||||
|
ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler,
|
||||||
|
) (out middleware.InitializeOutput, metadata middleware.Metadata, err error) {
|
||||||
|
if len(s.ServiceID) > 0 {
|
||||||
|
ctx = SetServiceID(ctx, s.ServiceID)
|
||||||
|
}
|
||||||
|
if len(s.SigningName) > 0 {
|
||||||
|
ctx = SetSigningName(ctx, s.SigningName)
|
||||||
|
}
|
||||||
|
if len(s.Region) > 0 {
|
||||||
|
ctx = setRegion(ctx, s.Region)
|
||||||
|
}
|
||||||
|
if len(s.OperationName) > 0 {
|
||||||
|
ctx = setOperationName(ctx, s.OperationName)
|
||||||
|
}
|
||||||
|
return next.HandleInitialize(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// service metadata keys for storing and lookup of runtime stack information.
|
||||||
|
type (
|
||||||
|
serviceIDKey struct{}
|
||||||
|
signingNameKey struct{}
|
||||||
|
signingRegionKey struct{}
|
||||||
|
regionKey struct{}
|
||||||
|
operationNameKey struct{}
|
||||||
|
partitionIDKey struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetServiceID retrieves the service id from the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func GetServiceID(ctx context.Context) (v string) {
|
||||||
|
v, _ = middleware.GetStackValue(ctx, serviceIDKey{}).(string)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSigningName retrieves the service signing name from the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func GetSigningName(ctx context.Context) (v string) {
|
||||||
|
v, _ = middleware.GetStackValue(ctx, signingNameKey{}).(string)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSigningRegion retrieves the region from the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func GetSigningRegion(ctx context.Context) (v string) {
|
||||||
|
v, _ = middleware.GetStackValue(ctx, signingRegionKey{}).(string)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegion retrieves the endpoint region from the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func GetRegion(ctx context.Context) (v string) {
|
||||||
|
v, _ = middleware.GetStackValue(ctx, regionKey{}).(string)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOperationName retrieves the service operation metadata from the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func GetOperationName(ctx context.Context) (v string) {
|
||||||
|
v, _ = middleware.GetStackValue(ctx, operationNameKey{}).(string)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPartitionID retrieves the endpoint partition id from the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func GetPartitionID(ctx context.Context) string {
|
||||||
|
v, _ := middleware.GetStackValue(ctx, partitionIDKey{}).(string)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSigningName set or modifies the signing name on the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func SetSigningName(ctx context.Context, value string) context.Context {
|
||||||
|
return middleware.WithStackValue(ctx, signingNameKey{}, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSigningRegion sets or modifies the region on the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func SetSigningRegion(ctx context.Context, value string) context.Context {
|
||||||
|
return middleware.WithStackValue(ctx, signingRegionKey{}, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetServiceID sets the service id on the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func SetServiceID(ctx context.Context, value string) context.Context {
|
||||||
|
return middleware.WithStackValue(ctx, serviceIDKey{}, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setRegion sets the endpoint region on the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func setRegion(ctx context.Context, value string) context.Context {
|
||||||
|
return middleware.WithStackValue(ctx, regionKey{}, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setOperationName sets the service operation on the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func setOperationName(ctx context.Context, value string) context.Context {
|
||||||
|
return middleware.WithStackValue(ctx, operationNameKey{}, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPartitionID sets the partition id of a resolved region on the context
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func SetPartitionID(ctx context.Context, value string) context.Context {
|
||||||
|
return middleware.WithStackValue(ctx, partitionIDKey{}, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndpointSource key
|
||||||
|
type endpointSourceKey struct{}
|
||||||
|
|
||||||
|
// GetEndpointSource returns an endpoint source if set on context
|
||||||
|
func GetEndpointSource(ctx context.Context) (v aws.EndpointSource) {
|
||||||
|
v, _ = middleware.GetStackValue(ctx, endpointSourceKey{}).(aws.EndpointSource)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEndpointSource sets endpoint source on context
|
||||||
|
func SetEndpointSource(ctx context.Context, value aws.EndpointSource) context.Context {
|
||||||
|
return middleware.WithStackValue(ctx, endpointSourceKey{}, value)
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/rand"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/sdk"
|
||||||
|
"github.com/aws/smithy-go/logging"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
smithyrand "github.com/aws/smithy-go/rand"
|
||||||
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientRequestID is a Smithy BuildMiddleware that will generate a unique ID for logical API operation
|
||||||
|
// invocation.
|
||||||
|
type ClientRequestID struct{}
|
||||||
|
|
||||||
|
// ID the identifier for the ClientRequestID
|
||||||
|
func (r *ClientRequestID) ID() string {
|
||||||
|
return "ClientRequestID"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleBuild attaches a unique operation invocation id for the operation to the request
|
||||||
|
func (r ClientRequestID) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (
|
||||||
|
out middleware.BuildOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
req, ok := in.Request.(*smithyhttp.Request)
|
||||||
|
if !ok {
|
||||||
|
return out, metadata, fmt.Errorf("unknown transport type %T", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
invocationID, err := smithyrand.NewUUID(rand.Reader).GetUUID()
|
||||||
|
if err != nil {
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const invocationIDHeader = "Amz-Sdk-Invocation-Id"
|
||||||
|
req.Header[invocationIDHeader] = append(req.Header[invocationIDHeader][:0], invocationID)
|
||||||
|
|
||||||
|
return next.HandleBuild(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordResponseTiming records the response timing for the SDK client requests.
|
||||||
|
type RecordResponseTiming struct{}
|
||||||
|
|
||||||
|
// ID is the middleware identifier
|
||||||
|
func (a *RecordResponseTiming) ID() string {
|
||||||
|
return "RecordResponseTiming"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleDeserialize calculates response metadata and clock skew
|
||||||
|
func (a RecordResponseTiming) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) (
|
||||||
|
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
out, metadata, err = next.HandleDeserialize(ctx, in)
|
||||||
|
responseAt := sdk.NowTime()
|
||||||
|
setResponseAt(&metadata, responseAt)
|
||||||
|
|
||||||
|
var serverTime time.Time
|
||||||
|
|
||||||
|
switch resp := out.RawResponse.(type) {
|
||||||
|
case *smithyhttp.Response:
|
||||||
|
respDateHeader := resp.Header.Get("Date")
|
||||||
|
if len(respDateHeader) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var parseErr error
|
||||||
|
serverTime, parseErr = smithyhttp.ParseTime(respDateHeader)
|
||||||
|
if parseErr != nil {
|
||||||
|
logger := middleware.GetLogger(ctx)
|
||||||
|
logger.Logf(logging.Warn, "failed to parse response Date header value, got %v",
|
||||||
|
parseErr.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
setServerTime(&metadata, serverTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !serverTime.IsZero() {
|
||||||
|
attemptSkew := serverTime.Sub(responseAt)
|
||||||
|
setAttemptSkew(&metadata, attemptSkew)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseAtKey struct{}
|
||||||
|
|
||||||
|
// GetResponseAt returns the time response was received at.
|
||||||
|
func GetResponseAt(metadata middleware.Metadata) (v time.Time, ok bool) {
|
||||||
|
v, ok = metadata.Get(responseAtKey{}).(time.Time)
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// setResponseAt sets the response time on the metadata.
|
||||||
|
func setResponseAt(metadata *middleware.Metadata, v time.Time) {
|
||||||
|
metadata.Set(responseAtKey{}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverTimeKey struct{}
|
||||||
|
|
||||||
|
// GetServerTime returns the server time for response.
|
||||||
|
func GetServerTime(metadata middleware.Metadata) (v time.Time, ok bool) {
|
||||||
|
v, ok = metadata.Get(serverTimeKey{}).(time.Time)
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// setServerTime sets the server time on the metadata.
|
||||||
|
func setServerTime(metadata *middleware.Metadata, v time.Time) {
|
||||||
|
metadata.Set(serverTimeKey{}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type attemptSkewKey struct{}
|
||||||
|
|
||||||
|
// GetAttemptSkew returns Attempt clock skew for response from metadata.
|
||||||
|
func GetAttemptSkew(metadata middleware.Metadata) (v time.Duration, ok bool) {
|
||||||
|
v, ok = metadata.Get(attemptSkewKey{}).(time.Duration)
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// setAttemptSkew sets the attempt clock skew on the metadata.
|
||||||
|
func setAttemptSkew(metadata *middleware.Metadata, v time.Duration) {
|
||||||
|
metadata.Set(attemptSkewKey{}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddClientRequestIDMiddleware adds ClientRequestID to the middleware stack
|
||||||
|
func AddClientRequestIDMiddleware(stack *middleware.Stack) error {
|
||||||
|
return stack.Build.Add(&ClientRequestID{}, middleware.After)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRecordResponseTiming adds RecordResponseTiming middleware to the
|
||||||
|
// middleware stack.
|
||||||
|
func AddRecordResponseTiming(stack *middleware.Stack) error {
|
||||||
|
return stack.Deserialize.Add(&RecordResponseTiming{}, middleware.After)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawResponseKey is the accessor key used to store and access the
|
||||||
|
// raw response within the response metadata.
|
||||||
|
type rawResponseKey struct{}
|
||||||
|
|
||||||
|
// addRawResponse middleware adds raw response on to the metadata
|
||||||
|
type addRawResponse struct{}
|
||||||
|
|
||||||
|
// ID the identifier for the ClientRequestID
|
||||||
|
func (m *addRawResponse) ID() string {
|
||||||
|
return "AddRawResponseToMetadata"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleDeserialize adds raw response on the middleware metadata
|
||||||
|
func (m addRawResponse) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) (
|
||||||
|
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
out, metadata, err = next.HandleDeserialize(ctx, in)
|
||||||
|
metadata.Set(rawResponseKey{}, out.RawResponse)
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRawResponseToMetadata adds middleware to the middleware stack that
|
||||||
|
// store raw response on to the metadata.
|
||||||
|
func AddRawResponseToMetadata(stack *middleware.Stack) error {
|
||||||
|
return stack.Deserialize.Add(&addRawResponse{}, middleware.Before)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawResponse returns raw response set on metadata
|
||||||
|
func GetRawResponse(metadata middleware.Metadata) interface{} {
|
||||||
|
return metadata.Get(rawResponseKey{})
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// requestIDKey is used to retrieve request id from response metadata
|
||||||
|
type requestIDKey struct{}
|
||||||
|
|
||||||
|
// SetRequestIDMetadata sets the provided request id over middleware metadata
|
||||||
|
func SetRequestIDMetadata(metadata *middleware.Metadata, id string) {
|
||||||
|
metadata.Set(requestIDKey{}, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestIDMetadata retrieves the request id from middleware metadata
|
||||||
|
// returns string and bool indicating value of request id, whether request id was set.
|
||||||
|
func GetRequestIDMetadata(metadata middleware.Metadata) (string, bool) {
|
||||||
|
if !metadata.Has(requestIDKey{}) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := metadata.Get(requestIDKey{}).(string)
|
||||||
|
if !ok {
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
return v, true
|
||||||
|
}
|
49
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/request_id_retriever.go
generated
vendored
Normal file
49
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/request_id_retriever.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddRequestIDRetrieverMiddleware adds request id retriever middleware
|
||||||
|
func AddRequestIDRetrieverMiddleware(stack *middleware.Stack) error {
|
||||||
|
// add error wrapper middleware before operation deserializers so that it can wrap the error response
|
||||||
|
// returned by operation deserializers
|
||||||
|
return stack.Deserialize.Insert(&requestIDRetriever{}, "OperationDeserializer", middleware.Before)
|
||||||
|
}
|
||||||
|
|
||||||
|
type requestIDRetriever struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the middleware identifier
|
||||||
|
func (m *requestIDRetriever) ID() string {
|
||||||
|
return "RequestIDRetriever"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *requestIDRetriever) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) (
|
||||||
|
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
out, metadata, err = next.HandleDeserialize(ctx, in)
|
||||||
|
|
||||||
|
resp, ok := out.RawResponse.(*smithyhttp.Response)
|
||||||
|
if !ok {
|
||||||
|
// No raw response to wrap with.
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different header which can map to request id
|
||||||
|
requestIDHeaderList := []string{"X-Amzn-Requestid", "X-Amz-RequestId"}
|
||||||
|
|
||||||
|
for _, h := range requestIDHeaderList {
|
||||||
|
// check for headers known to contain Request id
|
||||||
|
if v := resp.Header.Get(h); len(v) != 0 {
|
||||||
|
// set reqID on metadata for successful responses.
|
||||||
|
SetRequestIDMetadata(&metadata, v)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
|
@ -0,0 +1,256 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var languageVersion = strings.TrimPrefix(runtime.Version(), "go")
|
||||||
|
|
||||||
|
// SDKAgentKeyType is the metadata type to add to the SDK agent string
|
||||||
|
type SDKAgentKeyType int
|
||||||
|
|
||||||
|
// The set of valid SDKAgentKeyType constants. If an unknown value is assigned for SDKAgentKeyType it will
|
||||||
|
// be mapped to AdditionalMetadata.
|
||||||
|
const (
|
||||||
|
_ SDKAgentKeyType = iota
|
||||||
|
APIMetadata
|
||||||
|
OperatingSystemMetadata
|
||||||
|
LanguageMetadata
|
||||||
|
EnvironmentMetadata
|
||||||
|
FeatureMetadata
|
||||||
|
ConfigMetadata
|
||||||
|
FrameworkMetadata
|
||||||
|
AdditionalMetadata
|
||||||
|
ApplicationIdentifier
|
||||||
|
)
|
||||||
|
|
||||||
|
func (k SDKAgentKeyType) string() string {
|
||||||
|
switch k {
|
||||||
|
case APIMetadata:
|
||||||
|
return "api"
|
||||||
|
case OperatingSystemMetadata:
|
||||||
|
return "os"
|
||||||
|
case LanguageMetadata:
|
||||||
|
return "lang"
|
||||||
|
case EnvironmentMetadata:
|
||||||
|
return "exec-env"
|
||||||
|
case FeatureMetadata:
|
||||||
|
return "ft"
|
||||||
|
case ConfigMetadata:
|
||||||
|
return "cfg"
|
||||||
|
case FrameworkMetadata:
|
||||||
|
return "lib"
|
||||||
|
case ApplicationIdentifier:
|
||||||
|
return "app"
|
||||||
|
case AdditionalMetadata:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return "md"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const execEnvVar = `AWS_EXECUTION_ENV`
|
||||||
|
|
||||||
|
// requestUserAgent is a build middleware that set the User-Agent for the request.
|
||||||
|
type requestUserAgent struct {
|
||||||
|
sdkAgent, userAgent *smithyhttp.UserAgentBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRequestUserAgent returns a new requestUserAgent which will set the User-Agent and X-Amz-User-Agent for the
|
||||||
|
// request.
|
||||||
|
//
|
||||||
|
// User-Agent example:
|
||||||
|
// aws-sdk-go-v2/1.2.3
|
||||||
|
//
|
||||||
|
// X-Amz-User-Agent example:
|
||||||
|
// aws-sdk-go-v2/1.2.3 md/GOOS/linux md/GOARCH/amd64 lang/go/1.15
|
||||||
|
func newRequestUserAgent() *requestUserAgent {
|
||||||
|
userAgent, sdkAgent := smithyhttp.NewUserAgentBuilder(), smithyhttp.NewUserAgentBuilder()
|
||||||
|
addProductName(userAgent)
|
||||||
|
addProductName(sdkAgent)
|
||||||
|
|
||||||
|
r := &requestUserAgent{
|
||||||
|
sdkAgent: sdkAgent,
|
||||||
|
userAgent: userAgent,
|
||||||
|
}
|
||||||
|
|
||||||
|
addSDKMetadata(r)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNormalizedOSName() (os string) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "android":
|
||||||
|
os = "android"
|
||||||
|
case "linux":
|
||||||
|
os = "linux"
|
||||||
|
case "windows":
|
||||||
|
os = "windows"
|
||||||
|
case "darwin":
|
||||||
|
// Due to Apple M1 we can't distinguish between macOS and iOS when GOOS/GOARCH is darwin/amd64
|
||||||
|
// For now declare this as "other" until we have a better detection mechanism.
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
os = "other"
|
||||||
|
}
|
||||||
|
return os
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSDKMetadata(r *requestUserAgent) {
|
||||||
|
r.AddSDKAgentKey(OperatingSystemMetadata, getNormalizedOSName())
|
||||||
|
r.AddSDKAgentKeyValue(LanguageMetadata, "go", languageVersion)
|
||||||
|
r.AddSDKAgentKeyValue(AdditionalMetadata, "GOOS", runtime.GOOS)
|
||||||
|
r.AddSDKAgentKeyValue(AdditionalMetadata, "GOARCH", runtime.GOARCH)
|
||||||
|
if ev := os.Getenv(execEnvVar); len(ev) > 0 {
|
||||||
|
r.AddSDKAgentKey(EnvironmentMetadata, ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addProductName(builder *smithyhttp.UserAgentBuilder) {
|
||||||
|
builder.AddKeyValue(aws.SDKName, aws.SDKVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUserAgentKey retrieves a requestUserAgent from the provided stack, or initializes one.
|
||||||
|
func AddUserAgentKey(key string) func(*middleware.Stack) error {
|
||||||
|
return func(stack *middleware.Stack) error {
|
||||||
|
requestUserAgent, err := getOrAddRequestUserAgent(stack)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
requestUserAgent.AddUserAgentKey(key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUserAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one.
|
||||||
|
func AddUserAgentKeyValue(key, value string) func(*middleware.Stack) error {
|
||||||
|
return func(stack *middleware.Stack) error {
|
||||||
|
requestUserAgent, err := getOrAddRequestUserAgent(stack)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
requestUserAgent.AddUserAgentKeyValue(key, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSDKAgentKey retrieves a requestUserAgent from the provided stack, or initializes one.
|
||||||
|
func AddSDKAgentKey(keyType SDKAgentKeyType, key string) func(*middleware.Stack) error {
|
||||||
|
return func(stack *middleware.Stack) error {
|
||||||
|
requestUserAgent, err := getOrAddRequestUserAgent(stack)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
requestUserAgent.AddSDKAgentKey(keyType, key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSDKAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one.
|
||||||
|
func AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) func(*middleware.Stack) error {
|
||||||
|
return func(stack *middleware.Stack) error {
|
||||||
|
requestUserAgent, err := getOrAddRequestUserAgent(stack)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
requestUserAgent.AddSDKAgentKeyValue(keyType, key, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRequestUserAgentMiddleware registers a requestUserAgent middleware on the stack if not present.
|
||||||
|
func AddRequestUserAgentMiddleware(stack *middleware.Stack) error {
|
||||||
|
_, err := getOrAddRequestUserAgent(stack)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOrAddRequestUserAgent(stack *middleware.Stack) (*requestUserAgent, error) {
|
||||||
|
id := (*requestUserAgent)(nil).ID()
|
||||||
|
bm, ok := stack.Build.Get(id)
|
||||||
|
if !ok {
|
||||||
|
bm = newRequestUserAgent()
|
||||||
|
err := stack.Build.Add(bm, middleware.After)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestUserAgent, ok := bm.(*requestUserAgent)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%T for %s middleware did not match expected type", bm, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestUserAgent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUserAgentKey adds the component identified by name to the User-Agent string.
|
||||||
|
func (u *requestUserAgent) AddUserAgentKey(key string) {
|
||||||
|
u.userAgent.AddKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUserAgentKeyValue adds the key identified by the given name and value to the User-Agent string.
|
||||||
|
func (u *requestUserAgent) AddUserAgentKeyValue(key, value string) {
|
||||||
|
u.userAgent.AddKeyValue(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUserAgentKey adds the component identified by name to the User-Agent string.
|
||||||
|
func (u *requestUserAgent) AddSDKAgentKey(keyType SDKAgentKeyType, key string) {
|
||||||
|
u.sdkAgent.AddKey(keyType.string() + "/" + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUserAgentKeyValue adds the key identified by the given name and value to the User-Agent string.
|
||||||
|
func (u *requestUserAgent) AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) {
|
||||||
|
u.sdkAgent.AddKeyValue(keyType.string()+"/"+key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID the name of the middleware.
|
||||||
|
func (u *requestUserAgent) ID() string {
|
||||||
|
return "UserAgent"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleBuild adds or appends the constructed user agent to the request.
|
||||||
|
func (u *requestUserAgent) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (
|
||||||
|
out middleware.BuildOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
switch req := in.Request.(type) {
|
||||||
|
case *smithyhttp.Request:
|
||||||
|
u.addHTTPUserAgent(req)
|
||||||
|
u.addHTTPSDKAgent(req)
|
||||||
|
default:
|
||||||
|
return out, metadata, fmt.Errorf("unknown transport type %T", in)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next.HandleBuild(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *requestUserAgent) addHTTPUserAgent(request *smithyhttp.Request) {
|
||||||
|
const userAgent = "User-Agent"
|
||||||
|
updateHTTPHeader(request, userAgent, u.userAgent.Build())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *requestUserAgent) addHTTPSDKAgent(request *smithyhttp.Request) {
|
||||||
|
const sdkAgent = "X-Amz-User-Agent"
|
||||||
|
updateHTTPHeader(request, sdkAgent, u.sdkAgent.Build())
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateHTTPHeader(request *smithyhttp.Request, header string, value string) {
|
||||||
|
var current string
|
||||||
|
if v := request.Header[header]; len(v) > 0 {
|
||||||
|
current = v[0]
|
||||||
|
}
|
||||||
|
if len(current) > 0 {
|
||||||
|
current = value + " " + current
|
||||||
|
} else {
|
||||||
|
current = value
|
||||||
|
}
|
||||||
|
request.Header[header] = append(request.Header[header][:0], current)
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Array represents the encoding of Query lists and sets. A Query array is a
|
||||||
|
// representation of a list of values of a fixed type. A serialized array might
|
||||||
|
// look like the following:
|
||||||
|
//
|
||||||
|
// ListName.member.1=foo
|
||||||
|
// &ListName.member.2=bar
|
||||||
|
// &Listname.member.3=baz
|
||||||
|
type Array struct {
|
||||||
|
// The query values to add the array to.
|
||||||
|
values url.Values
|
||||||
|
// The array's prefix, which includes the names of all parent structures
|
||||||
|
// and ends with the name of the list. For example, the prefix might be
|
||||||
|
// "ParentStructure.ListName". This prefix will be used to form the full
|
||||||
|
// keys for each element in the list. For example, an entry might have the
|
||||||
|
// key "ParentStructure.ListName.member.MemberName.1".
|
||||||
|
//
|
||||||
|
// While this is currently represented as a string that gets added to, it
|
||||||
|
// could also be represented as a stack that only gets condensed into a
|
||||||
|
// string when a finalized key is created. This could potentially reduce
|
||||||
|
// allocations.
|
||||||
|
prefix string
|
||||||
|
// Whether the list is flat or not. A list that is not flat will produce the
|
||||||
|
// following entry to the url.Values for a given entry:
|
||||||
|
// ListName.MemberName.1=value
|
||||||
|
// A list that is flat will produce the following:
|
||||||
|
// ListName.1=value
|
||||||
|
flat bool
|
||||||
|
// The location name of the member. In most cases this should be "member".
|
||||||
|
memberName string
|
||||||
|
// Elements are stored in values, so we keep track of the list size here.
|
||||||
|
size int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArray(values url.Values, prefix string, flat bool, memberName string) *Array {
|
||||||
|
return &Array{
|
||||||
|
values: values,
|
||||||
|
prefix: prefix,
|
||||||
|
flat: flat,
|
||||||
|
memberName: memberName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value adds a new element to the Query Array. Returns a Value type used to
|
||||||
|
// encode the array element.
|
||||||
|
func (a *Array) Value() Value {
|
||||||
|
// Query lists start a 1, so adjust the size first
|
||||||
|
a.size++
|
||||||
|
prefix := a.prefix
|
||||||
|
if !a.flat {
|
||||||
|
prefix = fmt.Sprintf("%s.%s", prefix, a.memberName)
|
||||||
|
}
|
||||||
|
// Lists can't have flat members
|
||||||
|
return newValue(a.values, fmt.Sprintf("%s.%d", prefix, a.size), false)
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encoder is a Query encoder that supports construction of Query body
|
||||||
|
// values using methods.
|
||||||
|
type Encoder struct {
|
||||||
|
// The query values that will be built up to manage encoding.
|
||||||
|
values url.Values
|
||||||
|
// The writer that the encoded body will be written to.
|
||||||
|
writer io.Writer
|
||||||
|
Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder returns a new Query body encoder
|
||||||
|
func NewEncoder(writer io.Writer) *Encoder {
|
||||||
|
values := url.Values{}
|
||||||
|
return &Encoder{
|
||||||
|
values: values,
|
||||||
|
writer: writer,
|
||||||
|
Value: newBaseValue(values),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode returns the []byte slice representing the current
|
||||||
|
// state of the Query encoder.
|
||||||
|
func (e Encoder) Encode() error {
|
||||||
|
ws, ok := e.writer.(interface{ WriteString(string) (int, error) })
|
||||||
|
if !ok {
|
||||||
|
// Fall back to less optimal byte slice casting if WriteString isn't available.
|
||||||
|
ws = &wrapWriteString{writer: e.writer}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the keys and sort them to have a stable output
|
||||||
|
keys := make([]string, 0, len(e.values))
|
||||||
|
for k := range e.values {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
isFirstEntry := true
|
||||||
|
for _, key := range keys {
|
||||||
|
queryValues := e.values[key]
|
||||||
|
escapedKey := url.QueryEscape(key)
|
||||||
|
for _, value := range queryValues {
|
||||||
|
if !isFirstEntry {
|
||||||
|
if _, err := ws.WriteString(`&`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isFirstEntry = false
|
||||||
|
}
|
||||||
|
if _, err := ws.WriteString(escapedKey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := ws.WriteString(`=`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := ws.WriteString(url.QueryEscape(value)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapWriteString wraps an io.Writer to provide a WriteString method
|
||||||
|
// where one is not available.
|
||||||
|
type wrapWriteString struct {
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString writes a string to the wrapped writer by casting it to
|
||||||
|
// a byte array first.
|
||||||
|
func (w wrapWriteString) WriteString(v string) (int, error) {
|
||||||
|
return w.writer.Write([]byte(v))
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map represents the encoding of Query maps. A Query map is a representation
|
||||||
|
// of a mapping of arbitrary string keys to arbitrary values of a fixed type.
|
||||||
|
// A Map differs from an Object in that the set of keys is not fixed, in that
|
||||||
|
// the values must all be of the same type, and that map entries are ordered.
|
||||||
|
// A serialized map might look like the following:
|
||||||
|
//
|
||||||
|
// MapName.entry.1.key=Foo
|
||||||
|
// &MapName.entry.1.value=spam
|
||||||
|
// &MapName.entry.2.key=Bar
|
||||||
|
// &MapName.entry.2.value=eggs
|
||||||
|
type Map struct {
|
||||||
|
// The query values to add the map to.
|
||||||
|
values url.Values
|
||||||
|
// The map's prefix, which includes the names of all parent structures
|
||||||
|
// and ends with the name of the object. For example, the prefix might be
|
||||||
|
// "ParentStructure.MapName". This prefix will be used to form the full
|
||||||
|
// keys for each key-value pair of the map. For example, a value might have
|
||||||
|
// the key "ParentStructure.MapName.1.value".
|
||||||
|
//
|
||||||
|
// While this is currently represented as a string that gets added to, it
|
||||||
|
// could also be represented as a stack that only gets condensed into a
|
||||||
|
// string when a finalized key is created. This could potentially reduce
|
||||||
|
// allocations.
|
||||||
|
prefix string
|
||||||
|
// Whether the map is flat or not. A map that is not flat will produce the
|
||||||
|
// following entries to the url.Values for a given key-value pair:
|
||||||
|
// MapName.entry.1.KeyLocationName=mykey
|
||||||
|
// MapName.entry.1.ValueLocationName=myvalue
|
||||||
|
// A map that is flat will produce the following:
|
||||||
|
// MapName.1.KeyLocationName=mykey
|
||||||
|
// MapName.1.ValueLocationName=myvalue
|
||||||
|
flat bool
|
||||||
|
// The location name of the key. In most cases this should be "key".
|
||||||
|
keyLocationName string
|
||||||
|
// The location name of the value. In most cases this should be "value".
|
||||||
|
valueLocationName string
|
||||||
|
// Elements are stored in values, so we keep track of the list size here.
|
||||||
|
size int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMap(values url.Values, prefix string, flat bool, keyLocationName string, valueLocationName string) *Map {
|
||||||
|
return &Map{
|
||||||
|
values: values,
|
||||||
|
prefix: prefix,
|
||||||
|
flat: flat,
|
||||||
|
keyLocationName: keyLocationName,
|
||||||
|
valueLocationName: valueLocationName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key adds the given named key to the Query map.
|
||||||
|
// Returns a Value encoder that should be used to encode a Query value type.
|
||||||
|
func (m *Map) Key(name string) Value {
|
||||||
|
// Query lists start a 1, so adjust the size first
|
||||||
|
m.size++
|
||||||
|
var key string
|
||||||
|
var value string
|
||||||
|
if m.flat {
|
||||||
|
key = fmt.Sprintf("%s.%d.%s", m.prefix, m.size, m.keyLocationName)
|
||||||
|
value = fmt.Sprintf("%s.%d.%s", m.prefix, m.size, m.valueLocationName)
|
||||||
|
} else {
|
||||||
|
key = fmt.Sprintf("%s.entry.%d.%s", m.prefix, m.size, m.keyLocationName)
|
||||||
|
value = fmt.Sprintf("%s.entry.%d.%s", m.prefix, m.size, m.valueLocationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The key can only be a string, so we just go ahead and set it here
|
||||||
|
newValue(m.values, key, false).String(name)
|
||||||
|
|
||||||
|
// Maps can't have flat members
|
||||||
|
return newValue(m.values, value, false)
|
||||||
|
}
|
62
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/middleware.go
generated
vendored
Normal file
62
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/middleware.go
generated
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddAsGetRequestMiddleware adds a middleware to the Serialize stack after the
|
||||||
|
// operation serializer that will convert the query request body to a GET
|
||||||
|
// operation with the query message in the HTTP request querystring.
|
||||||
|
func AddAsGetRequestMiddleware(stack *middleware.Stack) error {
|
||||||
|
return stack.Serialize.Insert(&asGetRequest{}, "OperationSerializer", middleware.After)
|
||||||
|
}
|
||||||
|
|
||||||
|
type asGetRequest struct{}
|
||||||
|
|
||||||
|
func (*asGetRequest) ID() string { return "Query:AsGetRequest" }
|
||||||
|
|
||||||
|
func (m *asGetRequest) HandleSerialize(
|
||||||
|
ctx context.Context, input middleware.SerializeInput, next middleware.SerializeHandler,
|
||||||
|
) (
|
||||||
|
out middleware.SerializeOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
req, ok := input.Request.(*smithyhttp.Request)
|
||||||
|
if !ok {
|
||||||
|
return out, metadata, fmt.Errorf("expect smithy HTTP Request, got %T", input.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Method = "GET"
|
||||||
|
|
||||||
|
// If the stream is not set, nothing else to do.
|
||||||
|
stream := req.GetStream()
|
||||||
|
if stream == nil {
|
||||||
|
return next.HandleSerialize(ctx, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the stream since there will not be any body.
|
||||||
|
req.Header.Del("Content-Type")
|
||||||
|
req, err = req.SetStream(nil)
|
||||||
|
if err != nil {
|
||||||
|
return out, metadata, fmt.Errorf("unable update request body %w", err)
|
||||||
|
}
|
||||||
|
input.Request = req
|
||||||
|
|
||||||
|
// Update request query with the body's query string value.
|
||||||
|
delim := ""
|
||||||
|
if len(req.URL.RawQuery) != 0 {
|
||||||
|
delim = "&"
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(stream)
|
||||||
|
if err != nil {
|
||||||
|
return out, metadata, fmt.Errorf("unable to get request body %w", err)
|
||||||
|
}
|
||||||
|
req.URL.RawQuery += delim + string(b)
|
||||||
|
|
||||||
|
return next.HandleSerialize(ctx, input)
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Object represents the encoding of Query structures and unions. A Query
|
||||||
|
// object is a representation of a mapping of string keys to arbitrary
|
||||||
|
// values where there is a fixed set of keys whose values each have their
|
||||||
|
// own known type. A serialized object might look like the following:
|
||||||
|
//
|
||||||
|
// ObjectName.Foo=value
|
||||||
|
// &ObjectName.Bar=5
|
||||||
|
type Object struct {
|
||||||
|
// The query values to add the object to.
|
||||||
|
values url.Values
|
||||||
|
// The object's prefix, which includes the names of all parent structures
|
||||||
|
// and ends with the name of the object. For example, the prefix might be
|
||||||
|
// "ParentStructure.ObjectName". This prefix will be used to form the full
|
||||||
|
// keys for each member of the object. For example, a member might have the
|
||||||
|
// key "ParentStructure.ObjectName.MemberName".
|
||||||
|
//
|
||||||
|
// While this is currently represented as a string that gets added to, it
|
||||||
|
// could also be represented as a stack that only gets condensed into a
|
||||||
|
// string when a finalized key is created. This could potentially reduce
|
||||||
|
// allocations.
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newObject(values url.Values, prefix string) *Object {
|
||||||
|
return &Object{
|
||||||
|
values: values,
|
||||||
|
prefix: prefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key adds the given named key to the Query object.
|
||||||
|
// Returns a Value encoder that should be used to encode a Query value type.
|
||||||
|
func (o *Object) Key(name string) Value {
|
||||||
|
return o.key(name, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlatKey adds the given named key to the Query object.
|
||||||
|
// Returns a Value encoder that should be used to encode a Query value type. The
|
||||||
|
// value will be flattened if it is a map or array.
|
||||||
|
func (o *Object) FlatKey(name string) Value {
|
||||||
|
return o.key(name, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Object) key(name string, flatValue bool) Value {
|
||||||
|
if o.prefix != "" {
|
||||||
|
return newValue(o.values, fmt.Sprintf("%s.%s", o.prefix, name), flatValue)
|
||||||
|
}
|
||||||
|
return newValue(o.values, name, flatValue)
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/aws/smithy-go/encoding/httpbinding"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Value represents a Query Value type.
|
||||||
|
type Value struct {
|
||||||
|
// The query values to add the value to.
|
||||||
|
values url.Values
|
||||||
|
// The value's key, which will form the prefix for complex types.
|
||||||
|
key string
|
||||||
|
// Whether the value should be flattened or not if it's a flattenable type.
|
||||||
|
flat bool
|
||||||
|
queryValue httpbinding.QueryValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func newValue(values url.Values, key string, flat bool) Value {
|
||||||
|
return Value{
|
||||||
|
values: values,
|
||||||
|
key: key,
|
||||||
|
flat: flat,
|
||||||
|
queryValue: httpbinding.NewQueryValue(values, key, false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBaseValue(values url.Values) Value {
|
||||||
|
return Value{
|
||||||
|
values: values,
|
||||||
|
queryValue: httpbinding.NewQueryValue(nil, "", false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array returns a new Array encoder.
|
||||||
|
func (qv Value) Array(locationName string) *Array {
|
||||||
|
return newArray(qv.values, qv.key, qv.flat, locationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object returns a new Object encoder.
|
||||||
|
func (qv Value) Object() *Object {
|
||||||
|
return newObject(qv.values, qv.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map returns a new Map encoder.
|
||||||
|
func (qv Value) Map(keyLocationName string, valueLocationName string) *Map {
|
||||||
|
return newMap(qv.values, qv.key, qv.flat, keyLocationName, valueLocationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64EncodeBytes encodes v as a base64 query string value.
|
||||||
|
// This is intended to enable compatibility with the JSON encoder.
|
||||||
|
func (qv Value) Base64EncodeBytes(v []byte) {
|
||||||
|
qv.queryValue.Blob(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean encodes v as a query string value
|
||||||
|
func (qv Value) Boolean(v bool) {
|
||||||
|
qv.queryValue.Boolean(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes v as a query string value
|
||||||
|
func (qv Value) String(v string) {
|
||||||
|
qv.queryValue.String(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Byte encodes v as a query string value
|
||||||
|
func (qv Value) Byte(v int8) {
|
||||||
|
qv.queryValue.Byte(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short encodes v as a query string value
|
||||||
|
func (qv Value) Short(v int16) {
|
||||||
|
qv.queryValue.Short(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integer encodes v as a query string value
|
||||||
|
func (qv Value) Integer(v int32) {
|
||||||
|
qv.queryValue.Integer(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long encodes v as a query string value
|
||||||
|
func (qv Value) Long(v int64) {
|
||||||
|
qv.queryValue.Long(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float encodes v as a query string value
|
||||||
|
func (qv Value) Float(v float32) {
|
||||||
|
qv.queryValue.Float(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double encodes v as a query string value
|
||||||
|
func (qv Value) Double(v float64) {
|
||||||
|
qv.queryValue.Double(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigInteger encodes v as a query string value
|
||||||
|
func (qv Value) BigInteger(v *big.Int) {
|
||||||
|
qv.queryValue.BigInteger(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigDecimal encodes v as a query string value
|
||||||
|
func (qv Value) BigDecimal(v *big.Float) {
|
||||||
|
qv.queryValue.BigDecimal(v)
|
||||||
|
}
|
85
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/restjson/decoder_util.go
generated
vendored
Normal file
85
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/restjson/decoder_util.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package restjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/smithy-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetErrorInfo util looks for code, __type, and message members in the
|
||||||
|
// json body. These members are optionally available, and the function
|
||||||
|
// returns the value of member if it is available. This function is useful to
|
||||||
|
// identify the error code, msg in a REST JSON error response.
|
||||||
|
func GetErrorInfo(decoder *json.Decoder) (errorType string, message string, err error) {
|
||||||
|
var errInfo struct {
|
||||||
|
Code string
|
||||||
|
Type string `json:"__type"`
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
err = decoder.Decode(&errInfo)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return errorType, message, nil
|
||||||
|
}
|
||||||
|
return errorType, message, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign error type
|
||||||
|
if len(errInfo.Code) != 0 {
|
||||||
|
errorType = errInfo.Code
|
||||||
|
} else if len(errInfo.Type) != 0 {
|
||||||
|
errorType = errInfo.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign error message
|
||||||
|
if len(errInfo.Message) != 0 {
|
||||||
|
message = errInfo.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitize error
|
||||||
|
if len(errorType) != 0 {
|
||||||
|
errorType = SanitizeErrorCode(errorType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorType, message, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SanitizeErrorCode sanitizes the errorCode string .
|
||||||
|
// The rule for sanitizing is if a `:` character is present, then take only the
|
||||||
|
// contents before the first : character in the value.
|
||||||
|
// If a # character is present, then take only the contents after the
|
||||||
|
// first # character in the value.
|
||||||
|
func SanitizeErrorCode(errorCode string) string {
|
||||||
|
if strings.ContainsAny(errorCode, ":") {
|
||||||
|
errorCode = strings.SplitN(errorCode, ":", 2)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ContainsAny(errorCode, "#") {
|
||||||
|
errorCode = strings.SplitN(errorCode, "#", 2)[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSmithyGenericAPIError returns smithy generic api error and an error interface.
|
||||||
|
// Takes in json decoder, and error Code string as args. The function retrieves error message
|
||||||
|
// and error code from the decoder body. If errorCode of length greater than 0 is passed in as
|
||||||
|
// an argument, it is used instead.
|
||||||
|
func GetSmithyGenericAPIError(decoder *json.Decoder, errorCode string) (*smithy.GenericAPIError, error) {
|
||||||
|
errorType, message, err := GetErrorInfo(decoder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errorCode) == 0 {
|
||||||
|
errorCode = errorType
|
||||||
|
}
|
||||||
|
|
||||||
|
return &smithy.GenericAPIError{
|
||||||
|
Code: errorCode,
|
||||||
|
Message: message,
|
||||||
|
}, nil
|
||||||
|
}
|
56
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/xml/error_utils.go
generated
vendored
Normal file
56
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/xml/error_utils.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package xml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorComponents represents the error response fields
|
||||||
|
// that will be deserialized from an xml error response body
|
||||||
|
type ErrorComponents struct {
|
||||||
|
Code string
|
||||||
|
Message string
|
||||||
|
RequestID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetErrorResponseComponents returns the error fields from an xml error response body
|
||||||
|
func GetErrorResponseComponents(r io.Reader, noErrorWrapping bool) (ErrorComponents, error) {
|
||||||
|
if noErrorWrapping {
|
||||||
|
var errResponse noWrappedErrorResponse
|
||||||
|
if err := xml.NewDecoder(r).Decode(&errResponse); err != nil && err != io.EOF {
|
||||||
|
return ErrorComponents{}, fmt.Errorf("error while deserializing xml error response: %w", err)
|
||||||
|
}
|
||||||
|
return ErrorComponents{
|
||||||
|
Code: errResponse.Code,
|
||||||
|
Message: errResponse.Message,
|
||||||
|
RequestID: errResponse.RequestID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errResponse wrappedErrorResponse
|
||||||
|
if err := xml.NewDecoder(r).Decode(&errResponse); err != nil && err != io.EOF {
|
||||||
|
return ErrorComponents{}, fmt.Errorf("error while deserializing xml error response: %w", err)
|
||||||
|
}
|
||||||
|
return ErrorComponents{
|
||||||
|
Code: errResponse.Code,
|
||||||
|
Message: errResponse.Message,
|
||||||
|
RequestID: errResponse.RequestID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// noWrappedErrorResponse represents the error response body with
|
||||||
|
// no internal <Error></Error wrapping
|
||||||
|
type noWrappedErrorResponse struct {
|
||||||
|
Code string `xml:"Code"`
|
||||||
|
Message string `xml:"Message"`
|
||||||
|
RequestID string `xml:"RequestId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrappedErrorResponse represents the error response body
|
||||||
|
// wrapped within <Error>...</Error>
|
||||||
|
type wrappedErrorResponse struct {
|
||||||
|
Code string `xml:"Error>Code"`
|
||||||
|
Message string `xml:"Error>Message"`
|
||||||
|
RequestID string `xml:"RequestId"`
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package ratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TokenBucket provides a concurrency safe utility for adding and removing
|
||||||
|
// tokens from the available token bucket.
|
||||||
|
type TokenBucket struct {
|
||||||
|
capacity uint
|
||||||
|
maxCapacity uint
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTokenBucket returns an initialized TokenBucket with the capacity
|
||||||
|
// specified.
|
||||||
|
func NewTokenBucket(i uint) *TokenBucket {
|
||||||
|
return &TokenBucket{
|
||||||
|
capacity: i,
|
||||||
|
maxCapacity: i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve attempts to reduce the available tokens by the amount requested. If
|
||||||
|
// there are tokens available true will be returned along with the number of
|
||||||
|
// available tokens remaining. If amount requested is larger than the available
|
||||||
|
// capacity, false will be returned along with the available capacity. If the
|
||||||
|
// amount is less than the available capacity
|
||||||
|
func (t *TokenBucket) Retrieve(amount uint) (available uint, retrieved bool) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
if amount > t.capacity {
|
||||||
|
return t.capacity, false
|
||||||
|
}
|
||||||
|
|
||||||
|
t.capacity -= amount
|
||||||
|
return t.capacity, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refund returns the amount of tokens back to the available token bucket, up
|
||||||
|
// to the initial capacity.
|
||||||
|
func (t *TokenBucket) Refund(amount uint) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
t.capacity += amount
|
||||||
|
if t.capacity > t.maxCapacity {
|
||||||
|
t.capacity = t.maxCapacity
|
||||||
|
}
|
||||||
|
}
|
82
vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/token_rate_limit.go
generated
vendored
Normal file
82
vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/token_rate_limit.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package ratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rateToken struct {
|
||||||
|
tokenCost uint
|
||||||
|
bucket *TokenBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t rateToken) release() error {
|
||||||
|
t.bucket.Refund(t.tokenCost)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenRateLimit provides a Token Bucket RateLimiter implementation
|
||||||
|
// that limits the overall number of retry attempts that can be made across
|
||||||
|
// operation invocations.
|
||||||
|
type TokenRateLimit struct {
|
||||||
|
bucket *TokenBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTokenRateLimit returns an TokenRateLimit with default values.
|
||||||
|
// Functional options can configure the retry rate limiter.
|
||||||
|
func NewTokenRateLimit(tokens uint) *TokenRateLimit {
|
||||||
|
return &TokenRateLimit{
|
||||||
|
bucket: NewTokenBucket(tokens),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTimeoutError(error) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type canceledError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c canceledError) CanceledError() bool { return true }
|
||||||
|
func (c canceledError) Unwrap() error { return c.Err }
|
||||||
|
func (c canceledError) Error() string {
|
||||||
|
return fmt.Sprintf("canceled, %v", c.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken may cause a available pool of retry quota to be
|
||||||
|
// decremented. Will return an error if the decremented value can not be
|
||||||
|
// reduced from the retry quota.
|
||||||
|
func (l *TokenRateLimit) GetToken(ctx context.Context, cost uint) (func() error, error) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, canceledError{Err: ctx.Err()}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if avail, ok := l.bucket.Retrieve(cost); !ok {
|
||||||
|
return nil, QuotaExceededError{Available: avail, Requested: cost}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rateToken{
|
||||||
|
tokenCost: cost,
|
||||||
|
bucket: l.bucket,
|
||||||
|
}.release, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTokens increments the token bucket by a fixed amount.
|
||||||
|
func (l *TokenRateLimit) AddTokens(v uint) error {
|
||||||
|
l.bucket.Refund(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuotaExceededError provides the SDK error when the retries for a given
|
||||||
|
// token bucket have been exhausted.
|
||||||
|
type QuotaExceededError struct {
|
||||||
|
Available uint
|
||||||
|
Requested uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e QuotaExceededError) Error() string {
|
||||||
|
return fmt.Sprintf("retry quota exceeded, %d available, %d requested",
|
||||||
|
e.Available, e.Requested)
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO remove replace with smithy.CanceledError
|
||||||
|
|
||||||
|
// RequestCanceledError is the error that will be returned by an API request
|
||||||
|
// that was canceled. Requests given a Context may return this error when
|
||||||
|
// canceled.
|
||||||
|
type RequestCanceledError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanceledError returns true to satisfy interfaces checking for canceled errors.
|
||||||
|
func (*RequestCanceledError) CanceledError() bool { return true }
|
||||||
|
|
||||||
|
// Unwrap returns the underlying error, if there was one.
|
||||||
|
func (e *RequestCanceledError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
func (e *RequestCanceledError) Error() string {
|
||||||
|
return fmt.Sprintf("request canceled, %v", e.Err)
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Package retry provides interfaces and implementations for SDK request retry behavior.
|
||||||
|
//
|
||||||
|
// Retryer Interface and Implementations
|
||||||
|
//
|
||||||
|
// This packages defines Retryer interface that is used to either implement custom retry behavior
|
||||||
|
// or to extend the existing retry implementations provided by the SDK. This packages provides a single
|
||||||
|
// retry implementations: Standard.
|
||||||
|
//
|
||||||
|
// Standard
|
||||||
|
//
|
||||||
|
// Standard is the default retryer implementation used by service clients. The standard retryer is a rate limited
|
||||||
|
// retryer that has a configurable max attempts to limit the number of retry attempts when a retryable error occurs.
|
||||||
|
// In addition, the retryer uses a configurable token bucket to rate limit the retry attempts across the client,
|
||||||
|
// and uses an additional delay policy to limit the time between a requests subsequent attempts.
|
||||||
|
//
|
||||||
|
// By default the standard retryer uses the DefaultRetryables slice of IsErrorRetryable types to determine whether
|
||||||
|
// a given error is retryable. By default this list of retryables includes the following:
|
||||||
|
// - Retrying errors that implement the RetryableError method, and return true.
|
||||||
|
// - Connection Errors
|
||||||
|
// - Errors that implement a ConnectionError, Temporary, or Timeout method that return true.
|
||||||
|
// - Connection Reset Errors.
|
||||||
|
// - net.OpErr types that are dialing errors or are temporary.
|
||||||
|
// - HTTP Status Codes: 500, 502, 503, and 504.
|
||||||
|
// - API Error Codes
|
||||||
|
// - RequestTimeout, RequestTimeoutException
|
||||||
|
// - Throttling, ThrottlingException, ThrottledException, RequestThrottledException, TooManyRequestsException,
|
||||||
|
// RequestThrottled, SlowDown, EC2ThrottledException
|
||||||
|
// - ProvisionedThroughputExceededException, RequestLimitExceeded, BandwidthLimitExceeded, LimitExceededException
|
||||||
|
// - TransactionInProgressException, PriorRequestNotComplete
|
||||||
|
//
|
||||||
|
// The standard retryer will not retry a request in the event if the context associated with the request
|
||||||
|
// has been cancelled. Applications must handle this case explicitly if they wish to retry with a different context
|
||||||
|
// value.
|
||||||
|
//
|
||||||
|
// You can configure the standard retryer implementation to fit your applications by constructing a standard retryer
|
||||||
|
// using the NewStandard function, and providing one more functional arguments that mutate the StandardOptions
|
||||||
|
// structure. StandardOptions provides the ability to modify the token bucket rate limiter, retryable error conditions,
|
||||||
|
// and the retry delay policy.
|
||||||
|
//
|
||||||
|
// For example to modify the default retry attempts for the standard retryer:
|
||||||
|
//
|
||||||
|
// // configure the custom retryer
|
||||||
|
// customRetry := retry.NewStandard(func(o *retry.StandardOptions) {
|
||||||
|
// o.MaxAttempts = 5
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// // create a service client with the retryer
|
||||||
|
// s3.NewFromConfig(cfg, func(o *s3.Options) {
|
||||||
|
// o.Retryer = customRetry
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// Utilities
|
||||||
|
//
|
||||||
|
// A number of package functions have been provided to easily wrap retryer implementations in an implementation agnostic
|
||||||
|
// way. These are:
|
||||||
|
//
|
||||||
|
// AddWithErrorCodes - Provides the ability to add additional API error codes that should be considered retryable
|
||||||
|
// in addition to those considered retryable by the provided retryer.
|
||||||
|
//
|
||||||
|
// AddWithMaxAttempts - Provides the ability to set the max number of attempts for retrying a request by wrapping
|
||||||
|
// a retryer implementation.
|
||||||
|
//
|
||||||
|
// AddWithMaxBackoffDelay - Provides the ability to set the max back off delay that can occur before retrying a
|
||||||
|
// request by wrapping a retryer implementation.
|
||||||
|
//
|
||||||
|
// The following package functions have been provided to easily satisfy different retry interfaces to further customize
|
||||||
|
// a given retryer's behavior:
|
||||||
|
//
|
||||||
|
// BackoffDelayerFunc - Can be used to wrap a function to satisfy the BackoffDelayer interface. For example,
|
||||||
|
// you can use this method to easily create custom back off policies to be used with the
|
||||||
|
// standard retryer.
|
||||||
|
//
|
||||||
|
// IsErrorRetryableFunc - Can be used to wrap a function to satisfy the IsErrorRetryable interface. For example,
|
||||||
|
// this can be used to extend the standard retryer to add additional logic ot determine if a
|
||||||
|
// error should be retried.
|
||||||
|
//
|
||||||
|
// IsErrorTimeoutFunc - Can be used to wrap a function to satisfy IsErrorTimeout interface. For example,
|
||||||
|
// this can be used to extend the standard retryer to add additional logic to determine if an
|
||||||
|
// error should be considered a timeout.
|
||||||
|
package retry
|
|
@ -0,0 +1,20 @@
|
||||||
|
package retry
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// MaxAttemptsError provides the error when the maximum number of attempts have
|
||||||
|
// been exceeded.
|
||||||
|
type MaxAttemptsError struct {
|
||||||
|
Attempt int
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *MaxAttemptsError) Error() string {
|
||||||
|
return fmt.Sprintf("exceeded maximum number of attempts, %d, %v", e.Attempt, e.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the nested error causing the max attempts error. Provides the
|
||||||
|
// implementation for errors.Is and errors.As to unwrap nested errors.
|
||||||
|
func (e *MaxAttemptsError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package retry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/rand"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/timeconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExponentialJitterBackoff provides backoff delays with jitter based on the
|
||||||
|
// number of attempts.
|
||||||
|
type ExponentialJitterBackoff struct {
|
||||||
|
maxBackoff time.Duration
|
||||||
|
// precomputed number of attempts needed to reach max backoff.
|
||||||
|
maxBackoffAttempts float64
|
||||||
|
|
||||||
|
randFloat64 func() (float64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExponentialJitterBackoff returns an ExponentialJitterBackoff configured
|
||||||
|
// for the max backoff.
|
||||||
|
func NewExponentialJitterBackoff(maxBackoff time.Duration) *ExponentialJitterBackoff {
|
||||||
|
return &ExponentialJitterBackoff{
|
||||||
|
maxBackoff: maxBackoff,
|
||||||
|
maxBackoffAttempts: math.Log2(
|
||||||
|
float64(maxBackoff) / float64(time.Second)),
|
||||||
|
randFloat64: rand.CryptoRandFloat64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackoffDelay returns the duration to wait before the next attempt should be
|
||||||
|
// made. Returns an error if unable get a duration.
|
||||||
|
func (j *ExponentialJitterBackoff) BackoffDelay(attempt int, err error) (time.Duration, error) {
|
||||||
|
if attempt > int(j.maxBackoffAttempts) {
|
||||||
|
return j.maxBackoff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := j.randFloat64()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// [0.0, 1.0) * 2 ^ attempts
|
||||||
|
ri := int64(1 << uint64(attempt))
|
||||||
|
delaySeconds := b * float64(ri)
|
||||||
|
|
||||||
|
return timeconv.FloatSecondsDur(delaySeconds), nil
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package retry
|
||||||
|
|
||||||
|
import (
|
||||||
|
awsmiddle "github.com/aws/aws-sdk-go-v2/aws/middleware"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// attemptResultsKey is a metadata accessor key to retrieve metadata
|
||||||
|
// for all request attempts.
|
||||||
|
type attemptResultsKey struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAttemptResults retrieves attempts results from middleware metadata.
|
||||||
|
func GetAttemptResults(metadata middleware.Metadata) (AttemptResults, bool) {
|
||||||
|
m, ok := metadata.Get(attemptResultsKey{}).(AttemptResults)
|
||||||
|
return m, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttemptResults represents struct containing metadata returned by all request attempts.
|
||||||
|
type AttemptResults struct {
|
||||||
|
|
||||||
|
// Results is a slice consisting attempt result from all request attempts.
|
||||||
|
// Results are stored in order request attempt is made.
|
||||||
|
Results []AttemptResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttemptResult represents attempt result returned by a single request attempt.
|
||||||
|
type AttemptResult struct {
|
||||||
|
|
||||||
|
// Err is the error if received for the request attempt.
|
||||||
|
Err error
|
||||||
|
|
||||||
|
// Retryable denotes if request may be retried. This states if an
|
||||||
|
// error is considered retryable.
|
||||||
|
Retryable bool
|
||||||
|
|
||||||
|
// Retried indicates if this request was retried.
|
||||||
|
Retried bool
|
||||||
|
|
||||||
|
// ResponseMetadata is any existing metadata passed via the response middlewares.
|
||||||
|
ResponseMetadata middleware.Metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// addAttemptResults adds attempt results to middleware metadata
|
||||||
|
func addAttemptResults(metadata *middleware.Metadata, v AttemptResults) {
|
||||||
|
metadata.Set(attemptResultsKey{}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawResponse returns raw response recorded for the attempt result
|
||||||
|
func (a AttemptResult) GetRawResponse() interface{} {
|
||||||
|
return awsmiddle.GetRawResponse(a.ResponseMetadata)
|
||||||
|
}
|
|
@ -0,0 +1,273 @@
|
||||||
|
package retry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
awsmiddle "github.com/aws/aws-sdk-go-v2/aws/middleware"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/sdk"
|
||||||
|
"github.com/aws/smithy-go/logging"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
smithymiddle "github.com/aws/smithy-go/middleware"
|
||||||
|
"github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestCloner is a function that can take an input request type and clone the request
|
||||||
|
// for use in a subsequent retry attempt
|
||||||
|
type RequestCloner func(interface{}) interface{}
|
||||||
|
|
||||||
|
type retryMetadata struct {
|
||||||
|
AttemptNum int
|
||||||
|
AttemptTime time.Time
|
||||||
|
MaxAttempts int
|
||||||
|
AttemptClockSkew time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt is a Smithy FinalizeMiddleware that handles retry attempts using the provided
|
||||||
|
// Retryer implementation
|
||||||
|
type Attempt struct {
|
||||||
|
// Enable the logging of retry attempts performed by the SDK.
|
||||||
|
// This will include logging retry attempts, unretryable errors, and when max attempts are reached.
|
||||||
|
LogAttempts bool
|
||||||
|
|
||||||
|
retryer aws.Retryer
|
||||||
|
requestCloner RequestCloner
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAttemptMiddleware returns a new Attempt retry middleware.
|
||||||
|
func NewAttemptMiddleware(retryer aws.Retryer, requestCloner RequestCloner, optFns ...func(*Attempt)) *Attempt {
|
||||||
|
m := &Attempt{retryer: retryer, requestCloner: requestCloner}
|
||||||
|
for _, fn := range optFns {
|
||||||
|
fn(m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the middleware identifier
|
||||||
|
func (r *Attempt) ID() string {
|
||||||
|
return "Retry"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Attempt) logf(logger logging.Logger, classification logging.Classification, format string, v ...interface{}) {
|
||||||
|
if !r.LogAttempts {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Logf(classification, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFinalize utilizes the provider Retryer implementation to attempt retries over the next handler
|
||||||
|
func (r Attempt) HandleFinalize(ctx context.Context, in smithymiddle.FinalizeInput, next smithymiddle.FinalizeHandler) (
|
||||||
|
out smithymiddle.FinalizeOutput, metadata smithymiddle.Metadata, err error,
|
||||||
|
) {
|
||||||
|
var attemptNum int
|
||||||
|
var attemptClockSkew time.Duration
|
||||||
|
var attemptResults AttemptResults
|
||||||
|
|
||||||
|
maxAttempts := r.retryer.MaxAttempts()
|
||||||
|
|
||||||
|
for {
|
||||||
|
attemptNum++
|
||||||
|
attemptInput := in
|
||||||
|
attemptInput.Request = r.requestCloner(attemptInput.Request)
|
||||||
|
|
||||||
|
attemptCtx := setRetryMetadata(ctx, retryMetadata{
|
||||||
|
AttemptNum: attemptNum,
|
||||||
|
AttemptTime: sdk.NowTime().UTC(),
|
||||||
|
MaxAttempts: maxAttempts,
|
||||||
|
AttemptClockSkew: attemptClockSkew,
|
||||||
|
})
|
||||||
|
|
||||||
|
var attemptResult AttemptResult
|
||||||
|
|
||||||
|
out, attemptResult, err = r.handleAttempt(attemptCtx, attemptInput, next)
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
attemptClockSkew, ok = awsmiddle.GetAttemptSkew(attemptResult.ResponseMetadata)
|
||||||
|
if !ok {
|
||||||
|
attemptClockSkew = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldRetry := attemptResult.Retried
|
||||||
|
|
||||||
|
// add attempt metadata to list of all attempt metadata
|
||||||
|
attemptResults.Results = append(attemptResults.Results, attemptResult)
|
||||||
|
|
||||||
|
if !shouldRetry {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addAttemptResults(&metadata, attemptResults)
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleAttempt handles an individual request attempt.
|
||||||
|
func (r Attempt) handleAttempt(ctx context.Context, in smithymiddle.FinalizeInput, next smithymiddle.FinalizeHandler) (
|
||||||
|
out smithymiddle.FinalizeOutput, attemptResult AttemptResult, err error,
|
||||||
|
) {
|
||||||
|
defer func() {
|
||||||
|
attemptResult.Err = err
|
||||||
|
}()
|
||||||
|
|
||||||
|
relRetryToken := r.retryer.GetInitialToken()
|
||||||
|
logger := smithymiddle.GetLogger(ctx)
|
||||||
|
service, operation := awsmiddle.GetServiceID(ctx), awsmiddle.GetOperationName(ctx)
|
||||||
|
|
||||||
|
retryMetadata, _ := getRetryMetadata(ctx)
|
||||||
|
attemptNum := retryMetadata.AttemptNum
|
||||||
|
maxAttempts := retryMetadata.MaxAttempts
|
||||||
|
|
||||||
|
if attemptNum > 1 {
|
||||||
|
if rewindable, ok := in.Request.(interface{ RewindStream() error }); ok {
|
||||||
|
if rewindErr := rewindable.RewindStream(); rewindErr != nil {
|
||||||
|
err = fmt.Errorf("failed to rewind transport stream for retry, %w", rewindErr)
|
||||||
|
return out, attemptResult, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logf(logger, logging.Debug, "retrying request %s/%s, attempt %d", service, operation, attemptNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata smithymiddle.Metadata
|
||||||
|
out, metadata, err = next.HandleFinalize(ctx, in)
|
||||||
|
attemptResult.ResponseMetadata = metadata
|
||||||
|
|
||||||
|
if releaseError := relRetryToken(err); releaseError != nil && err != nil {
|
||||||
|
err = fmt.Errorf("failed to release token after request error, %w", err)
|
||||||
|
return out, attemptResult, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return out, attemptResult, err
|
||||||
|
}
|
||||||
|
|
||||||
|
retryable := r.retryer.IsErrorRetryable(err)
|
||||||
|
if !retryable {
|
||||||
|
r.logf(logger, logging.Debug, "request failed with unretryable error %v", err)
|
||||||
|
return out, attemptResult, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set retryable to true
|
||||||
|
attemptResult.Retryable = true
|
||||||
|
|
||||||
|
if maxAttempts > 0 && attemptNum >= maxAttempts {
|
||||||
|
r.logf(logger, logging.Debug, "max retry attempts exhausted, max %d", maxAttempts)
|
||||||
|
err = &MaxAttemptsError{
|
||||||
|
Attempt: attemptNum,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
return out, attemptResult, err
|
||||||
|
}
|
||||||
|
|
||||||
|
relRetryToken, reqErr := r.retryer.GetRetryToken(ctx, err)
|
||||||
|
if reqErr != nil {
|
||||||
|
return out, attemptResult, reqErr
|
||||||
|
}
|
||||||
|
|
||||||
|
retryDelay, reqErr := r.retryer.RetryDelay(attemptNum, err)
|
||||||
|
if reqErr != nil {
|
||||||
|
return out, attemptResult, reqErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqErr = sdk.SleepWithContext(ctx, retryDelay); reqErr != nil {
|
||||||
|
err = &aws.RequestCanceledError{Err: reqErr}
|
||||||
|
return out, attemptResult, err
|
||||||
|
}
|
||||||
|
|
||||||
|
attemptResult.Retried = true
|
||||||
|
|
||||||
|
return out, attemptResult, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetricsHeader attaches SDK request metric header for retries to the transport
|
||||||
|
type MetricsHeader struct{}
|
||||||
|
|
||||||
|
// ID returns the middleware identifier
|
||||||
|
func (r *MetricsHeader) ID() string {
|
||||||
|
return "RetryMetricsHeader"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFinalize attaches the sdk request metric header to the transport layer
|
||||||
|
func (r MetricsHeader) HandleFinalize(ctx context.Context, in smithymiddle.FinalizeInput, next smithymiddle.FinalizeHandler) (
|
||||||
|
out smithymiddle.FinalizeOutput, metadata smithymiddle.Metadata, err error,
|
||||||
|
) {
|
||||||
|
retryMetadata, _ := getRetryMetadata(ctx)
|
||||||
|
|
||||||
|
const retryMetricHeader = "Amz-Sdk-Request"
|
||||||
|
var parts []string
|
||||||
|
|
||||||
|
parts = append(parts, "attempt="+strconv.Itoa(retryMetadata.AttemptNum))
|
||||||
|
if retryMetadata.MaxAttempts != 0 {
|
||||||
|
parts = append(parts, "max="+strconv.Itoa(retryMetadata.MaxAttempts))
|
||||||
|
}
|
||||||
|
|
||||||
|
var ttl time.Time
|
||||||
|
if deadline, ok := ctx.Deadline(); ok {
|
||||||
|
ttl = deadline
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only append the TTL if it can be determined.
|
||||||
|
if !ttl.IsZero() && retryMetadata.AttemptClockSkew > 0 {
|
||||||
|
const unixTimeFormat = "20060102T150405Z"
|
||||||
|
ttl = ttl.Add(retryMetadata.AttemptClockSkew)
|
||||||
|
parts = append(parts, "ttl="+ttl.Format(unixTimeFormat))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch req := in.Request.(type) {
|
||||||
|
case *http.Request:
|
||||||
|
req.Header[retryMetricHeader] = append(req.Header[retryMetricHeader][:0], strings.Join(parts, "; "))
|
||||||
|
default:
|
||||||
|
return out, metadata, fmt.Errorf("unknown transport type %T", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next.HandleFinalize(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
type retryMetadataKey struct{}
|
||||||
|
|
||||||
|
// getRetryMetadata retrieves retryMetadata from the context and a bool
|
||||||
|
// indicating if it was set.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func getRetryMetadata(ctx context.Context) (metadata retryMetadata, ok bool) {
|
||||||
|
metadata, ok = middleware.GetStackValue(ctx, retryMetadataKey{}).(retryMetadata)
|
||||||
|
return metadata, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// setRetryMetadata sets the retryMetadata on the context.
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func setRetryMetadata(ctx context.Context, metadata retryMetadata) context.Context {
|
||||||
|
return middleware.WithStackValue(ctx, retryMetadataKey{}, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRetryMiddlewaresOptions is the set of options that can be passed to AddRetryMiddlewares for configuring retry
|
||||||
|
// associated middleware.
|
||||||
|
type AddRetryMiddlewaresOptions struct {
|
||||||
|
Retryer aws.Retryer
|
||||||
|
|
||||||
|
// Enable the logging of retry attempts performed by the SDK.
|
||||||
|
// This will include logging retry attempts, unretryable errors, and when max attempts are reached.
|
||||||
|
LogRetryAttempts bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRetryMiddlewares adds retry middleware to operation middleware stack
|
||||||
|
func AddRetryMiddlewares(stack *smithymiddle.Stack, options AddRetryMiddlewaresOptions) error {
|
||||||
|
attempt := NewAttemptMiddleware(options.Retryer, http.RequestCloner, func(middleware *Attempt) {
|
||||||
|
middleware.LogAttempts = options.LogRetryAttempts
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := stack.Finalize.Add(attempt, smithymiddle.After); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := stack.Finalize.Add(&MetricsHeader{}, smithymiddle.After); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package retry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddWithErrorCodes returns a Retryer with additional error codes considered
|
||||||
|
// for determining if the error should be retried.
|
||||||
|
func AddWithErrorCodes(r aws.Retryer, codes ...string) aws.Retryer {
|
||||||
|
retryable := &RetryableErrorCode{
|
||||||
|
Codes: map[string]struct{}{},
|
||||||
|
}
|
||||||
|
for _, c := range codes {
|
||||||
|
retryable.Codes[c] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &withIsErrorRetryable{
|
||||||
|
Retryer: r,
|
||||||
|
Retryable: retryable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withIsErrorRetryable struct {
|
||||||
|
aws.Retryer
|
||||||
|
Retryable IsErrorRetryable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *withIsErrorRetryable) IsErrorRetryable(err error) bool {
|
||||||
|
if v := r.Retryable.IsErrorRetryable(err); v != aws.UnknownTernary {
|
||||||
|
return v.Bool()
|
||||||
|
}
|
||||||
|
return r.Retryer.IsErrorRetryable(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddWithMaxAttempts returns a Retryer with MaxAttempts set to the value
|
||||||
|
// specified.
|
||||||
|
func AddWithMaxAttempts(r aws.Retryer, max int) aws.Retryer {
|
||||||
|
return &withMaxAttempts{
|
||||||
|
Retryer: r,
|
||||||
|
Max: max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withMaxAttempts struct {
|
||||||
|
aws.Retryer
|
||||||
|
Max int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withMaxAttempts) MaxAttempts() int {
|
||||||
|
return w.Max
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddWithMaxBackoffDelay returns a retryer wrapping the passed in retryer
|
||||||
|
// overriding the RetryDelay behavior for a alternate minimum initial backoff
|
||||||
|
// delay.
|
||||||
|
func AddWithMaxBackoffDelay(r aws.Retryer, delay time.Duration) aws.Retryer {
|
||||||
|
return &withMaxBackoffDelay{
|
||||||
|
Retryer: r,
|
||||||
|
backoff: NewExponentialJitterBackoff(delay),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type withMaxBackoffDelay struct {
|
||||||
|
aws.Retryer
|
||||||
|
backoff *ExponentialJitterBackoff
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *withMaxBackoffDelay) RetryDelay(attempt int, err error) (time.Duration, error) {
|
||||||
|
return r.backoff.BackoffDelay(attempt, err)
|
||||||
|
}
|
|
@ -0,0 +1,186 @@
|
||||||
|
package retry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsErrorRetryable provides the interface of an implementation to determine if
|
||||||
|
// a error as the result of an operation is retryable.
|
||||||
|
type IsErrorRetryable interface {
|
||||||
|
IsErrorRetryable(error) aws.Ternary
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrorRetryables is a collection of checks to determine of the error is
|
||||||
|
// retryable. Iterates through the checks and returns the state of retryable
|
||||||
|
// if any check returns something other than unknown.
|
||||||
|
type IsErrorRetryables []IsErrorRetryable
|
||||||
|
|
||||||
|
// IsErrorRetryable returns if the error is retryable if any of the checks in
|
||||||
|
// the list return a value other than unknown.
|
||||||
|
func (r IsErrorRetryables) IsErrorRetryable(err error) aws.Ternary {
|
||||||
|
for _, re := range r {
|
||||||
|
if v := re.IsErrorRetryable(err); v != aws.UnknownTernary {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrorRetryableFunc wraps a function with the IsErrorRetryable interface.
|
||||||
|
type IsErrorRetryableFunc func(error) aws.Ternary
|
||||||
|
|
||||||
|
// IsErrorRetryable returns if the error is retryable.
|
||||||
|
func (fn IsErrorRetryableFunc) IsErrorRetryable(err error) aws.Ternary {
|
||||||
|
return fn(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryableError is an IsErrorRetryable implementation which uses the
|
||||||
|
// optional interface Retryable on the error value to determine if the error is
|
||||||
|
// retryable.
|
||||||
|
type RetryableError struct{}
|
||||||
|
|
||||||
|
// IsErrorRetryable returns if the error is retryable if it satisfies the
|
||||||
|
// Retryable interface, and returns if the attempt should be retried.
|
||||||
|
func (RetryableError) IsErrorRetryable(err error) aws.Ternary {
|
||||||
|
var v interface{ RetryableError() bool }
|
||||||
|
|
||||||
|
if !errors.As(err, &v) {
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
return aws.BoolTernary(v.RetryableError())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoRetryCanceledError detects if the error was an request canceled error and
|
||||||
|
// returns if so.
|
||||||
|
type NoRetryCanceledError struct{}
|
||||||
|
|
||||||
|
// IsErrorRetryable returns the error is not retryable if the request was
|
||||||
|
// canceled.
|
||||||
|
func (NoRetryCanceledError) IsErrorRetryable(err error) aws.Ternary {
|
||||||
|
var v interface{ CanceledError() bool }
|
||||||
|
|
||||||
|
if !errors.As(err, &v) {
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.CanceledError() {
|
||||||
|
return aws.FalseTernary
|
||||||
|
}
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryableConnectionError determines if the underlying error is an HTTP
|
||||||
|
// connection and returns if it should be retried.
|
||||||
|
//
|
||||||
|
// Includes errors such as connection reset, connection refused, net dial,
|
||||||
|
// temporary, and timeout errors.
|
||||||
|
type RetryableConnectionError struct{}
|
||||||
|
|
||||||
|
// IsErrorRetryable returns if the error is caused by and HTTP connection
|
||||||
|
// error, and should be retried.
|
||||||
|
func (r RetryableConnectionError) IsErrorRetryable(err error) aws.Ternary {
|
||||||
|
if err == nil {
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
var retryable bool
|
||||||
|
|
||||||
|
var conErr interface{ ConnectionError() bool }
|
||||||
|
var tempErr interface{ Temporary() bool }
|
||||||
|
var timeoutErr interface{ Timeout() bool }
|
||||||
|
var urlErr *url.Error
|
||||||
|
var netOpErr *net.OpError
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errors.As(err, &conErr) && conErr.ConnectionError():
|
||||||
|
retryable = true
|
||||||
|
|
||||||
|
case strings.Contains(err.Error(), "connection reset"):
|
||||||
|
retryable = true
|
||||||
|
|
||||||
|
case errors.As(err, &urlErr):
|
||||||
|
// Refused connections should be retried as the service may not yet be
|
||||||
|
// running on the port. Go TCP dial considers refused connections as
|
||||||
|
// not temporary.
|
||||||
|
if strings.Contains(urlErr.Error(), "connection refused") {
|
||||||
|
retryable = true
|
||||||
|
} else {
|
||||||
|
return r.IsErrorRetryable(errors.Unwrap(urlErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
case errors.As(err, &netOpErr):
|
||||||
|
// Network dial, or temporary network errors are always retryable.
|
||||||
|
if strings.EqualFold(netOpErr.Op, "dial") || netOpErr.Temporary() {
|
||||||
|
retryable = true
|
||||||
|
} else {
|
||||||
|
return r.IsErrorRetryable(errors.Unwrap(netOpErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
case errors.As(err, &tempErr) && tempErr.Temporary():
|
||||||
|
// Fallback to the generic temporary check, with temporary errors
|
||||||
|
// retryable.
|
||||||
|
retryable = true
|
||||||
|
|
||||||
|
case errors.As(err, &timeoutErr) && timeoutErr.Timeout():
|
||||||
|
// Fallback to the generic timeout check, with timeout errors
|
||||||
|
// retryable.
|
||||||
|
retryable = true
|
||||||
|
|
||||||
|
default:
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
return aws.BoolTernary(retryable)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryableHTTPStatusCode provides a IsErrorRetryable based on HTTP status
|
||||||
|
// codes.
|
||||||
|
type RetryableHTTPStatusCode struct {
|
||||||
|
Codes map[int]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrorRetryable return if the passed in error is retryable based on the
|
||||||
|
// HTTP status code.
|
||||||
|
func (r RetryableHTTPStatusCode) IsErrorRetryable(err error) aws.Ternary {
|
||||||
|
var v interface{ HTTPStatusCode() int }
|
||||||
|
|
||||||
|
if !errors.As(err, &v) {
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := r.Codes[v.HTTPStatusCode()]
|
||||||
|
if !ok {
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
return aws.TrueTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryableErrorCode determines if an attempt should be retried based on the
|
||||||
|
// API error code.
|
||||||
|
type RetryableErrorCode struct {
|
||||||
|
Codes map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrorRetryable return if the error is retryable based on the error codes.
|
||||||
|
// Returns unknown if the error doesn't have a code or it is unknown.
|
||||||
|
func (r RetryableErrorCode) IsErrorRetryable(err error) aws.Ternary {
|
||||||
|
var v interface{ ErrorCode() string }
|
||||||
|
|
||||||
|
if !errors.As(err, &v) {
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := r.Codes[v.ErrorCode()]
|
||||||
|
if !ok {
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
return aws.TrueTernary
|
||||||
|
}
|
|
@ -0,0 +1,210 @@
|
||||||
|
package retry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws/ratelimit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackoffDelayer provides the interface for determining the delay to before
|
||||||
|
// another request attempt, that previously failed.
|
||||||
|
type BackoffDelayer interface {
|
||||||
|
BackoffDelay(attempt int, err error) (time.Duration, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackoffDelayerFunc provides a wrapper around a function to determine the
|
||||||
|
// backoff delay of an attempt retry.
|
||||||
|
type BackoffDelayerFunc func(int, error) (time.Duration, error)
|
||||||
|
|
||||||
|
// BackoffDelay returns the delay before attempt to retry a request.
|
||||||
|
func (fn BackoffDelayerFunc) BackoffDelay(attempt int, err error) (time.Duration, error) {
|
||||||
|
return fn(attempt, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultMaxAttempts is the maximum of attempts for an API request
|
||||||
|
DefaultMaxAttempts int = 3
|
||||||
|
|
||||||
|
// DefaultMaxBackoff is the maximum back off delay between attempts
|
||||||
|
DefaultMaxBackoff time.Duration = 20 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default retry token quota values.
|
||||||
|
const (
|
||||||
|
DefaultRetryRateTokens uint = 500
|
||||||
|
DefaultRetryCost uint = 5
|
||||||
|
DefaultRetryTimeoutCost uint = 10
|
||||||
|
DefaultNoRetryIncrement uint = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultRetryableHTTPStatusCodes is the default set of HTTP status codes the SDK
|
||||||
|
// should consider as retryable errors.
|
||||||
|
var DefaultRetryableHTTPStatusCodes = map[int]struct{}{
|
||||||
|
500: {},
|
||||||
|
502: {},
|
||||||
|
503: {},
|
||||||
|
504: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultRetryableErrorCodes provides the set of API error codes that should
|
||||||
|
// be retried.
|
||||||
|
var DefaultRetryableErrorCodes = map[string]struct{}{
|
||||||
|
"RequestTimeout": {},
|
||||||
|
"RequestTimeoutException": {},
|
||||||
|
|
||||||
|
// Throttled status codes
|
||||||
|
"Throttling": {},
|
||||||
|
"ThrottlingException": {},
|
||||||
|
"ThrottledException": {},
|
||||||
|
"RequestThrottledException": {},
|
||||||
|
"TooManyRequestsException": {},
|
||||||
|
"ProvisionedThroughputExceededException": {},
|
||||||
|
"TransactionInProgressException": {},
|
||||||
|
"RequestLimitExceeded": {},
|
||||||
|
"BandwidthLimitExceeded": {},
|
||||||
|
"LimitExceededException": {},
|
||||||
|
"RequestThrottled": {},
|
||||||
|
"SlowDown": {},
|
||||||
|
"PriorRequestNotComplete": {},
|
||||||
|
"EC2ThrottledException": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultRetryables provides the set of retryable checks that are used by
|
||||||
|
// default.
|
||||||
|
var DefaultRetryables = []IsErrorRetryable{
|
||||||
|
NoRetryCanceledError{},
|
||||||
|
RetryableError{},
|
||||||
|
RetryableConnectionError{},
|
||||||
|
RetryableHTTPStatusCode{
|
||||||
|
Codes: DefaultRetryableHTTPStatusCodes,
|
||||||
|
},
|
||||||
|
RetryableErrorCode{
|
||||||
|
Codes: DefaultRetryableErrorCodes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// StandardOptions provides the functional options for configuring the standard
|
||||||
|
// retryable, and delay behavior.
|
||||||
|
type StandardOptions struct {
|
||||||
|
MaxAttempts int
|
||||||
|
MaxBackoff time.Duration
|
||||||
|
Backoff BackoffDelayer
|
||||||
|
|
||||||
|
Retryables []IsErrorRetryable
|
||||||
|
Timeouts []IsErrorTimeout
|
||||||
|
|
||||||
|
RateLimiter RateLimiter
|
||||||
|
RetryCost uint
|
||||||
|
RetryTimeoutCost uint
|
||||||
|
NoRetryIncrement uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimiter provides the interface for limiting the rate of request retries
|
||||||
|
// allowed by the retrier.
|
||||||
|
type RateLimiter interface {
|
||||||
|
GetToken(ctx context.Context, cost uint) (releaseToken func() error, err error)
|
||||||
|
AddTokens(uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func nopTokenRelease(error) error { return nil }
|
||||||
|
|
||||||
|
// Standard is the standard retry pattern for the SDK. It uses a set of
|
||||||
|
// retryable checks to determine of the failed request should be retried, and
|
||||||
|
// what retry delay should be used.
|
||||||
|
type Standard struct {
|
||||||
|
options StandardOptions
|
||||||
|
|
||||||
|
timeout IsErrorTimeout
|
||||||
|
retryable IsErrorRetryable
|
||||||
|
backoff BackoffDelayer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStandard initializes a standard retry behavior with defaults that can be
|
||||||
|
// overridden via functional options.
|
||||||
|
func NewStandard(fnOpts ...func(*StandardOptions)) *Standard {
|
||||||
|
o := StandardOptions{
|
||||||
|
MaxAttempts: DefaultMaxAttempts,
|
||||||
|
MaxBackoff: DefaultMaxBackoff,
|
||||||
|
Retryables: DefaultRetryables,
|
||||||
|
|
||||||
|
RateLimiter: ratelimit.NewTokenRateLimit(DefaultRetryRateTokens),
|
||||||
|
RetryCost: DefaultRetryCost,
|
||||||
|
RetryTimeoutCost: DefaultRetryTimeoutCost,
|
||||||
|
NoRetryIncrement: DefaultNoRetryIncrement,
|
||||||
|
}
|
||||||
|
for _, fn := range fnOpts {
|
||||||
|
fn(&o)
|
||||||
|
}
|
||||||
|
|
||||||
|
backoff := o.Backoff
|
||||||
|
if backoff == nil {
|
||||||
|
backoff = NewExponentialJitterBackoff(o.MaxBackoff)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := make([]IsErrorRetryable, len(o.Retryables))
|
||||||
|
copy(rs, o.Retryables)
|
||||||
|
|
||||||
|
ts := make([]IsErrorTimeout, len(o.Timeouts))
|
||||||
|
copy(ts, o.Timeouts)
|
||||||
|
|
||||||
|
return &Standard{
|
||||||
|
options: o,
|
||||||
|
backoff: backoff,
|
||||||
|
retryable: IsErrorRetryables(rs),
|
||||||
|
timeout: IsErrorTimeouts(ts),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxAttempts returns the maximum number of attempts that can be made for a
|
||||||
|
// request before failing.
|
||||||
|
func (s *Standard) MaxAttempts() int {
|
||||||
|
return s.options.MaxAttempts
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrorRetryable returns if the error is can be retried or not. Should not
|
||||||
|
// consider the number of attempts made.
|
||||||
|
func (s *Standard) IsErrorRetryable(err error) bool {
|
||||||
|
return s.retryable.IsErrorRetryable(err).Bool()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryDelay returns the delay to use before another request attempt is made.
|
||||||
|
func (s *Standard) RetryDelay(attempt int, err error) (time.Duration, error) {
|
||||||
|
return s.backoff.BackoffDelay(attempt, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInitialToken returns the initial request token that can increment the
|
||||||
|
// retry token pool if the request is successful.
|
||||||
|
func (s *Standard) GetInitialToken() func(error) error {
|
||||||
|
return releaseToken(s.incrementTokens).release
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Standard) incrementTokens() error {
|
||||||
|
return s.options.RateLimiter.AddTokens(s.options.NoRetryIncrement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRetryToken attempts to deduct the retry cost from the retry token pool.
|
||||||
|
// Returning the token release function, or error.
|
||||||
|
func (s *Standard) GetRetryToken(ctx context.Context, err error) (func(error) error, error) {
|
||||||
|
cost := s.options.RetryCost
|
||||||
|
if s.timeout.IsErrorTimeout(err).Bool() {
|
||||||
|
cost = s.options.RetryTimeoutCost
|
||||||
|
}
|
||||||
|
|
||||||
|
fn, err := s.options.RateLimiter.GetToken(ctx, cost)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return releaseToken(fn).release, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type releaseToken func() error
|
||||||
|
|
||||||
|
func (f releaseToken) release(err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return f()
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package retry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsErrorTimeout provides the interface of an implementation to determine if
|
||||||
|
// a error matches.
|
||||||
|
type IsErrorTimeout interface {
|
||||||
|
IsErrorTimeout(err error) aws.Ternary
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrorTimeouts is a collection of checks to determine of the error is
|
||||||
|
// retryable. Iterates through the checks and returns the state of retryable
|
||||||
|
// if any check returns something other than unknown.
|
||||||
|
type IsErrorTimeouts []IsErrorTimeout
|
||||||
|
|
||||||
|
// IsErrorTimeout returns if the error is retryable if any of the checks in
|
||||||
|
// the list return a value other than unknown.
|
||||||
|
func (ts IsErrorTimeouts) IsErrorTimeout(err error) aws.Ternary {
|
||||||
|
for _, t := range ts {
|
||||||
|
if v := t.IsErrorTimeout(err); v != aws.UnknownTernary {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrorTimeoutFunc wraps a function with the IsErrorTimeout interface.
|
||||||
|
type IsErrorTimeoutFunc func(error) aws.Ternary
|
||||||
|
|
||||||
|
// IsErrorTimeout returns if the error is retryable.
|
||||||
|
func (fn IsErrorTimeoutFunc) IsErrorTimeout(err error) aws.Ternary {
|
||||||
|
return fn(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeouterError provides the IsErrorTimeout implementation for determining if
|
||||||
|
// an error is a timeout based on type with the Timeout method.
|
||||||
|
type TimeouterError struct{}
|
||||||
|
|
||||||
|
// IsErrorTimeout returns if the error is a timeout error.
|
||||||
|
func (t TimeouterError) IsErrorTimeout(err error) aws.Ternary {
|
||||||
|
var v interface{ Timeout() bool }
|
||||||
|
|
||||||
|
if !errors.As(err, &v) {
|
||||||
|
return aws.UnknownTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
return aws.BoolTernary(v.Timeout())
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Retryer is an interface to determine if a given error from a
|
||||||
|
// request should be retried, and if so what backoff delay to apply. The
|
||||||
|
// default implementation used by most services is the retry package's Standard
|
||||||
|
// type. Which contains basic retry logic using exponential backoff.
|
||||||
|
type Retryer interface {
|
||||||
|
// IsErrorRetryable returns if the failed request is retryable. This check
|
||||||
|
// should determine if the error can be retried, or if the error is
|
||||||
|
// terminal.
|
||||||
|
IsErrorRetryable(error) bool
|
||||||
|
|
||||||
|
// MaxAttempts returns the maximum number of attempts that can be made for
|
||||||
|
// a request before failing. A value of 0 implies that the request should
|
||||||
|
// be retried until it succeeds if the errors are retryable.
|
||||||
|
MaxAttempts() int
|
||||||
|
|
||||||
|
// RetryDelay returns the delay that should be used before retrying the
|
||||||
|
// request. Will return error if the if the delay could not be determined.
|
||||||
|
RetryDelay(attempt int, opErr error) (time.Duration, error)
|
||||||
|
|
||||||
|
// GetRetryToken attempts to deduct the retry cost from the retry token pool.
|
||||||
|
// Returning the token release function, or error.
|
||||||
|
GetRetryToken(ctx context.Context, opErr error) (releaseToken func(error) error, err error)
|
||||||
|
|
||||||
|
// GetInitalToken returns the initial request token that can increment the
|
||||||
|
// retry token pool if the request is successful.
|
||||||
|
GetInitialToken() (releaseToken func(error) error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NopRetryer provides a RequestRetryDecider implementation that will flag
|
||||||
|
// all attempt errors as not retryable, with a max attempts of 1.
|
||||||
|
type NopRetryer struct{}
|
||||||
|
|
||||||
|
// IsErrorRetryable returns false for all error values.
|
||||||
|
func (NopRetryer) IsErrorRetryable(error) bool { return false }
|
||||||
|
|
||||||
|
// MaxAttempts always returns 1 for the original request attempt.
|
||||||
|
func (NopRetryer) MaxAttempts() int { return 1 }
|
||||||
|
|
||||||
|
// RetryDelay is not valid for the NopRetryer. Will always return error.
|
||||||
|
func (NopRetryer) RetryDelay(int, error) (time.Duration, error) {
|
||||||
|
return 0, fmt.Errorf("not retrying any request errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRetryToken returns a stub function that does nothing.
|
||||||
|
func (NopRetryer) GetRetryToken(context.Context, error) (func(error) error, error) {
|
||||||
|
return nopReleaseToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInitialToken returns a stub function that does nothing.
|
||||||
|
func (NopRetryer) GetInitialToken() func(error) error {
|
||||||
|
return nopReleaseToken
|
||||||
|
}
|
||||||
|
|
||||||
|
func nopReleaseToken(error) error { return nil }
|
115
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/cache.go
generated
vendored
Normal file
115
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/cache.go
generated
vendored
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupKey(service, region string) string {
|
||||||
|
var s strings.Builder
|
||||||
|
s.Grow(len(region) + len(service) + 3)
|
||||||
|
s.WriteString(region)
|
||||||
|
s.WriteRune('/')
|
||||||
|
s.WriteString(service)
|
||||||
|
return s.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type derivedKey struct {
|
||||||
|
AccessKey string
|
||||||
|
Date time.Time
|
||||||
|
Credential []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type derivedKeyCache struct {
|
||||||
|
values map[string]derivedKey
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDerivedKeyCache() derivedKeyCache {
|
||||||
|
return derivedKeyCache{
|
||||||
|
values: make(map[string]derivedKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *derivedKeyCache) Get(credentials aws.Credentials, service, region string, signingTime SigningTime) []byte {
|
||||||
|
key := lookupKey(service, region)
|
||||||
|
s.mutex.RLock()
|
||||||
|
if cred, ok := s.get(key, credentials, signingTime.Time); ok {
|
||||||
|
s.mutex.RUnlock()
|
||||||
|
return cred
|
||||||
|
}
|
||||||
|
s.mutex.RUnlock()
|
||||||
|
|
||||||
|
s.mutex.Lock()
|
||||||
|
if cred, ok := s.get(key, credentials, signingTime.Time); ok {
|
||||||
|
s.mutex.Unlock()
|
||||||
|
return cred
|
||||||
|
}
|
||||||
|
cred := deriveKey(credentials.SecretAccessKey, service, region, signingTime)
|
||||||
|
entry := derivedKey{
|
||||||
|
AccessKey: credentials.AccessKeyID,
|
||||||
|
Date: signingTime.Time,
|
||||||
|
Credential: cred,
|
||||||
|
}
|
||||||
|
s.values[key] = entry
|
||||||
|
s.mutex.Unlock()
|
||||||
|
|
||||||
|
return cred
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *derivedKeyCache) get(key string, credentials aws.Credentials, signingTime time.Time) ([]byte, bool) {
|
||||||
|
cacheEntry, ok := s.retrieveFromCache(key)
|
||||||
|
if ok && cacheEntry.AccessKey == credentials.AccessKeyID && isSameDay(signingTime, cacheEntry.Date) {
|
||||||
|
return cacheEntry.Credential, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *derivedKeyCache) retrieveFromCache(key string) (derivedKey, bool) {
|
||||||
|
if v, ok := s.values[key]; ok {
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
return derivedKey{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigningKeyDeriver derives a signing key from a set of credentials
|
||||||
|
type SigningKeyDeriver struct {
|
||||||
|
cache derivedKeyCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSigningKeyDeriver returns a new SigningKeyDeriver
|
||||||
|
func NewSigningKeyDeriver() *SigningKeyDeriver {
|
||||||
|
return &SigningKeyDeriver{
|
||||||
|
cache: newDerivedKeyCache(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeriveKey returns a derived signing key from the given credentials to be used with SigV4 signing.
|
||||||
|
func (k *SigningKeyDeriver) DeriveKey(credential aws.Credentials, service, region string, signingTime SigningTime) []byte {
|
||||||
|
return k.cache.Get(credential, service, region, signingTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deriveKey(secret, service, region string, t SigningTime) []byte {
|
||||||
|
hmacDate := HMACSHA256([]byte("AWS4"+secret), []byte(t.ShortTimeFormat()))
|
||||||
|
hmacRegion := HMACSHA256(hmacDate, []byte(region))
|
||||||
|
hmacService := HMACSHA256(hmacRegion, []byte(service))
|
||||||
|
return HMACSHA256(hmacService, []byte("aws4_request"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSameDay(x, y time.Time) bool {
|
||||||
|
xYear, xMonth, xDay := x.Date()
|
||||||
|
yYear, yMonth, yDay := y.Date()
|
||||||
|
|
||||||
|
if xYear != yYear {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if xMonth != yMonth {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return xDay == yDay
|
||||||
|
}
|
36
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/const.go
generated
vendored
Normal file
36
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/const.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EmptyStringSHA256 is the hex encoded sha256 value of an empty string
|
||||||
|
EmptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
|
||||||
|
|
||||||
|
// UnsignedPayload indicates that the request payload body is unsigned
|
||||||
|
UnsignedPayload = "UNSIGNED-PAYLOAD"
|
||||||
|
|
||||||
|
// AmzAlgorithmKey indicates the signing algorithm
|
||||||
|
AmzAlgorithmKey = "X-Amz-Algorithm"
|
||||||
|
|
||||||
|
// AmzSecurityTokenKey indicates the security token to be used with temporary credentials
|
||||||
|
AmzSecurityTokenKey = "X-Amz-Security-Token"
|
||||||
|
|
||||||
|
// AmzDateKey is the UTC timestamp for the request in the format YYYYMMDD'T'HHMMSS'Z'
|
||||||
|
AmzDateKey = "X-Amz-Date"
|
||||||
|
|
||||||
|
// AmzCredentialKey is the access key ID and credential scope
|
||||||
|
AmzCredentialKey = "X-Amz-Credential"
|
||||||
|
|
||||||
|
// AmzSignedHeadersKey is the set of headers signed for the request
|
||||||
|
AmzSignedHeadersKey = "X-Amz-SignedHeaders"
|
||||||
|
|
||||||
|
// AmzSignatureKey is the query parameter to store the SigV4 signature
|
||||||
|
AmzSignatureKey = "X-Amz-Signature"
|
||||||
|
|
||||||
|
// TimeFormat is the time format to be used in the X-Amz-Date header or query parameter
|
||||||
|
TimeFormat = "20060102T150405Z"
|
||||||
|
|
||||||
|
// ShortTimeFormat is the shorten time format used in the credential scope
|
||||||
|
ShortTimeFormat = "20060102"
|
||||||
|
|
||||||
|
// ContentSHAKey is the SHA256 of request body
|
||||||
|
ContentSHAKey = "X-Amz-Content-Sha256"
|
||||||
|
)
|
82
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/header_rules.go
generated
vendored
Normal file
82
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/header_rules.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdkstrings "github.com/aws/aws-sdk-go-v2/internal/strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rules houses a set of Rule needed for validation of a
|
||||||
|
// string value
|
||||||
|
type Rules []Rule
|
||||||
|
|
||||||
|
// Rule interface allows for more flexible rules and just simply
|
||||||
|
// checks whether or not a value adheres to that Rule
|
||||||
|
type Rule interface {
|
||||||
|
IsValid(value string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid will iterate through all rules and see if any rules
|
||||||
|
// apply to the value and supports nested rules
|
||||||
|
func (r Rules) IsValid(value string) bool {
|
||||||
|
for _, rule := range r {
|
||||||
|
if rule.IsValid(value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapRule generic Rule for maps
|
||||||
|
type MapRule map[string]struct{}
|
||||||
|
|
||||||
|
// IsValid for the map Rule satisfies whether it exists in the map
|
||||||
|
func (m MapRule) IsValid(value string) bool {
|
||||||
|
_, ok := m[value]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whitelist is a generic Rule for whitelisting
|
||||||
|
type Whitelist struct {
|
||||||
|
Rule
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid for Whitelist checks if the value is within the Whitelist
|
||||||
|
func (w Whitelist) IsValid(value string) bool {
|
||||||
|
return w.Rule.IsValid(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blacklist is a generic Rule for blacklisting
|
||||||
|
type Blacklist struct {
|
||||||
|
Rule
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid for Whitelist checks if the value is within the Whitelist
|
||||||
|
func (b Blacklist) IsValid(value string) bool {
|
||||||
|
return !b.Rule.IsValid(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patterns is a list of strings to match against
|
||||||
|
type Patterns []string
|
||||||
|
|
||||||
|
// IsValid for Patterns checks each pattern and returns if a match has
|
||||||
|
// been found
|
||||||
|
func (p Patterns) IsValid(value string) bool {
|
||||||
|
for _, pattern := range p {
|
||||||
|
if sdkstrings.HasPrefixFold(value, pattern) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// InclusiveRules rules allow for rules to depend on one another
|
||||||
|
type InclusiveRules []Rule
|
||||||
|
|
||||||
|
// IsValid will return true if all rules are true
|
||||||
|
func (r InclusiveRules) IsValid(value string) bool {
|
||||||
|
for _, rule := range r {
|
||||||
|
if !rule.IsValid(value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
67
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/headers.go
generated
vendored
Normal file
67
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/headers.go
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
// IgnoredHeaders is a list of headers that are ignored during signing
|
||||||
|
var IgnoredHeaders = Rules{
|
||||||
|
Blacklist{
|
||||||
|
MapRule{
|
||||||
|
"Authorization": struct{}{},
|
||||||
|
"User-Agent": struct{}{},
|
||||||
|
"X-Amzn-Trace-Id": struct{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequiredSignedHeaders is a whitelist for Build canonical headers.
|
||||||
|
var RequiredSignedHeaders = Rules{
|
||||||
|
Whitelist{
|
||||||
|
MapRule{
|
||||||
|
"Cache-Control": struct{}{},
|
||||||
|
"Content-Disposition": struct{}{},
|
||||||
|
"Content-Encoding": struct{}{},
|
||||||
|
"Content-Language": struct{}{},
|
||||||
|
"Content-Md5": struct{}{},
|
||||||
|
"Content-Type": struct{}{},
|
||||||
|
"Expires": struct{}{},
|
||||||
|
"If-Match": struct{}{},
|
||||||
|
"If-Modified-Since": struct{}{},
|
||||||
|
"If-None-Match": struct{}{},
|
||||||
|
"If-Unmodified-Since": struct{}{},
|
||||||
|
"Range": struct{}{},
|
||||||
|
"X-Amz-Acl": struct{}{},
|
||||||
|
"X-Amz-Copy-Source": struct{}{},
|
||||||
|
"X-Amz-Copy-Source-If-Match": struct{}{},
|
||||||
|
"X-Amz-Copy-Source-If-Modified-Since": struct{}{},
|
||||||
|
"X-Amz-Copy-Source-If-None-Match": struct{}{},
|
||||||
|
"X-Amz-Copy-Source-If-Unmodified-Since": struct{}{},
|
||||||
|
"X-Amz-Copy-Source-Range": struct{}{},
|
||||||
|
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Algorithm": struct{}{},
|
||||||
|
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key": struct{}{},
|
||||||
|
"X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
|
||||||
|
"X-Amz-Grant-Full-control": struct{}{},
|
||||||
|
"X-Amz-Grant-Read": struct{}{},
|
||||||
|
"X-Amz-Grant-Read-Acp": struct{}{},
|
||||||
|
"X-Amz-Grant-Write": struct{}{},
|
||||||
|
"X-Amz-Grant-Write-Acp": struct{}{},
|
||||||
|
"X-Amz-Metadata-Directive": struct{}{},
|
||||||
|
"X-Amz-Mfa": struct{}{},
|
||||||
|
"X-Amz-Request-Payer": struct{}{},
|
||||||
|
"X-Amz-Server-Side-Encryption": struct{}{},
|
||||||
|
"X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id": struct{}{},
|
||||||
|
"X-Amz-Server-Side-Encryption-Customer-Algorithm": struct{}{},
|
||||||
|
"X-Amz-Server-Side-Encryption-Customer-Key": struct{}{},
|
||||||
|
"X-Amz-Server-Side-Encryption-Customer-Key-Md5": struct{}{},
|
||||||
|
"X-Amz-Storage-Class": struct{}{},
|
||||||
|
"X-Amz-Website-Redirect-Location": struct{}{},
|
||||||
|
"X-Amz-Content-Sha256": struct{}{},
|
||||||
|
"X-Amz-Tagging": struct{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Patterns{"X-Amz-Meta-"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowedQueryHoisting is a whitelist for Build query headers. The boolean value
|
||||||
|
// represents whether or not it is a pattern.
|
||||||
|
var AllowedQueryHoisting = InclusiveRules{
|
||||||
|
Blacklist{RequiredSignedHeaders},
|
||||||
|
Patterns{"X-Amz-"},
|
||||||
|
}
|
13
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/hmac.go
generated
vendored
Normal file
13
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/hmac.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HMACSHA256 computes a HMAC-SHA256 of data given the provided key.
|
||||||
|
func HMACSHA256(key []byte, data []byte) []byte {
|
||||||
|
hash := hmac.New(sha256.New, key)
|
||||||
|
hash.Write(data)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
75
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/host.go
generated
vendored
Normal file
75
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/host.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SanitizeHostForHeader removes default port from host and updates request.Host
|
||||||
|
func SanitizeHostForHeader(r *http.Request) {
|
||||||
|
host := getHost(r)
|
||||||
|
port := portOnly(host)
|
||||||
|
if port != "" && isDefaultPort(r.URL.Scheme, port) {
|
||||||
|
r.Host = stripPort(host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns host from request
|
||||||
|
func getHost(r *http.Request) string {
|
||||||
|
if r.Host != "" {
|
||||||
|
return r.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.URL.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hostname returns u.Host, without any port number.
|
||||||
|
//
|
||||||
|
// If Host is an IPv6 literal with a port number, Hostname returns the
|
||||||
|
// IPv6 literal without the square brackets. IPv6 literals may include
|
||||||
|
// a zone identifier.
|
||||||
|
//
|
||||||
|
// Copied from the Go 1.8 standard library (net/url)
|
||||||
|
func stripPort(hostport string) string {
|
||||||
|
colon := strings.IndexByte(hostport, ':')
|
||||||
|
if colon == -1 {
|
||||||
|
return hostport
|
||||||
|
}
|
||||||
|
if i := strings.IndexByte(hostport, ']'); i != -1 {
|
||||||
|
return strings.TrimPrefix(hostport[:i], "[")
|
||||||
|
}
|
||||||
|
return hostport[:colon]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port returns the port part of u.Host, without the leading colon.
|
||||||
|
// If u.Host doesn't contain a port, Port returns an empty string.
|
||||||
|
//
|
||||||
|
// Copied from the Go 1.8 standard library (net/url)
|
||||||
|
func portOnly(hostport string) string {
|
||||||
|
colon := strings.IndexByte(hostport, ':')
|
||||||
|
if colon == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if i := strings.Index(hostport, "]:"); i != -1 {
|
||||||
|
return hostport[i+len("]:"):]
|
||||||
|
}
|
||||||
|
if strings.Contains(hostport, "]") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return hostport[colon+len(":"):]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the specified URI is using the standard port
|
||||||
|
// (i.e. port 80 for HTTP URIs or 443 for HTTPS URIs)
|
||||||
|
func isDefaultPort(scheme, port string) bool {
|
||||||
|
if port == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerCaseScheme := strings.ToLower(scheme)
|
||||||
|
if (lowerCaseScheme == "http" && port == "80") || (lowerCaseScheme == "https" && port == "443") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
36
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/time.go
generated
vendored
Normal file
36
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/time.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// SigningTime provides a wrapper around a time.Time which provides cached values for SigV4 signing.
|
||||||
|
type SigningTime struct {
|
||||||
|
time.Time
|
||||||
|
timeFormat string
|
||||||
|
shortTimeFormat string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSigningTime creates a new SigningTime given a time.Time
|
||||||
|
func NewSigningTime(t time.Time) SigningTime {
|
||||||
|
return SigningTime{
|
||||||
|
Time: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeFormat provides a time formatted in the X-Amz-Date format.
|
||||||
|
func (m *SigningTime) TimeFormat() string {
|
||||||
|
return m.format(&m.timeFormat, TimeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShortTimeFormat provides a time formatted of 20060102.
|
||||||
|
func (m *SigningTime) ShortTimeFormat() string {
|
||||||
|
return m.format(&m.shortTimeFormat, ShortTimeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SigningTime) format(target *string, format string) string {
|
||||||
|
if len(*target) > 0 {
|
||||||
|
return *target
|
||||||
|
}
|
||||||
|
v := m.Time.Format(format)
|
||||||
|
*target = v
|
||||||
|
return v
|
||||||
|
}
|
64
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/util.go
generated
vendored
Normal file
64
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/util.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const doubleSpace = " "
|
||||||
|
|
||||||
|
// StripExcessSpaces will rewrite the passed in slice's string values to not
|
||||||
|
// contain muliple side-by-side spaces.
|
||||||
|
func StripExcessSpaces(str string) string {
|
||||||
|
var j, k, l, m, spaces int
|
||||||
|
// Trim trailing spaces
|
||||||
|
for j = len(str) - 1; j >= 0 && str[j] == ' '; j-- {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim leading spaces
|
||||||
|
for k = 0; k < j && str[k] == ' '; k++ {
|
||||||
|
}
|
||||||
|
str = str[k : j+1]
|
||||||
|
|
||||||
|
// Strip multiple spaces.
|
||||||
|
j = strings.Index(str, doubleSpace)
|
||||||
|
if j < 0 {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := []byte(str)
|
||||||
|
for k, m, l = j, j, len(buf); k < l; k++ {
|
||||||
|
if buf[k] == ' ' {
|
||||||
|
if spaces == 0 {
|
||||||
|
// First space.
|
||||||
|
buf[m] = buf[k]
|
||||||
|
m++
|
||||||
|
}
|
||||||
|
spaces++
|
||||||
|
} else {
|
||||||
|
// End of multiple spaces.
|
||||||
|
spaces = 0
|
||||||
|
buf[m] = buf[k]
|
||||||
|
m++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(buf[:m])
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetURIPath returns the escaped URI component from the provided URL
|
||||||
|
func GetURIPath(u *url.URL) string {
|
||||||
|
var uri string
|
||||||
|
|
||||||
|
if len(u.Opaque) > 0 {
|
||||||
|
uri = "/" + strings.Join(strings.Split(u.Opaque, "/")[3:], "/")
|
||||||
|
} else {
|
||||||
|
uri = u.EscapedPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(uri) == 0 {
|
||||||
|
uri = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri
|
||||||
|
}
|
|
@ -0,0 +1,292 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
|
||||||
|
v4Internal "github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/sdk"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
smithyHTTP "github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const computePayloadHashMiddlewareID = "ComputePayloadHash"
|
||||||
|
|
||||||
|
// HashComputationError indicates an error occurred while computing the signing hash
|
||||||
|
type HashComputationError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is the error message
|
||||||
|
func (e *HashComputationError) Error() string {
|
||||||
|
return fmt.Sprintf("failed to compute payload hash: %v", e.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying error if one is set
|
||||||
|
func (e *HashComputationError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigningError indicates an error condition occurred while performing SigV4 signing
|
||||||
|
type SigningError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SigningError) Error() string {
|
||||||
|
return fmt.Sprintf("failed to sign request: %v", e.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying error cause
|
||||||
|
func (e *SigningError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsignedPayload sets the SigV4 request payload hash to unsigned.
|
||||||
|
//
|
||||||
|
// Will not set the Unsigned Payload magic SHA value, if a SHA has already been
|
||||||
|
// stored in the context. (e.g. application pre-computed SHA256 before making
|
||||||
|
// API call).
|
||||||
|
//
|
||||||
|
// This middleware does not check the X-Amz-Content-Sha256 header, if that
|
||||||
|
// header is serialized a middleware must translate it into the context.
|
||||||
|
type unsignedPayload struct{}
|
||||||
|
|
||||||
|
// AddUnsignedPayloadMiddleware adds unsignedPayload to the operation
|
||||||
|
// middleware stack
|
||||||
|
func AddUnsignedPayloadMiddleware(stack *middleware.Stack) error {
|
||||||
|
return stack.Build.Add(&unsignedPayload{}, middleware.After)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the unsignedPayload identifier
|
||||||
|
func (m *unsignedPayload) ID() string {
|
||||||
|
return computePayloadHashMiddlewareID
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleBuild sets the payload hash to be an unsigned payload
|
||||||
|
func (m *unsignedPayload) HandleBuild(
|
||||||
|
ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
|
||||||
|
) (
|
||||||
|
out middleware.BuildOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
// This should not compute the content SHA256 if the value is already
|
||||||
|
// known. (e.g. application pre-computed SHA256 before making API call).
|
||||||
|
// Does not have any tight coupling to the X-Amz-Content-Sha256 header, if
|
||||||
|
// that header is provided a middleware must translate it into the context.
|
||||||
|
contentSHA := GetPayloadHash(ctx)
|
||||||
|
if len(contentSHA) == 0 {
|
||||||
|
contentSHA = v4Internal.UnsignedPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = SetPayloadHash(ctx, contentSHA)
|
||||||
|
return next.HandleBuild(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// computePayloadSHA256 computes SHA256 payload hash to sign.
|
||||||
|
//
|
||||||
|
// Will not set the Unsigned Payload magic SHA value, if a SHA has already been
|
||||||
|
// stored in the context. (e.g. application pre-computed SHA256 before making
|
||||||
|
// API call).
|
||||||
|
//
|
||||||
|
// This middleware does not check the X-Amz-Content-Sha256 header, if that
|
||||||
|
// header is serialized a middleware must translate it into the context.
|
||||||
|
type computePayloadSHA256 struct{}
|
||||||
|
|
||||||
|
// AddComputePayloadSHA256Middleware adds computePayloadSHA256 to the
|
||||||
|
// operation middleware stack
|
||||||
|
func AddComputePayloadSHA256Middleware(stack *middleware.Stack) error {
|
||||||
|
return stack.Build.Add(&computePayloadSHA256{}, middleware.After)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveComputePayloadSHA256Middleware removes computePayloadSHA256 from the
|
||||||
|
// operation middleware stack
|
||||||
|
func RemoveComputePayloadSHA256Middleware(stack *middleware.Stack) error {
|
||||||
|
_, err := stack.Build.Remove(computePayloadHashMiddlewareID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID is the middleware name
|
||||||
|
func (m *computePayloadSHA256) ID() string {
|
||||||
|
return computePayloadHashMiddlewareID
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleBuild compute the payload hash for the request payload
|
||||||
|
func (m *computePayloadSHA256) HandleBuild(
|
||||||
|
ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
|
||||||
|
) (
|
||||||
|
out middleware.BuildOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
req, ok := in.Request.(*smithyHTTP.Request)
|
||||||
|
if !ok {
|
||||||
|
return out, metadata, &HashComputationError{
|
||||||
|
Err: fmt.Errorf("unexpected request middleware type %T", in.Request),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should not compute the content SHA256 if the value is already
|
||||||
|
// known. (e.g. application pre-computed SHA256 before making API call)
|
||||||
|
// Does not have any tight coupling to the X-Amz-Content-Sha256 header, if
|
||||||
|
// that header is provided a middleware must translate it into the context.
|
||||||
|
if contentSHA := GetPayloadHash(ctx); len(contentSHA) != 0 {
|
||||||
|
return next.HandleBuild(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.New()
|
||||||
|
if stream := req.GetStream(); stream != nil {
|
||||||
|
_, err = io.Copy(hash, stream)
|
||||||
|
if err != nil {
|
||||||
|
return out, metadata, &HashComputationError{
|
||||||
|
Err: fmt.Errorf("failed to compute payload hash, %w", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := req.RewindStream(); err != nil {
|
||||||
|
return out, metadata, &HashComputationError{
|
||||||
|
Err: fmt.Errorf("failed to seek body to start, %w", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = SetPayloadHash(ctx, hex.EncodeToString(hash.Sum(nil)))
|
||||||
|
|
||||||
|
return next.HandleBuild(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// contentSHA256Header sets the X-Amz-Content-Sha256 header value to
|
||||||
|
// the Payload hash stored in the context.
|
||||||
|
type contentSHA256Header struct{}
|
||||||
|
|
||||||
|
// AddContentSHA256HeaderMiddleware adds ContentSHA256Header to the
|
||||||
|
// operation middleware stack
|
||||||
|
func AddContentSHA256HeaderMiddleware(stack *middleware.Stack) error {
|
||||||
|
return stack.Build.Insert(&contentSHA256Header{}, computePayloadHashMiddlewareID, middleware.After)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveContentSHA256HeaderMiddleware removes contentSHA256Header middleware
|
||||||
|
// from the operation middleware stack
|
||||||
|
func RemoveContentSHA256HeaderMiddleware(stack *middleware.Stack) error {
|
||||||
|
_, err := stack.Build.Remove((*contentSHA256Header)(nil).ID())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the ContentSHA256HeaderMiddleware identifier
|
||||||
|
func (m *contentSHA256Header) ID() string {
|
||||||
|
return "SigV4ContentSHA256Header"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleBuild sets the X-Amz-Content-Sha256 header value to the Payload hash
|
||||||
|
// stored in the context.
|
||||||
|
func (m *contentSHA256Header) HandleBuild(
|
||||||
|
ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
|
||||||
|
) (
|
||||||
|
out middleware.BuildOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
req, ok := in.Request.(*smithyHTTP.Request)
|
||||||
|
if !ok {
|
||||||
|
return out, metadata, &HashComputationError{Err: fmt.Errorf("unexpected request middleware type %T", in.Request)}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set(v4Internal.ContentSHAKey, GetPayloadHash(ctx))
|
||||||
|
|
||||||
|
return next.HandleBuild(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignHTTPRequestMiddlewareOptions is the configuration options for the SignHTTPRequestMiddleware middleware.
|
||||||
|
type SignHTTPRequestMiddlewareOptions struct {
|
||||||
|
CredentialsProvider aws.CredentialsProvider
|
||||||
|
Signer HTTPSigner
|
||||||
|
LogSigning bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignHTTPRequestMiddleware is a `FinalizeMiddleware` implementation for SigV4 HTTP Signing
|
||||||
|
type SignHTTPRequestMiddleware struct {
|
||||||
|
credentialsProvider aws.CredentialsProvider
|
||||||
|
signer HTTPSigner
|
||||||
|
logSigning bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSignHTTPRequestMiddleware constructs a SignHTTPRequestMiddleware using the given Signer for signing requests
|
||||||
|
func NewSignHTTPRequestMiddleware(options SignHTTPRequestMiddlewareOptions) *SignHTTPRequestMiddleware {
|
||||||
|
return &SignHTTPRequestMiddleware{
|
||||||
|
credentialsProvider: options.CredentialsProvider,
|
||||||
|
signer: options.Signer,
|
||||||
|
logSigning: options.LogSigning,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID is the SignHTTPRequestMiddleware identifier
|
||||||
|
func (s *SignHTTPRequestMiddleware) ID() string {
|
||||||
|
return "Signing"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFinalize will take the provided input and sign the request using the SigV4 authentication scheme
|
||||||
|
func (s *SignHTTPRequestMiddleware) HandleFinalize(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (
|
||||||
|
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
if !haveCredentialProvider(s.credentialsProvider) {
|
||||||
|
return next.HandleFinalize(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, ok := in.Request.(*smithyHTTP.Request)
|
||||||
|
if !ok {
|
||||||
|
return out, metadata, &SigningError{Err: fmt.Errorf("unexpected request middleware type %T", in.Request)}
|
||||||
|
}
|
||||||
|
|
||||||
|
signingName, signingRegion := awsmiddleware.GetSigningName(ctx), awsmiddleware.GetSigningRegion(ctx)
|
||||||
|
payloadHash := GetPayloadHash(ctx)
|
||||||
|
if len(payloadHash) == 0 {
|
||||||
|
return out, metadata, &SigningError{Err: fmt.Errorf("computed payload hash missing from context")}
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials, err := s.credentialsProvider.Retrieve(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return out, metadata, &SigningError{Err: fmt.Errorf("failed to retrieve credentials: %w", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.signer.SignHTTP(ctx, credentials, req.Request, payloadHash, signingName, signingRegion, sdk.NowTime(),
|
||||||
|
func(o *SignerOptions) {
|
||||||
|
o.Logger = middleware.GetLogger(ctx)
|
||||||
|
o.LogSigning = s.logSigning
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return out, metadata, &SigningError{Err: fmt.Errorf("failed to sign http request, %w", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next.HandleFinalize(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func haveCredentialProvider(p aws.CredentialsProvider) bool {
|
||||||
|
if p == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch p.(type) {
|
||||||
|
case aws.AnonymousCredentials,
|
||||||
|
*aws.AnonymousCredentials:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type payloadHashKey struct{}
|
||||||
|
|
||||||
|
// GetPayloadHash retrieves the payload hash to use for signing
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func GetPayloadHash(ctx context.Context) (v string) {
|
||||||
|
v, _ = middleware.GetStackValue(ctx, payloadHashKey{}).(string)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPayloadHash sets the payload hash to be used for signing the request
|
||||||
|
//
|
||||||
|
// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues
|
||||||
|
// to clear all stack values.
|
||||||
|
func SetPayloadHash(ctx context.Context, hash string) context.Context {
|
||||||
|
return middleware.WithStackValue(ctx, payloadHashKey{}, hash)
|
||||||
|
}
|
127
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/presign_middleware.go
generated
vendored
Normal file
127
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/presign_middleware.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/internal/sdk"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
smithyHTTP "github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPPresigner is an interface to a SigV4 signer that can sign create a
|
||||||
|
// presigned URL for a HTTP requests.
|
||||||
|
type HTTPPresigner interface {
|
||||||
|
PresignHTTP(
|
||||||
|
ctx context.Context, credentials aws.Credentials, r *http.Request,
|
||||||
|
payloadHash string, service string, region string, signingTime time.Time,
|
||||||
|
optFns ...func(*SignerOptions),
|
||||||
|
) (url string, signedHeader http.Header, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresignedHTTPRequest provides the URL and signed headers that are included
|
||||||
|
// in the presigned URL.
|
||||||
|
type PresignedHTTPRequest struct {
|
||||||
|
URL string
|
||||||
|
Method string
|
||||||
|
SignedHeader http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresignHTTPRequestMiddlewareOptions is the options for the PresignHTTPRequestMiddleware middleware.
|
||||||
|
type PresignHTTPRequestMiddlewareOptions struct {
|
||||||
|
CredentialsProvider aws.CredentialsProvider
|
||||||
|
Presigner HTTPPresigner
|
||||||
|
LogSigning bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresignHTTPRequestMiddleware provides the Finalize middleware for creating a
|
||||||
|
// presigned URL for an HTTP request.
|
||||||
|
//
|
||||||
|
// Will short circuit the middleware stack and not forward onto the next
|
||||||
|
// Finalize handler.
|
||||||
|
type PresignHTTPRequestMiddleware struct {
|
||||||
|
credentialsProvider aws.CredentialsProvider
|
||||||
|
presigner HTTPPresigner
|
||||||
|
logSigning bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPresignHTTPRequestMiddleware returns a new PresignHTTPRequestMiddleware
|
||||||
|
// initialized with the presigner.
|
||||||
|
func NewPresignHTTPRequestMiddleware(options PresignHTTPRequestMiddlewareOptions) *PresignHTTPRequestMiddleware {
|
||||||
|
return &PresignHTTPRequestMiddleware{
|
||||||
|
credentialsProvider: options.CredentialsProvider,
|
||||||
|
presigner: options.Presigner,
|
||||||
|
logSigning: options.LogSigning,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID provides the middleware ID.
|
||||||
|
func (*PresignHTTPRequestMiddleware) ID() string { return "PresignHTTPRequest" }
|
||||||
|
|
||||||
|
// HandleFinalize will take the provided input and create a presigned url for
|
||||||
|
// the http request using the SigV4 presign authentication scheme.
|
||||||
|
//
|
||||||
|
// Since the signed request is not a valid HTTP request
|
||||||
|
func (s *PresignHTTPRequestMiddleware) HandleFinalize(
|
||||||
|
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
|
||||||
|
) (
|
||||||
|
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
req, ok := in.Request.(*smithyHTTP.Request)
|
||||||
|
if !ok {
|
||||||
|
return out, metadata, &SigningError{
|
||||||
|
Err: fmt.Errorf("unexpected request middleware type %T", in.Request),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq := req.Build(ctx)
|
||||||
|
if !haveCredentialProvider(s.credentialsProvider) {
|
||||||
|
out.Result = &PresignedHTTPRequest{
|
||||||
|
URL: httpReq.URL.String(),
|
||||||
|
Method: httpReq.Method,
|
||||||
|
SignedHeader: http.Header{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
signingName := awsmiddleware.GetSigningName(ctx)
|
||||||
|
signingRegion := awsmiddleware.GetSigningRegion(ctx)
|
||||||
|
payloadHash := GetPayloadHash(ctx)
|
||||||
|
if len(payloadHash) == 0 {
|
||||||
|
return out, metadata, &SigningError{
|
||||||
|
Err: fmt.Errorf("computed payload hash missing from context"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials, err := s.credentialsProvider.Retrieve(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return out, metadata, &SigningError{
|
||||||
|
Err: fmt.Errorf("failed to retrieve credentials: %w", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u, h, err := s.presigner.PresignHTTP(ctx, credentials,
|
||||||
|
httpReq, payloadHash, signingName, signingRegion, sdk.NowTime(),
|
||||||
|
func(o *SignerOptions) {
|
||||||
|
o.Logger = middleware.GetLogger(ctx)
|
||||||
|
o.LogSigning = s.logSigning
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return out, metadata, &SigningError{
|
||||||
|
Err: fmt.Errorf("failed to sign http request, %w", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Result = &PresignedHTTPRequest{
|
||||||
|
URL: u,
|
||||||
|
Method: httpReq.Method,
|
||||||
|
SignedHeader: h,
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, metadata, nil
|
||||||
|
}
|
|
@ -0,0 +1,539 @@
|
||||||
|
// Package v4 implements signing for AWS V4 signer
|
||||||
|
//
|
||||||
|
// Provides request signing for request that need to be signed with
|
||||||
|
// AWS V4 Signatures.
|
||||||
|
//
|
||||||
|
// Standalone Signer
|
||||||
|
//
|
||||||
|
// Generally using the signer outside of the SDK should not require any additional
|
||||||
|
// The signer does this by taking advantage of the URL.EscapedPath method. If your request URI requires
|
||||||
|
// additional escaping you many need to use the URL.Opaque to define what the raw URI should be sent
|
||||||
|
// to the service as.
|
||||||
|
//
|
||||||
|
// The signer will first check the URL.Opaque field, and use its value if set.
|
||||||
|
// The signer does require the URL.Opaque field to be set in the form of:
|
||||||
|
//
|
||||||
|
// "//<hostname>/<path>"
|
||||||
|
//
|
||||||
|
// // e.g.
|
||||||
|
// "//example.com/some/path"
|
||||||
|
//
|
||||||
|
// The leading "//" and hostname are required or the URL.Opaque escaping will
|
||||||
|
// not work correctly.
|
||||||
|
//
|
||||||
|
// If URL.Opaque is not set the signer will fallback to the URL.EscapedPath()
|
||||||
|
// method and using the returned value.
|
||||||
|
//
|
||||||
|
// AWS v4 signature validation requires that the canonical string's URI path
|
||||||
|
// element must be the URI escaped form of the HTTP request's path.
|
||||||
|
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||||
|
//
|
||||||
|
// The Go HTTP client will perform escaping automatically on the request. Some
|
||||||
|
// of these escaping may cause signature validation errors because the HTTP
|
||||||
|
// request differs from the URI path or query that the signature was generated.
|
||||||
|
// https://golang.org/pkg/net/url/#URL.EscapedPath
|
||||||
|
//
|
||||||
|
// Because of this, it is recommended that when using the signer outside of the
|
||||||
|
// SDK that explicitly escaping the request prior to being signed is preferable,
|
||||||
|
// and will help prevent signature validation errors. This can be done by setting
|
||||||
|
// the URL.Opaque or URL.RawPath. The SDK will use URL.Opaque first and then
|
||||||
|
// call URL.EscapedPath() if Opaque is not set.
|
||||||
|
//
|
||||||
|
// Test `TestStandaloneSign` provides a complete example of using the signer
|
||||||
|
// outside of the SDK and pre-escaping the URI path.
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
v4Internal "github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4"
|
||||||
|
"github.com/aws/smithy-go/encoding/httpbinding"
|
||||||
|
"github.com/aws/smithy-go/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
signingAlgorithm = "AWS4-HMAC-SHA256"
|
||||||
|
authorizationHeader = "Authorization"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPSigner is an interface to a SigV4 signer that can sign HTTP requests
|
||||||
|
type HTTPSigner interface {
|
||||||
|
SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time, optFns ...func(*SignerOptions)) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyDerivator interface {
|
||||||
|
DeriveKey(credential aws.Credentials, service, region string, signingTime v4Internal.SigningTime) []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignerOptions is the SigV4 Signer options.
|
||||||
|
type SignerOptions struct {
|
||||||
|
// Disables the Signer's moving HTTP header key/value pairs from the HTTP
|
||||||
|
// request header to the request's query string. This is most commonly used
|
||||||
|
// with pre-signed requests preventing headers from being added to the
|
||||||
|
// request's query string.
|
||||||
|
DisableHeaderHoisting bool
|
||||||
|
|
||||||
|
// Disables the automatic escaping of the URI path of the request for the
|
||||||
|
// siganture's canonical string's path. For services that do not need additional
|
||||||
|
// escaping then use this to disable the signer escaping the path.
|
||||||
|
//
|
||||||
|
// S3 is an example of a service that does not need additional escaping.
|
||||||
|
//
|
||||||
|
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||||
|
DisableURIPathEscaping bool
|
||||||
|
|
||||||
|
// The logger to send log messages to.
|
||||||
|
Logger logging.Logger
|
||||||
|
|
||||||
|
// Enable logging of signed requests.
|
||||||
|
// This will enable logging of the canonical request, the string to sign, and for presigning the subsequent
|
||||||
|
// presigned URL.
|
||||||
|
LogSigning bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signer applies AWS v4 signing to given request. Use this to sign requests
|
||||||
|
// that need to be signed with AWS V4 Signatures.
|
||||||
|
type Signer struct {
|
||||||
|
options SignerOptions
|
||||||
|
keyDerivator keyDerivator
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSigner returns a new SigV4 Signer
|
||||||
|
func NewSigner(optFns ...func(signer *SignerOptions)) *Signer {
|
||||||
|
options := SignerOptions{}
|
||||||
|
|
||||||
|
for _, fn := range optFns {
|
||||||
|
fn(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Signer{options: options, keyDerivator: v4Internal.NewSigningKeyDeriver()}
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpSigner struct {
|
||||||
|
Request *http.Request
|
||||||
|
ServiceName string
|
||||||
|
Region string
|
||||||
|
Time v4Internal.SigningTime
|
||||||
|
Credentials aws.Credentials
|
||||||
|
KeyDerivator keyDerivator
|
||||||
|
IsPreSign bool
|
||||||
|
|
||||||
|
PayloadHash string
|
||||||
|
|
||||||
|
DisableHeaderHoisting bool
|
||||||
|
DisableURIPathEscaping bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *httpSigner) Build() (signedRequest, error) {
|
||||||
|
req := s.Request
|
||||||
|
|
||||||
|
query := req.URL.Query()
|
||||||
|
headers := req.Header
|
||||||
|
|
||||||
|
s.setRequiredSigningFields(headers, query)
|
||||||
|
|
||||||
|
// Sort Each Query Key's Values
|
||||||
|
for key := range query {
|
||||||
|
sort.Strings(query[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
v4Internal.SanitizeHostForHeader(req)
|
||||||
|
|
||||||
|
credentialScope := s.buildCredentialScope()
|
||||||
|
credentialStr := s.Credentials.AccessKeyID + "/" + credentialScope
|
||||||
|
if s.IsPreSign {
|
||||||
|
query.Set(v4Internal.AmzCredentialKey, credentialStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsignedHeaders := headers
|
||||||
|
if s.IsPreSign && !s.DisableHeaderHoisting {
|
||||||
|
var urlValues url.Values
|
||||||
|
urlValues, unsignedHeaders = buildQuery(v4Internal.AllowedQueryHoisting, headers)
|
||||||
|
for k := range urlValues {
|
||||||
|
query[k] = urlValues[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
host := req.URL.Host
|
||||||
|
if len(req.Host) > 0 {
|
||||||
|
host = req.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
signedHeaders, signedHeadersStr, canonicalHeaderStr := s.buildCanonicalHeaders(host, v4Internal.IgnoredHeaders, unsignedHeaders, s.Request.ContentLength)
|
||||||
|
|
||||||
|
if s.IsPreSign {
|
||||||
|
query.Set(v4Internal.AmzSignedHeadersKey, signedHeadersStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawQuery strings.Builder
|
||||||
|
rawQuery.WriteString(strings.Replace(query.Encode(), "+", "%20", -1))
|
||||||
|
|
||||||
|
canonicalURI := v4Internal.GetURIPath(req.URL)
|
||||||
|
if !s.DisableURIPathEscaping {
|
||||||
|
canonicalURI = httpbinding.EscapePath(canonicalURI, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
canonicalString := s.buildCanonicalString(
|
||||||
|
req.Method,
|
||||||
|
canonicalURI,
|
||||||
|
rawQuery.String(),
|
||||||
|
signedHeadersStr,
|
||||||
|
canonicalHeaderStr,
|
||||||
|
)
|
||||||
|
|
||||||
|
strToSign := s.buildStringToSign(credentialScope, canonicalString)
|
||||||
|
signingSignature, err := s.buildSignature(strToSign)
|
||||||
|
if err != nil {
|
||||||
|
return signedRequest{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.IsPreSign {
|
||||||
|
rawQuery.WriteString("&X-Amz-Signature=")
|
||||||
|
rawQuery.WriteString(signingSignature)
|
||||||
|
} else {
|
||||||
|
headers[authorizationHeader] = append(headers[authorizationHeader][:0], buildAuthorizationHeader(credentialStr, signedHeadersStr, signingSignature))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.URL.RawQuery = rawQuery.String()
|
||||||
|
|
||||||
|
return signedRequest{
|
||||||
|
Request: req,
|
||||||
|
SignedHeaders: signedHeaders,
|
||||||
|
CanonicalString: canonicalString,
|
||||||
|
StringToSign: strToSign,
|
||||||
|
PreSigned: s.IsPreSign,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAuthorizationHeader(credentialStr, signedHeadersStr, signingSignature string) string {
|
||||||
|
const credential = "Credential="
|
||||||
|
const signedHeaders = "SignedHeaders="
|
||||||
|
const signature = "Signature="
|
||||||
|
const commaSpace = ", "
|
||||||
|
|
||||||
|
var parts strings.Builder
|
||||||
|
parts.Grow(len(signingAlgorithm) + 1 +
|
||||||
|
len(credential) + len(credentialStr) + 2 +
|
||||||
|
len(signedHeaders) + len(signedHeadersStr) + 2 +
|
||||||
|
len(signature) + len(signingSignature),
|
||||||
|
)
|
||||||
|
parts.WriteString(signingAlgorithm)
|
||||||
|
parts.WriteRune(' ')
|
||||||
|
parts.WriteString(credential)
|
||||||
|
parts.WriteString(credentialStr)
|
||||||
|
parts.WriteString(commaSpace)
|
||||||
|
parts.WriteString(signedHeaders)
|
||||||
|
parts.WriteString(signedHeadersStr)
|
||||||
|
parts.WriteString(commaSpace)
|
||||||
|
parts.WriteString(signature)
|
||||||
|
parts.WriteString(signingSignature)
|
||||||
|
return parts.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignHTTP signs AWS v4 requests with the provided payload hash, service name, region the
|
||||||
|
// request is made to, and time the request is signed at. The signTime allows
|
||||||
|
// you to specify that a request is signed for the future, and cannot be
|
||||||
|
// used until then.
|
||||||
|
//
|
||||||
|
// The payloadHash is the hex encoded SHA-256 hash of the request payload, and
|
||||||
|
// must be provided. Even if the request has no payload (aka body). If the
|
||||||
|
// request has no payload you should use the hex encoded SHA-256 of an empty
|
||||||
|
// string as the payloadHash value.
|
||||||
|
//
|
||||||
|
// "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
//
|
||||||
|
// Some services such as Amazon S3 accept alternative values for the payload
|
||||||
|
// hash, such as "UNSIGNED-PAYLOAD" for requests where the body will not be
|
||||||
|
// included in the request signature.
|
||||||
|
//
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
|
||||||
|
//
|
||||||
|
// Sign differs from Presign in that it will sign the request using HTTP
|
||||||
|
// header values. This type of signing is intended for http.Request values that
|
||||||
|
// will not be shared, or are shared in a way the header values on the request
|
||||||
|
// will not be lost.
|
||||||
|
//
|
||||||
|
// The passed in request will be modified in place.
|
||||||
|
func (s Signer) SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time, optFns ...func(options *SignerOptions)) error {
|
||||||
|
options := s.options
|
||||||
|
|
||||||
|
for _, fn := range optFns {
|
||||||
|
fn(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := &httpSigner{
|
||||||
|
Request: r,
|
||||||
|
PayloadHash: payloadHash,
|
||||||
|
ServiceName: service,
|
||||||
|
Region: region,
|
||||||
|
Credentials: credentials,
|
||||||
|
Time: v4Internal.NewSigningTime(signingTime.UTC()),
|
||||||
|
DisableHeaderHoisting: options.DisableHeaderHoisting,
|
||||||
|
DisableURIPathEscaping: options.DisableURIPathEscaping,
|
||||||
|
KeyDerivator: s.keyDerivator,
|
||||||
|
}
|
||||||
|
|
||||||
|
signedRequest, err := signer.Build()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logSigningInfo(ctx, options, &signedRequest, false)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresignHTTP signs AWS v4 requests with the payload hash, service name, region
|
||||||
|
// the request is made to, and time the request is signed at. The signTime
|
||||||
|
// allows you to specify that a request is signed for the future, and cannot
|
||||||
|
// be used until then.
|
||||||
|
//
|
||||||
|
// Returns the signed URL and the map of HTTP headers that were included in the
|
||||||
|
// signature or an error if signing the request failed. For presigned requests
|
||||||
|
// these headers and their values must be included on the HTTP request when it
|
||||||
|
// is made. This is helpful to know what header values need to be shared with
|
||||||
|
// the party the presigned request will be distributed to.
|
||||||
|
//
|
||||||
|
// The payloadHash is the hex encoded SHA-256 hash of the request payload, and
|
||||||
|
// must be provided. Even if the request has no payload (aka body). If the
|
||||||
|
// request has no payload you should use the hex encoded SHA-256 of an empty
|
||||||
|
// string as the payloadHash value.
|
||||||
|
//
|
||||||
|
// "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
//
|
||||||
|
// Some services such as Amazon S3 accept alternative values for the payload
|
||||||
|
// hash, such as "UNSIGNED-PAYLOAD" for requests where the body will not be
|
||||||
|
// included in the request signature.
|
||||||
|
//
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
|
||||||
|
//
|
||||||
|
// PresignHTTP differs from SignHTTP in that it will sign the request using
|
||||||
|
// query string instead of header values. This allows you to share the
|
||||||
|
// Presigned Request's URL with third parties, or distribute it throughout your
|
||||||
|
// system with minimal dependencies.
|
||||||
|
//
|
||||||
|
// PresignHTTP will not set the expires time of the presigned request
|
||||||
|
// automatically. To specify the expire duration for a request add the
|
||||||
|
// "X-Amz-Expires" query parameter on the request with the value as the
|
||||||
|
// duration in seconds the presigned URL should be considered valid for. This
|
||||||
|
// parameter is not used by all AWS services, and is most notable used by
|
||||||
|
// Amazon S3 APIs.
|
||||||
|
//
|
||||||
|
// expires := 20 * time.Minute
|
||||||
|
// query := req.URL.Query()
|
||||||
|
// query.Set("X-Amz-Expires", strconv.FormatInt(int64(expires/time.Second), 10)
|
||||||
|
// req.URL.RawQuery = query.Encode()
|
||||||
|
//
|
||||||
|
// This method does not modify the provided request.
|
||||||
|
func (s *Signer) PresignHTTP(
|
||||||
|
ctx context.Context, credentials aws.Credentials, r *http.Request,
|
||||||
|
payloadHash string, service string, region string, signingTime time.Time,
|
||||||
|
optFns ...func(*SignerOptions),
|
||||||
|
) (signedURI string, signedHeaders http.Header, err error) {
|
||||||
|
options := s.options
|
||||||
|
|
||||||
|
for _, fn := range optFns {
|
||||||
|
fn(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := &httpSigner{
|
||||||
|
Request: r.Clone(r.Context()),
|
||||||
|
PayloadHash: payloadHash,
|
||||||
|
ServiceName: service,
|
||||||
|
Region: region,
|
||||||
|
Credentials: credentials,
|
||||||
|
Time: v4Internal.NewSigningTime(signingTime.UTC()),
|
||||||
|
IsPreSign: true,
|
||||||
|
DisableHeaderHoisting: options.DisableHeaderHoisting,
|
||||||
|
DisableURIPathEscaping: options.DisableURIPathEscaping,
|
||||||
|
KeyDerivator: s.keyDerivator,
|
||||||
|
}
|
||||||
|
|
||||||
|
signedRequest, err := signer.Build()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logSigningInfo(ctx, options, &signedRequest, true)
|
||||||
|
|
||||||
|
signedHeaders = make(http.Header)
|
||||||
|
|
||||||
|
// For the signed headers we canonicalize the header keys in the returned map.
|
||||||
|
// This avoids situations where can standard library double headers like host header. For example the standard
|
||||||
|
// library will set the Host header, even if it is present in lower-case form.
|
||||||
|
for k, v := range signedRequest.SignedHeaders {
|
||||||
|
key := textproto.CanonicalMIMEHeaderKey(k)
|
||||||
|
signedHeaders[key] = append(signedHeaders[key], v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedRequest.Request.URL.String(), signedHeaders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *httpSigner) buildCredentialScope() string {
|
||||||
|
return strings.Join([]string{
|
||||||
|
s.Time.ShortTimeFormat(),
|
||||||
|
s.Region,
|
||||||
|
s.ServiceName,
|
||||||
|
"aws4_request",
|
||||||
|
}, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildQuery(r v4Internal.Rule, header http.Header) (url.Values, http.Header) {
|
||||||
|
query := url.Values{}
|
||||||
|
unsignedHeaders := http.Header{}
|
||||||
|
for k, h := range header {
|
||||||
|
if r.IsValid(k) {
|
||||||
|
query[k] = h
|
||||||
|
} else {
|
||||||
|
unsignedHeaders[k] = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return query, unsignedHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *httpSigner) buildCanonicalHeaders(host string, rule v4Internal.Rule, header http.Header, length int64) (signed http.Header, signedHeaders, canonicalHeadersStr string) {
|
||||||
|
signed = make(http.Header)
|
||||||
|
|
||||||
|
var headers []string
|
||||||
|
const hostHeader = "host"
|
||||||
|
headers = append(headers, hostHeader)
|
||||||
|
signed[hostHeader] = append(signed[hostHeader], host)
|
||||||
|
|
||||||
|
if length > 0 {
|
||||||
|
const contentLengthHeader = "content-length"
|
||||||
|
headers = append(headers, contentLengthHeader)
|
||||||
|
signed[contentLengthHeader] = append(signed[contentLengthHeader], strconv.FormatInt(length, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range header {
|
||||||
|
if !rule.IsValid(k) {
|
||||||
|
continue // ignored header
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerCaseKey := strings.ToLower(k)
|
||||||
|
if _, ok := signed[lowerCaseKey]; ok {
|
||||||
|
// include additional values
|
||||||
|
signed[lowerCaseKey] = append(signed[lowerCaseKey], v...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = append(headers, lowerCaseKey)
|
||||||
|
signed[lowerCaseKey] = v
|
||||||
|
}
|
||||||
|
sort.Strings(headers)
|
||||||
|
|
||||||
|
signedHeaders = strings.Join(headers, ";")
|
||||||
|
|
||||||
|
var canonicalHeaders strings.Builder
|
||||||
|
n := len(headers)
|
||||||
|
const colon = ':'
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if headers[i] == hostHeader {
|
||||||
|
canonicalHeaders.WriteString(hostHeader)
|
||||||
|
canonicalHeaders.WriteRune(colon)
|
||||||
|
canonicalHeaders.WriteString(v4Internal.StripExcessSpaces(host))
|
||||||
|
} else {
|
||||||
|
canonicalHeaders.WriteString(headers[i])
|
||||||
|
canonicalHeaders.WriteRune(colon)
|
||||||
|
canonicalHeaders.WriteString(strings.Join(signed[headers[i]], ","))
|
||||||
|
}
|
||||||
|
canonicalHeaders.WriteRune('\n')
|
||||||
|
}
|
||||||
|
canonicalHeadersStr = canonicalHeaders.String()
|
||||||
|
|
||||||
|
return signed, signedHeaders, canonicalHeadersStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *httpSigner) buildCanonicalString(method, uri, query, signedHeaders, canonicalHeaders string) string {
|
||||||
|
return strings.Join([]string{
|
||||||
|
method,
|
||||||
|
uri,
|
||||||
|
query,
|
||||||
|
canonicalHeaders,
|
||||||
|
signedHeaders,
|
||||||
|
s.PayloadHash,
|
||||||
|
}, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *httpSigner) buildStringToSign(credentialScope, canonicalRequestString string) string {
|
||||||
|
return strings.Join([]string{
|
||||||
|
signingAlgorithm,
|
||||||
|
s.Time.TimeFormat(),
|
||||||
|
credentialScope,
|
||||||
|
hex.EncodeToString(makeHash(sha256.New(), []byte(canonicalRequestString))),
|
||||||
|
}, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeHash(hash hash.Hash, b []byte) []byte {
|
||||||
|
hash.Reset()
|
||||||
|
hash.Write(b)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *httpSigner) buildSignature(strToSign string) (string, error) {
|
||||||
|
key := s.KeyDerivator.DeriveKey(s.Credentials, s.ServiceName, s.Region, s.Time)
|
||||||
|
return hex.EncodeToString(v4Internal.HMACSHA256(key, []byte(strToSign))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *httpSigner) setRequiredSigningFields(headers http.Header, query url.Values) {
|
||||||
|
amzDate := s.Time.TimeFormat()
|
||||||
|
|
||||||
|
if s.IsPreSign {
|
||||||
|
query.Set(v4Internal.AmzAlgorithmKey, signingAlgorithm)
|
||||||
|
if sessionToken := s.Credentials.SessionToken; len(sessionToken) > 0 {
|
||||||
|
query.Set("X-Amz-Security-Token", sessionToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Set(v4Internal.AmzDateKey, amzDate)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
headers[v4Internal.AmzDateKey] = append(headers[v4Internal.AmzDateKey][:0], amzDate)
|
||||||
|
|
||||||
|
if len(s.Credentials.SessionToken) > 0 {
|
||||||
|
headers[v4Internal.AmzSecurityTokenKey] = append(headers[v4Internal.AmzSecurityTokenKey][:0], s.Credentials.SessionToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logSigningInfo(ctx context.Context, options SignerOptions, request *signedRequest, isPresign bool) {
|
||||||
|
if !options.LogSigning {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signedURLMsg := ""
|
||||||
|
if isPresign {
|
||||||
|
signedURLMsg = fmt.Sprintf(logSignedURLMsg, request.Request.URL.String())
|
||||||
|
}
|
||||||
|
logger := logging.WithContext(ctx, options.Logger)
|
||||||
|
logger.Logf(logging.Debug, logSignInfoMsg, request.CanonicalString, request.StringToSign, signedURLMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
type signedRequest struct {
|
||||||
|
Request *http.Request
|
||||||
|
SignedHeaders http.Header
|
||||||
|
CanonicalString string
|
||||||
|
StringToSign string
|
||||||
|
PreSigned bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const logSignInfoMsg = `Request Signature:
|
||||||
|
---[ CANONICAL STRING ]-----------------------------
|
||||||
|
%s
|
||||||
|
---[ STRING TO SIGN ]--------------------------------
|
||||||
|
%s%s
|
||||||
|
-----------------------------------------------------`
|
||||||
|
const logSignedURLMsg = `
|
||||||
|
---[ SIGNED URL ]------------------------------------
|
||||||
|
%s`
|
|
@ -0,0 +1,280 @@
|
||||||
|
// Code generated by aws/generate.go DO NOT EDIT.
|
||||||
|
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/smithy-go/ptr"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bool returns a pointer value for the bool value passed in.
|
||||||
|
func Bool(v bool) *bool {
|
||||||
|
return ptr.Bool(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolSlice returns a slice of bool pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func BoolSlice(vs []bool) []*bool {
|
||||||
|
return ptr.BoolSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolMap returns a map of bool pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func BoolMap(vs map[string]bool) map[string]*bool {
|
||||||
|
return ptr.BoolMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Byte returns a pointer value for the byte value passed in.
|
||||||
|
func Byte(v byte) *byte {
|
||||||
|
return ptr.Byte(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteSlice returns a slice of byte pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func ByteSlice(vs []byte) []*byte {
|
||||||
|
return ptr.ByteSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteMap returns a map of byte pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func ByteMap(vs map[string]byte) map[string]*byte {
|
||||||
|
return ptr.ByteMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a pointer value for the string value passed in.
|
||||||
|
func String(v string) *string {
|
||||||
|
return ptr.String(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSlice returns a slice of string pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func StringSlice(vs []string) []*string {
|
||||||
|
return ptr.StringSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringMap returns a map of string pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func StringMap(vs map[string]string) map[string]*string {
|
||||||
|
return ptr.StringMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns a pointer value for the int value passed in.
|
||||||
|
func Int(v int) *int {
|
||||||
|
return ptr.Int(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntSlice returns a slice of int pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func IntSlice(vs []int) []*int {
|
||||||
|
return ptr.IntSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntMap returns a map of int pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func IntMap(vs map[string]int) map[string]*int {
|
||||||
|
return ptr.IntMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8 returns a pointer value for the int8 value passed in.
|
||||||
|
func Int8(v int8) *int8 {
|
||||||
|
return ptr.Int8(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8Slice returns a slice of int8 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Int8Slice(vs []int8) []*int8 {
|
||||||
|
return ptr.Int8Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8Map returns a map of int8 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Int8Map(vs map[string]int8) map[string]*int8 {
|
||||||
|
return ptr.Int8Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16 returns a pointer value for the int16 value passed in.
|
||||||
|
func Int16(v int16) *int16 {
|
||||||
|
return ptr.Int16(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16Slice returns a slice of int16 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Int16Slice(vs []int16) []*int16 {
|
||||||
|
return ptr.Int16Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16Map returns a map of int16 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Int16Map(vs map[string]int16) map[string]*int16 {
|
||||||
|
return ptr.Int16Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 returns a pointer value for the int32 value passed in.
|
||||||
|
func Int32(v int32) *int32 {
|
||||||
|
return ptr.Int32(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32Slice returns a slice of int32 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Int32Slice(vs []int32) []*int32 {
|
||||||
|
return ptr.Int32Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32Map returns a map of int32 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Int32Map(vs map[string]int32) map[string]*int32 {
|
||||||
|
return ptr.Int32Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns a pointer value for the int64 value passed in.
|
||||||
|
func Int64(v int64) *int64 {
|
||||||
|
return ptr.Int64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Slice returns a slice of int64 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Int64Slice(vs []int64) []*int64 {
|
||||||
|
return ptr.Int64Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Map returns a map of int64 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Int64Map(vs map[string]int64) map[string]*int64 {
|
||||||
|
return ptr.Int64Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint returns a pointer value for the uint value passed in.
|
||||||
|
func Uint(v uint) *uint {
|
||||||
|
return ptr.Uint(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UintSlice returns a slice of uint pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func UintSlice(vs []uint) []*uint {
|
||||||
|
return ptr.UintSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UintMap returns a map of uint pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func UintMap(vs map[string]uint) map[string]*uint {
|
||||||
|
return ptr.UintMap(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8 returns a pointer value for the uint8 value passed in.
|
||||||
|
func Uint8(v uint8) *uint8 {
|
||||||
|
return ptr.Uint8(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8Slice returns a slice of uint8 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Uint8Slice(vs []uint8) []*uint8 {
|
||||||
|
return ptr.Uint8Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8Map returns a map of uint8 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Uint8Map(vs map[string]uint8) map[string]*uint8 {
|
||||||
|
return ptr.Uint8Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16 returns a pointer value for the uint16 value passed in.
|
||||||
|
func Uint16(v uint16) *uint16 {
|
||||||
|
return ptr.Uint16(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16Slice returns a slice of uint16 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Uint16Slice(vs []uint16) []*uint16 {
|
||||||
|
return ptr.Uint16Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16Map returns a map of uint16 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Uint16Map(vs map[string]uint16) map[string]*uint16 {
|
||||||
|
return ptr.Uint16Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 returns a pointer value for the uint32 value passed in.
|
||||||
|
func Uint32(v uint32) *uint32 {
|
||||||
|
return ptr.Uint32(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32Slice returns a slice of uint32 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Uint32Slice(vs []uint32) []*uint32 {
|
||||||
|
return ptr.Uint32Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32Map returns a map of uint32 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Uint32Map(vs map[string]uint32) map[string]*uint32 {
|
||||||
|
return ptr.Uint32Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 returns a pointer value for the uint64 value passed in.
|
||||||
|
func Uint64(v uint64) *uint64 {
|
||||||
|
return ptr.Uint64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64Slice returns a slice of uint64 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Uint64Slice(vs []uint64) []*uint64 {
|
||||||
|
return ptr.Uint64Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64Map returns a map of uint64 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Uint64Map(vs map[string]uint64) map[string]*uint64 {
|
||||||
|
return ptr.Uint64Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32 returns a pointer value for the float32 value passed in.
|
||||||
|
func Float32(v float32) *float32 {
|
||||||
|
return ptr.Float32(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32Slice returns a slice of float32 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Float32Slice(vs []float32) []*float32 {
|
||||||
|
return ptr.Float32Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32Map returns a map of float32 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Float32Map(vs map[string]float32) map[string]*float32 {
|
||||||
|
return ptr.Float32Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 returns a pointer value for the float64 value passed in.
|
||||||
|
func Float64(v float64) *float64 {
|
||||||
|
return ptr.Float64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Slice returns a slice of float64 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Float64Slice(vs []float64) []*float64 {
|
||||||
|
return ptr.Float64Slice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Map returns a map of float64 pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func Float64Map(vs map[string]float64) map[string]*float64 {
|
||||||
|
return ptr.Float64Map(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns a pointer value for the time.Time value passed in.
|
||||||
|
func Time(v time.Time) *time.Time {
|
||||||
|
return ptr.Time(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeSlice returns a slice of time.Time pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func TimeSlice(vs []time.Time) []*time.Time {
|
||||||
|
return ptr.TimeSlice(vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeMap returns a map of time.Time pointers from the values
|
||||||
|
// passed in.
|
||||||
|
func TimeMap(vs map[string]time.Time) map[string]*time.Time {
|
||||||
|
return ptr.TimeMap(vs)
|
||||||
|
}
|
|
@ -0,0 +1,301 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defaults for the HTTPTransportBuilder.
|
||||||
|
var (
|
||||||
|
// Default connection pool options
|
||||||
|
DefaultHTTPTransportMaxIdleConns = 100
|
||||||
|
DefaultHTTPTransportMaxIdleConnsPerHost = 10
|
||||||
|
|
||||||
|
// Default connection timeouts
|
||||||
|
DefaultHTTPTransportIdleConnTimeout = 90 * time.Second
|
||||||
|
DefaultHTTPTransportTLSHandleshakeTimeout = 10 * time.Second
|
||||||
|
DefaultHTTPTransportExpectContinueTimeout = 1 * time.Second
|
||||||
|
|
||||||
|
// Default to TLS 1.2 for all HTTPS requests.
|
||||||
|
DefaultHTTPTransportTLSMinVersion uint16 = tls.VersionTLS12
|
||||||
|
)
|
||||||
|
|
||||||
|
// Timeouts for net.Dialer's network connection.
|
||||||
|
var (
|
||||||
|
DefaultDialConnectTimeout = 30 * time.Second
|
||||||
|
DefaultDialKeepAliveTimeout = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildableClient provides a HTTPClient implementation with options to
|
||||||
|
// create copies of the HTTPClient when additional configuration is provided.
|
||||||
|
//
|
||||||
|
// The client's methods will not share the http.Transport value between copies
|
||||||
|
// of the BuildableClient. Only exported member values of the Transport and
|
||||||
|
// optional Dialer will be copied between copies of BuildableClient.
|
||||||
|
type BuildableClient struct {
|
||||||
|
transport *http.Transport
|
||||||
|
dialer *net.Dialer
|
||||||
|
|
||||||
|
initOnce sync.Once
|
||||||
|
|
||||||
|
clientTimeout time.Duration
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuildableClient returns an initialized client for invoking HTTP
|
||||||
|
// requests.
|
||||||
|
func NewBuildableClient() *BuildableClient {
|
||||||
|
return &BuildableClient{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do implements the HTTPClient interface's Do method to invoke a HTTP request,
|
||||||
|
// and receive the response. Uses the BuildableClient's current
|
||||||
|
// configuration to invoke the http.Request.
|
||||||
|
//
|
||||||
|
// If connection pooling is enabled (aka HTTP KeepAlive) the client will only
|
||||||
|
// share pooled connections with its own instance. Copies of the
|
||||||
|
// BuildableClient will have their own connection pools.
|
||||||
|
//
|
||||||
|
// Redirect (3xx) responses will not be followed, the HTTP response received
|
||||||
|
// will returned instead.
|
||||||
|
func (b *BuildableClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
b.initOnce.Do(b.build)
|
||||||
|
|
||||||
|
return b.client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BuildableClient) build() {
|
||||||
|
b.client = wrapWithLimitedRedirect(&http.Client{
|
||||||
|
Timeout: b.clientTimeout,
|
||||||
|
Transport: b.GetTransport(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BuildableClient) clone() *BuildableClient {
|
||||||
|
cpy := NewBuildableClient()
|
||||||
|
cpy.transport = b.GetTransport()
|
||||||
|
cpy.dialer = b.GetDialer()
|
||||||
|
cpy.clientTimeout = b.clientTimeout
|
||||||
|
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTransportOptions copies the BuildableClient and returns it with the
|
||||||
|
// http.Transport options applied.
|
||||||
|
//
|
||||||
|
// If a non (*http.Transport) was set as the round tripper, the round tripper
|
||||||
|
// will be replaced with a default Transport value before invoking the option
|
||||||
|
// functions.
|
||||||
|
func (b *BuildableClient) WithTransportOptions(opts ...func(*http.Transport)) *BuildableClient {
|
||||||
|
cpy := b.clone()
|
||||||
|
|
||||||
|
tr := cpy.GetTransport()
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(tr)
|
||||||
|
}
|
||||||
|
cpy.transport = tr
|
||||||
|
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDialerOptions copies the BuildableClient and returns it with the
|
||||||
|
// net.Dialer options applied. Will set the client's http.Transport DialContext
|
||||||
|
// member.
|
||||||
|
func (b *BuildableClient) WithDialerOptions(opts ...func(*net.Dialer)) *BuildableClient {
|
||||||
|
cpy := b.clone()
|
||||||
|
|
||||||
|
dialer := cpy.GetDialer()
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(dialer)
|
||||||
|
}
|
||||||
|
cpy.dialer = dialer
|
||||||
|
|
||||||
|
tr := cpy.GetTransport()
|
||||||
|
tr.DialContext = cpy.dialer.DialContext
|
||||||
|
cpy.transport = tr
|
||||||
|
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout Sets the timeout used by the client for all requests.
|
||||||
|
func (b *BuildableClient) WithTimeout(timeout time.Duration) *BuildableClient {
|
||||||
|
cpy := b.clone()
|
||||||
|
cpy.clientTimeout = timeout
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransport returns a copy of the client's HTTP Transport.
|
||||||
|
func (b *BuildableClient) GetTransport() *http.Transport {
|
||||||
|
var tr *http.Transport
|
||||||
|
if b.transport != nil {
|
||||||
|
tr = b.transport.Clone()
|
||||||
|
} else {
|
||||||
|
tr = defaultHTTPTransport()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDialer returns a copy of the client's network dialer.
|
||||||
|
func (b *BuildableClient) GetDialer() *net.Dialer {
|
||||||
|
var dialer *net.Dialer
|
||||||
|
if b.dialer != nil {
|
||||||
|
dialer = shallowCopyStruct(b.dialer).(*net.Dialer)
|
||||||
|
} else {
|
||||||
|
dialer = defaultDialer()
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTimeout returns a copy of the client's timeout to cancel requests with.
|
||||||
|
func (b *BuildableClient) GetTimeout() time.Duration {
|
||||||
|
return b.clientTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultDialer() *net.Dialer {
|
||||||
|
return &net.Dialer{
|
||||||
|
Timeout: DefaultDialConnectTimeout,
|
||||||
|
KeepAlive: DefaultDialKeepAliveTimeout,
|
||||||
|
DualStack: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultHTTPTransport() *http.Transport {
|
||||||
|
dialer := defaultDialer()
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: dialer.DialContext,
|
||||||
|
TLSHandshakeTimeout: DefaultHTTPTransportTLSHandleshakeTimeout,
|
||||||
|
MaxIdleConns: DefaultHTTPTransportMaxIdleConns,
|
||||||
|
MaxIdleConnsPerHost: DefaultHTTPTransportMaxIdleConnsPerHost,
|
||||||
|
IdleConnTimeout: DefaultHTTPTransportIdleConnTimeout,
|
||||||
|
ExpectContinueTimeout: DefaultHTTPTransportExpectContinueTimeout,
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
MinVersion: DefaultHTTPTransportTLSMinVersion,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
// shallowCopyStruct creates a shallow copy of the passed in source struct, and
|
||||||
|
// returns that copy of the same struct type.
|
||||||
|
func shallowCopyStruct(src interface{}) interface{} {
|
||||||
|
srcVal := reflect.ValueOf(src)
|
||||||
|
srcValType := srcVal.Type()
|
||||||
|
|
||||||
|
var returnAsPtr bool
|
||||||
|
if srcValType.Kind() == reflect.Ptr {
|
||||||
|
srcVal = srcVal.Elem()
|
||||||
|
srcValType = srcValType.Elem()
|
||||||
|
returnAsPtr = true
|
||||||
|
}
|
||||||
|
dstVal := reflect.New(srcValType).Elem()
|
||||||
|
|
||||||
|
for i := 0; i < srcValType.NumField(); i++ {
|
||||||
|
ft := srcValType.Field(i)
|
||||||
|
if len(ft.PkgPath) != 0 {
|
||||||
|
// unexported fields have a PkgPath
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dstVal.Field(i).Set(srcVal.Field(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
if returnAsPtr {
|
||||||
|
dstVal = dstVal.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
return dstVal.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapWithLimitedRedirect updates the Client's Transport and CheckRedirect to
|
||||||
|
// not follow any redirect other than 307 and 308. No other redirect will be
|
||||||
|
// followed.
|
||||||
|
//
|
||||||
|
// If the client does not have a Transport defined will use a new SDK default
|
||||||
|
// http.Transport configuration.
|
||||||
|
func wrapWithLimitedRedirect(c *http.Client) *http.Client {
|
||||||
|
tr := c.Transport
|
||||||
|
if tr == nil {
|
||||||
|
tr = defaultHTTPTransport()
|
||||||
|
}
|
||||||
|
|
||||||
|
cc := *c
|
||||||
|
cc.CheckRedirect = limitedRedirect
|
||||||
|
cc.Transport = suppressBadHTTPRedirectTransport{
|
||||||
|
tr: tr,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cc
|
||||||
|
}
|
||||||
|
|
||||||
|
// limitedRedirect is a CheckRedirect that prevents the client from following
|
||||||
|
// any non 307/308 HTTP status code redirects.
|
||||||
|
//
|
||||||
|
// The 307 and 308 redirects are allowed because the client must use the
|
||||||
|
// original HTTP method for the redirected to location. Whereas 301 and 302
|
||||||
|
// allow the client to switch to GET for the redirect.
|
||||||
|
//
|
||||||
|
// Suppresses all redirect requests with a URL of badHTTPRedirectLocation.
|
||||||
|
func limitedRedirect(r *http.Request, via []*http.Request) error {
|
||||||
|
// Request.Response, in CheckRedirect is the response that is triggering
|
||||||
|
// the redirect.
|
||||||
|
resp := r.Response
|
||||||
|
if r.URL.String() == badHTTPRedirectLocation {
|
||||||
|
resp.Header.Del(badHTTPRedirectLocation)
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case 307, 308:
|
||||||
|
// Only allow 307 and 308 redirects as they preserve the method.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// suppressBadHTTPRedirectTransport provides an http.RoundTripper
|
||||||
|
// implementation that wraps another http.RoundTripper to prevent HTTP client
|
||||||
|
// receiving 301 and 302 HTTP responses redirects without the required location
|
||||||
|
// header.
|
||||||
|
//
|
||||||
|
// Clients using this utility must have a CheckRedirect, e.g. limitedRedirect,
|
||||||
|
// that check for responses with having a URL of baseHTTPRedirectLocation, and
|
||||||
|
// suppress the redirect.
|
||||||
|
type suppressBadHTTPRedirectTransport struct {
|
||||||
|
tr http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
const badHTTPRedirectLocation = `https://amazonaws.com/badhttpredirectlocation`
|
||||||
|
|
||||||
|
// RoundTrip backfills a stub location when a 301/302 response is received
|
||||||
|
// without a location. This stub location is used by limitedRedirect to prevent
|
||||||
|
// the HTTP client from failing attempting to use follow a redirect without a
|
||||||
|
// location value.
|
||||||
|
func (t suppressBadHTTPRedirectTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := t.tr.RoundTrip(r)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// S3 is the only known service to return 301 without location header.
|
||||||
|
// The Go standard library HTTP client will return an opaque error if it
|
||||||
|
// tries to follow a 301/302 response missing the location header.
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case 301, 302:
|
||||||
|
if v := resp.Header.Get("Location"); len(v) == 0 {
|
||||||
|
resp.Header.Set("Location", badHTTPRedirectLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
42
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/content_type.go
generated
vendored
Normal file
42
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/content_type.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// removeContentTypeHeader is a build middleware that removes
|
||||||
|
// content type header if content-length header is unset or
|
||||||
|
// is set to zero,
|
||||||
|
type removeContentTypeHeader struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID the name of the middleware.
|
||||||
|
func (m *removeContentTypeHeader) ID() string {
|
||||||
|
return "RemoveContentTypeHeader"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleBuild adds or appends the constructed user agent to the request.
|
||||||
|
func (m *removeContentTypeHeader) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (
|
||||||
|
out middleware.BuildOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
req, ok := in.Request.(*smithyhttp.Request)
|
||||||
|
if !ok {
|
||||||
|
return out, metadata, fmt.Errorf("unknown transport type %T", in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove contentTypeHeader when content-length is zero
|
||||||
|
if req.ContentLength == 0 {
|
||||||
|
req.Header.Del("content-type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return next.HandleBuild(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveContentTypeHeader removes content-type header if
|
||||||
|
// content length is unset or equal to zero.
|
||||||
|
func RemoveContentTypeHeader(stack *middleware.Stack) error {
|
||||||
|
return stack.Build.Add(&removeContentTypeHeader{}, middleware.After)
|
||||||
|
}
|
33
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/response_error.go
generated
vendored
Normal file
33
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/response_error.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseError provides the HTTP centric error type wrapping the underlying error
|
||||||
|
// with the HTTP response value and the deserialized RequestID.
|
||||||
|
type ResponseError struct {
|
||||||
|
*smithyhttp.ResponseError
|
||||||
|
|
||||||
|
// RequestID associated with response error
|
||||||
|
RequestID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceRequestID returns the request id associated with Response Error
|
||||||
|
func (e *ResponseError) ServiceRequestID() string { return e.RequestID }
|
||||||
|
|
||||||
|
// Error returns the formatted error
|
||||||
|
func (e *ResponseError) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"https response error StatusCode: %d, RequestID: %s, %v",
|
||||||
|
e.Response.StatusCode, e.RequestID, e.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// As populates target and returns true if the type of target is a error type that
|
||||||
|
// the ResponseError embeds, (e.g.AWS HTTP ResponseError)
|
||||||
|
func (e *ResponseError) As(target interface{}) bool {
|
||||||
|
return errors.As(e.ResponseError, target)
|
||||||
|
}
|
54
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/response_error_middleware.go
generated
vendored
Normal file
54
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/response_error_middleware.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddResponseErrorMiddleware adds response error wrapper middleware
|
||||||
|
func AddResponseErrorMiddleware(stack *middleware.Stack) error {
|
||||||
|
// add error wrapper middleware before request id retriever middleware so that it can wrap the error response
|
||||||
|
// returned by operation deserializers
|
||||||
|
return stack.Deserialize.Insert(&responseErrorWrapper{}, "RequestIDRetriever", middleware.Before)
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseErrorWrapper struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the middleware identifier
|
||||||
|
func (m *responseErrorWrapper) ID() string {
|
||||||
|
return "ResponseErrorWrapper"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *responseErrorWrapper) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) (
|
||||||
|
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
out, metadata, err = next.HandleDeserialize(ctx, in)
|
||||||
|
if err == nil {
|
||||||
|
// Nothing to do when there is no error.
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, ok := out.RawResponse.(*smithyhttp.Response)
|
||||||
|
if !ok {
|
||||||
|
// No raw response to wrap with.
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// look for request id in metadata
|
||||||
|
reqID, _ := awsmiddleware.GetRequestIDMetadata(metadata)
|
||||||
|
|
||||||
|
// Wrap the returned smithy error with the request id retrieved from the metadata
|
||||||
|
err = &ResponseError{
|
||||||
|
ResponseError: &smithyhttp.ResponseError{
|
||||||
|
Response: resp,
|
||||||
|
Err: err,
|
||||||
|
},
|
||||||
|
RequestID: reqID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
104
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/timeout_read_closer.go
generated
vendored
Normal file
104
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/timeout_read_closer.go
generated
vendored
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/smithy-go"
|
||||||
|
"github.com/aws/smithy-go/middleware"
|
||||||
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type readResult struct {
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseTimeoutError is an error when the reads from the response are
|
||||||
|
// delayed longer than the timeout the read was configured for.
|
||||||
|
type ResponseTimeoutError struct {
|
||||||
|
TimeoutDur time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout returns that the error is was caused by a timeout, and can be
|
||||||
|
// retried.
|
||||||
|
func (*ResponseTimeoutError) Timeout() bool { return true }
|
||||||
|
|
||||||
|
func (e *ResponseTimeoutError) Error() string {
|
||||||
|
return fmt.Sprintf("read on body reach timeout limit, %v", e.TimeoutDur)
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeoutReadCloser will handle body reads that take too long.
|
||||||
|
// We will return a ErrReadTimeout error if a timeout occurs.
|
||||||
|
type timeoutReadCloser struct {
|
||||||
|
reader io.ReadCloser
|
||||||
|
duration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read will spin off a goroutine to call the reader's Read method. We will
|
||||||
|
// select on the timer's channel or the read's channel. Whoever completes first
|
||||||
|
// will be returned.
|
||||||
|
func (r *timeoutReadCloser) Read(b []byte) (int, error) {
|
||||||
|
timer := time.NewTimer(r.duration)
|
||||||
|
c := make(chan readResult, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
n, err := r.reader.Read(b)
|
||||||
|
timer.Stop()
|
||||||
|
c <- readResult{n: n, err: err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case data := <-c:
|
||||||
|
return data.n, data.err
|
||||||
|
case <-timer.C:
|
||||||
|
return 0, &ResponseTimeoutError{TimeoutDur: r.duration}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *timeoutReadCloser) Close() error {
|
||||||
|
return r.reader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddResponseReadTimeoutMiddleware adds a middleware to the stack that wraps the
|
||||||
|
// response body so that a read that takes too long will return an error.
|
||||||
|
func AddResponseReadTimeoutMiddleware(stack *middleware.Stack, duration time.Duration) error {
|
||||||
|
return stack.Deserialize.Add(&readTimeout{duration: duration}, middleware.After)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTimeout wraps the response body with a timeoutReadCloser
|
||||||
|
type readTimeout struct {
|
||||||
|
duration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the id of the middleware
|
||||||
|
func (*readTimeout) ID() string {
|
||||||
|
return "ReadResponseTimeout"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleDeserialize implements the DeserializeMiddleware interface
|
||||||
|
func (m *readTimeout) HandleDeserialize(
|
||||||
|
ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler,
|
||||||
|
) (
|
||||||
|
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
|
||||||
|
) {
|
||||||
|
out, metadata, err = next.HandleDeserialize(ctx, in)
|
||||||
|
if err != nil {
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, ok := out.RawResponse.(*smithyhttp.Response)
|
||||||
|
if !ok {
|
||||||
|
return out, metadata, &smithy.DeserializationError{Err: fmt.Errorf("unknown transport type %T", out.RawResponse)}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Body = &timeoutReadCloser{
|
||||||
|
reader: response.Body,
|
||||||
|
duration: m.duration,
|
||||||
|
}
|
||||||
|
out.RawResponse = response
|
||||||
|
|
||||||
|
return out, metadata, err
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package aws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ternary is an enum allowing an unknown or none state in addition to a bool's
|
||||||
|
// true and false.
|
||||||
|
type Ternary int
|
||||||
|
|
||||||
|
func (t Ternary) String() string {
|
||||||
|
switch t {
|
||||||
|
case UnknownTernary:
|
||||||
|
return "unknown"
|
||||||
|
case FalseTernary:
|
||||||
|
return "false"
|
||||||
|
case TrueTernary:
|
||||||
|
return "true"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown value, %d", int(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns true if the value is TrueTernary, false otherwise.
|
||||||
|
func (t Ternary) Bool() bool {
|
||||||
|
return t == TrueTernary
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enumerations for the values of the Ternary type.
|
||||||
|
const (
|
||||||
|
UnknownTernary Ternary = iota
|
||||||
|
FalseTernary
|
||||||
|
TrueTernary
|
||||||
|
)
|
||||||
|
|
||||||
|
// BoolTernary returns a true or false Ternary value for the bool provided.
|
||||||
|
func BoolTernary(v bool) Ternary {
|
||||||
|
if v {
|
||||||
|
return TrueTernary
|
||||||
|
}
|
||||||
|
return FalseTernary
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Package aws provides core functionality for making requests to AWS services.
|
||||||
|
package aws
|
||||||
|
|
||||||
|
// SDKName is the name of this AWS SDK
|
||||||
|
const SDKName = "aws-sdk-go-v2"
|
||||||
|
|
||||||
|
// SDKVersion is the version of this SDK
|
||||||
|
const SDKVersion = "1.2.1"
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
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,190 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultLoaders are a slice of functions that will read external configuration
|
||||||
|
// sources for configuration values. These values are read by the AWSConfigResolvers
|
||||||
|
// using interfaces to extract specific information from the external configuration.
|
||||||
|
var defaultLoaders = []loader{
|
||||||
|
loadEnvConfig,
|
||||||
|
loadSharedConfigIgnoreNotExist,
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultAWSConfigResolvers are a slice of functions that will resolve external
|
||||||
|
// configuration values into AWS configuration values.
|
||||||
|
//
|
||||||
|
// This will setup the AWS configuration's Region,
|
||||||
|
var defaultAWSConfigResolvers = []awsConfigResolver{
|
||||||
|
// Resolves the default configuration the SDK's aws.Config will be
|
||||||
|
// initialized with.
|
||||||
|
resolveDefaultAWSConfig,
|
||||||
|
|
||||||
|
// Sets the logger to be used. Could be user provided logger, and client
|
||||||
|
// logging mode.
|
||||||
|
resolveLogger,
|
||||||
|
resolveClientLogMode,
|
||||||
|
|
||||||
|
// Sets the HTTP client and configuration to use for making requests using
|
||||||
|
// the HTTP transport.
|
||||||
|
resolveHTTPClient,
|
||||||
|
resolveCustomCABundle,
|
||||||
|
|
||||||
|
// Sets the endpoint resolving behavior the API Clients will use for making
|
||||||
|
// requests to. Clients default to their own clients this allows overrides
|
||||||
|
// to be specified.
|
||||||
|
resolveEndpointResolver,
|
||||||
|
|
||||||
|
// Sets the retry behavior API clients will use within their retry attempt
|
||||||
|
// middleware. Defaults to unset, allowing API clients to define their own
|
||||||
|
// retry behavior.
|
||||||
|
resolveRetryer,
|
||||||
|
|
||||||
|
// Sets the region the API Clients should use for making requests to.
|
||||||
|
resolveRegion,
|
||||||
|
resolveEC2IMDSRegion,
|
||||||
|
resolveDefaultRegion,
|
||||||
|
|
||||||
|
// Sets the additional set of middleware stack mutators that will custom
|
||||||
|
// API client request pipeline middleware.
|
||||||
|
resolveAPIOptions,
|
||||||
|
|
||||||
|
// Sets the resolved credentials the API clients will use for
|
||||||
|
// authentication. Provides the SDK's default credential chain.
|
||||||
|
//
|
||||||
|
// Should probably be the last step in the resolve chain to ensure that all
|
||||||
|
// other configurations are resolved first in case downstream credentials
|
||||||
|
// implementations depend on or can be configured with earlier resolved
|
||||||
|
// configuration options.
|
||||||
|
resolveCredentials,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Config represents a generic configuration value or set of values. This type
|
||||||
|
// will be used by the AWSConfigResolvers to extract
|
||||||
|
//
|
||||||
|
// General the Config type will use type assertion against the Provider interfaces
|
||||||
|
// to extract specific data from the Config.
|
||||||
|
type Config interface{}
|
||||||
|
|
||||||
|
// A loader is used to load external configuration data and returns it as
|
||||||
|
// a generic Config type.
|
||||||
|
//
|
||||||
|
// The loader should return an error if it fails to load the external configuration
|
||||||
|
// or the configuration data is malformed, or required components missing.
|
||||||
|
type loader func(context.Context, configs) (Config, error)
|
||||||
|
|
||||||
|
// An awsConfigResolver will extract configuration data from the configs slice
|
||||||
|
// using the provider interfaces to extract specific functionality. The extracted
|
||||||
|
// configuration values will be written to the AWS Config value.
|
||||||
|
//
|
||||||
|
// The resolver should return an error if it it fails to extract the data, the
|
||||||
|
// data is malformed, or incomplete.
|
||||||
|
type awsConfigResolver func(ctx context.Context, cfg *aws.Config, configs configs) error
|
||||||
|
|
||||||
|
// configs is a slice of Config values. These values will be used by the
|
||||||
|
// AWSConfigResolvers to extract external configuration values to populate the
|
||||||
|
// AWS Config type.
|
||||||
|
//
|
||||||
|
// Use AppendFromLoaders to add additional external Config values that are
|
||||||
|
// loaded from external sources.
|
||||||
|
//
|
||||||
|
// Use ResolveAWSConfig after external Config values have been added or loaded
|
||||||
|
// to extract the loaded configuration values into the AWS Config.
|
||||||
|
type configs []Config
|
||||||
|
|
||||||
|
// AppendFromLoaders iterates over the slice of loaders passed in calling each
|
||||||
|
// loader function in order. The external config value returned by the loader
|
||||||
|
// will be added to the returned configs slice.
|
||||||
|
//
|
||||||
|
// If a loader returns an error this method will stop iterating and return
|
||||||
|
// that error.
|
||||||
|
func (cs configs) AppendFromLoaders(ctx context.Context, loaders []loader) (configs, error) {
|
||||||
|
for _, fn := range loaders {
|
||||||
|
cfg, err := fn(ctx, cs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cs = append(cs, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveAWSConfig returns a AWS configuration populated with values by calling
|
||||||
|
// the resolvers slice passed in. Each resolver is called in order. Any resolver
|
||||||
|
// may overwrite the AWS Configuration value of a previous resolver.
|
||||||
|
//
|
||||||
|
// If an resolver returns an error this method will return that error, and stop
|
||||||
|
// iterating over the resolvers.
|
||||||
|
func (cs configs) ResolveAWSConfig(ctx context.Context, resolvers []awsConfigResolver) (aws.Config, error) {
|
||||||
|
var cfg aws.Config
|
||||||
|
|
||||||
|
for _, fn := range resolvers {
|
||||||
|
if err := fn(ctx, &cfg, cs); err != nil {
|
||||||
|
// TODO provide better error?
|
||||||
|
return aws.Config{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sources []interface{}
|
||||||
|
for _, s := range cs {
|
||||||
|
sources = append(sources, s)
|
||||||
|
}
|
||||||
|
cfg.ConfigSources = sources
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveConfig calls the provide function passing slice of configuration sources.
|
||||||
|
// This implements the aws.ConfigResolver interface.
|
||||||
|
func (cs configs) ResolveConfig(f func(configs []interface{}) error) error {
|
||||||
|
var cfgs []interface{}
|
||||||
|
for i := range cs {
|
||||||
|
cfgs = append(cfgs, cs[i])
|
||||||
|
}
|
||||||
|
return f(cfgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadDefaultConfig reads the SDK's default external configurations, and
|
||||||
|
// populates an AWS Config with the values from the external configurations.
|
||||||
|
//
|
||||||
|
// An optional variadic set of additional Config values can be provided as input
|
||||||
|
// that will be prepended to the configs slice. Use this to add custom configuration.
|
||||||
|
// The custom configurations must satisfy the respective providers for their data
|
||||||
|
// or the custom data will be ignored by the resolvers and config loaders.
|
||||||
|
//
|
||||||
|
// cfg, err := config.LoadDefaultConfig( context.TODO(),
|
||||||
|
// WithSharedConfigProfile("test-profile"),
|
||||||
|
// )
|
||||||
|
// if err != nil {
|
||||||
|
// panic(fmt.Sprintf("failed loading config, %v", err))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// The default configuration sources are:
|
||||||
|
// * Environment Variables
|
||||||
|
// * Shared Configuration and Shared Credentials files.
|
||||||
|
func LoadDefaultConfig(ctx context.Context, optFns ...func(*LoadOptions) error) (cfg aws.Config, err error) {
|
||||||
|
var options LoadOptions
|
||||||
|
for _, optFn := range optFns {
|
||||||
|
optFn(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign Load Options to configs
|
||||||
|
var cfgCpy = configs{options}
|
||||||
|
|
||||||
|
cfgCpy, err = cfgCpy.AppendFromLoaders(ctx, defaultLoaders)
|
||||||
|
if err != nil {
|
||||||
|
return aws.Config{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err = cfgCpy.ResolveAWSConfig(ctx, defaultAWSConfigResolvers)
|
||||||
|
if err != nil {
|
||||||
|
return aws.Config{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Package config provides utilities for loading configuration from multiple
|
||||||
|
// sources that can be used to configure the SDK's API clients, and utilities.
|
||||||
|
//
|
||||||
|
// The config package will load configuration from environment variables, AWS
|
||||||
|
// shared configuration file (~/.aws/config), and AWS shared credentials file
|
||||||
|
// (~/.aws/credentials).
|
||||||
|
//
|
||||||
|
// Use the LoadDefaultConfig to load configuration from all the SDK's supported
|
||||||
|
// sources, and resolve credentials using the SDK's default credential chain.
|
||||||
|
//
|
||||||
|
// LoadDefaultConfig allows for a variadic list of additional Config sources that can
|
||||||
|
// provide one or more configuration values which can be used to programmatically control the resolution
|
||||||
|
// of a specific value, or allow for broader range of additional configuration sources not supported by the SDK.
|
||||||
|
// A Config source implements one or more provider interfaces defined in this package. Config sources passed in will
|
||||||
|
// take precedence over the default environment and shared config sources used by the SDK. If one or more Config sources
|
||||||
|
// implement the same provider interface, priority will be handled by the order in which the sources were passed in.
|
||||||
|
//
|
||||||
|
// A number of helpers (prefixed by ``With``) are provided in this package that implement their respective provider
|
||||||
|
// interface. These helpers should be used for overriding configuration programmatically at runtime.
|
||||||
|
package config
|
|
@ -0,0 +1,341 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CredentialsSourceName provides a name of the provider when config is
|
||||||
|
// loaded from environment.
|
||||||
|
const CredentialsSourceName = "EnvConfigCredentials"
|
||||||
|
|
||||||
|
// Environment variables that will be read for configuration values.
|
||||||
|
const (
|
||||||
|
awsAccessKeyIDEnvVar = "AWS_ACCESS_KEY_ID"
|
||||||
|
awsAccessKeyEnvVar = "AWS_ACCESS_KEY"
|
||||||
|
|
||||||
|
awsSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
|
||||||
|
awsSecretKeyEnvVar = "AWS_SECRET_KEY"
|
||||||
|
|
||||||
|
awsSessionTokenEnvVar = "AWS_SESSION_TOKEN"
|
||||||
|
|
||||||
|
awsContainerCredentialsEndpointEnvVar = "AWS_CONTAINER_CREDENTIALS_FULL_URI"
|
||||||
|
awsContainerCredentialsRelativePathEnvVar = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
|
||||||
|
awsContainerPProviderAuthorizationEnvVar = "AWS_CONTAINER_AUTHORIZATION_TOKEN"
|
||||||
|
|
||||||
|
awsRegionEnvVar = "AWS_REGION"
|
||||||
|
awsDefaultRegionEnvVar = "AWS_DEFAULT_REGION"
|
||||||
|
|
||||||
|
awsProfileEnvVar = "AWS_PROFILE"
|
||||||
|
awsDefaultProfileEnvVar = "AWS_DEFAULT_PROFILE"
|
||||||
|
|
||||||
|
awsSharedCredentialsFileEnvVar = "AWS_SHARED_CREDENTIALS_FILE"
|
||||||
|
|
||||||
|
awsConfigFileEnvVar = "AWS_CONFIG_FILE"
|
||||||
|
|
||||||
|
awsCustomCABundleEnvVar = "AWS_CA_BUNDLE"
|
||||||
|
|
||||||
|
awsWebIdentityTokenFilePathEnvKey = "AWS_WEB_IDENTITY_TOKEN_FILE"
|
||||||
|
|
||||||
|
awsRoleARNEnvKey = "AWS_ROLE_ARN"
|
||||||
|
awsRoleSessionNameEnvKey = "AWS_ROLE_SESSION_NAME"
|
||||||
|
|
||||||
|
awsEnableEndpointDiscoveryEnvKey = "AWS_ENABLE_ENDPOINT_DISCOVERY"
|
||||||
|
|
||||||
|
awsS3UseARNRegionEnvVar = "AWS_S3_USE_ARN_REGION"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
credAccessEnvKeys = []string{
|
||||||
|
awsAccessKeyIDEnvVar,
|
||||||
|
awsAccessKeyEnvVar,
|
||||||
|
}
|
||||||
|
credSecretEnvKeys = []string{
|
||||||
|
awsSecretAccessKeyEnvVar,
|
||||||
|
awsSecretKeyEnvVar,
|
||||||
|
}
|
||||||
|
regionEnvKeys = []string{
|
||||||
|
awsRegionEnvVar,
|
||||||
|
awsDefaultRegionEnvVar,
|
||||||
|
}
|
||||||
|
profileEnvKeys = []string{
|
||||||
|
awsProfileEnvVar,
|
||||||
|
awsDefaultProfileEnvVar,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnvConfig is a collection of environment values the SDK will read
|
||||||
|
// setup config from. All environment values are optional. But some values
|
||||||
|
// such as credentials require multiple values to be complete or the values
|
||||||
|
// will be ignored.
|
||||||
|
type EnvConfig struct {
|
||||||
|
// Environment configuration values. If set both Access Key ID and Secret Access
|
||||||
|
// Key must be provided. Session Token and optionally also be provided, but is
|
||||||
|
// not required.
|
||||||
|
//
|
||||||
|
// # Access Key ID
|
||||||
|
// AWS_ACCESS_KEY_ID=AKID
|
||||||
|
// AWS_ACCESS_KEY=AKID # only read if AWS_ACCESS_KEY_ID is not set.
|
||||||
|
//
|
||||||
|
// # Secret Access Key
|
||||||
|
// AWS_SECRET_ACCESS_KEY=SECRET
|
||||||
|
// AWS_SECRET_KEY=SECRET # only read if AWS_SECRET_ACCESS_KEY is not set.
|
||||||
|
//
|
||||||
|
// # Session Token
|
||||||
|
// AWS_SESSION_TOKEN=TOKEN
|
||||||
|
Credentials aws.Credentials
|
||||||
|
|
||||||
|
// ContainerCredentialsEndpoint value is the HTTP enabled endpoint to retrieve credentials
|
||||||
|
// using the endpointcreds.Provider
|
||||||
|
ContainerCredentialsEndpoint string
|
||||||
|
|
||||||
|
// ContainerCredentialsRelativePath is the relative URI path that will be used when attempting to retrieve
|
||||||
|
// credentials from the container endpoint.
|
||||||
|
ContainerCredentialsRelativePath string
|
||||||
|
|
||||||
|
// ContainerAuthorizationToken is the authorization token that will be included in the HTTP Authorization
|
||||||
|
// header when attempting to retrieve credentials from the container credentials endpoint.
|
||||||
|
ContainerAuthorizationToken string
|
||||||
|
|
||||||
|
// Region value will instruct the SDK where to make service API requests to. If is
|
||||||
|
// not provided in the environment the region must be provided before a service
|
||||||
|
// client request is made.
|
||||||
|
//
|
||||||
|
// AWS_REGION=us-west-2
|
||||||
|
// AWS_DEFAULT_REGION=us-west-2
|
||||||
|
Region string
|
||||||
|
|
||||||
|
// Profile name the SDK should load use when loading shared configuration from the
|
||||||
|
// shared configuration files. If not provided "default" will be used as the
|
||||||
|
// profile name.
|
||||||
|
//
|
||||||
|
// AWS_PROFILE=my_profile
|
||||||
|
// AWS_DEFAULT_PROFILE=my_profile
|
||||||
|
SharedConfigProfile string
|
||||||
|
|
||||||
|
// Shared credentials file path can be set to instruct the SDK to use an alternate
|
||||||
|
// file for the shared credentials. If not set the file will be loaded from
|
||||||
|
// $HOME/.aws/credentials on Linux/Unix based systems, and
|
||||||
|
// %USERPROFILE%\.aws\credentials on Windows.
|
||||||
|
//
|
||||||
|
// AWS_SHARED_CREDENTIALS_FILE=$HOME/my_shared_credentials
|
||||||
|
SharedCredentialsFile string
|
||||||
|
|
||||||
|
// Shared config file path can be set to instruct the SDK to use an alternate
|
||||||
|
// file for the shared config. If not set the file will be loaded from
|
||||||
|
// $HOME/.aws/config on Linux/Unix based systems, and
|
||||||
|
// %USERPROFILE%\.aws\config on Windows.
|
||||||
|
//
|
||||||
|
// AWS_CONFIG_FILE=$HOME/my_shared_config
|
||||||
|
SharedConfigFile string
|
||||||
|
|
||||||
|
// Sets the path to a custom Credentials Authority (CA) Bundle PEM file
|
||||||
|
// that the SDK will use instead of the system's root CA bundle.
|
||||||
|
// Only use this if you want to configure the SDK to use a custom set
|
||||||
|
// of CAs.
|
||||||
|
//
|
||||||
|
// Enabling this option will attempt to merge the Transport
|
||||||
|
// into the SDK's HTTP client. If the client's Transport is
|
||||||
|
// not a http.Transport an error will be returned. If the
|
||||||
|
// Transport's TLS config is set this option will cause the
|
||||||
|
// SDK to overwrite the Transport's TLS config's RootCAs value.
|
||||||
|
//
|
||||||
|
// Setting a custom HTTPClient in the aws.Config options will override this setting.
|
||||||
|
// To use this option and custom HTTP client, the HTTP client needs to be provided
|
||||||
|
// when creating the config. Not the service client.
|
||||||
|
//
|
||||||
|
// AWS_CA_BUNDLE=$HOME/my_custom_ca_bundle
|
||||||
|
CustomCABundle string
|
||||||
|
|
||||||
|
// Enables endpoint discovery via environment variables.
|
||||||
|
//
|
||||||
|
// AWS_ENABLE_ENDPOINT_DISCOVERY=true
|
||||||
|
EnableEndpointDiscovery *bool
|
||||||
|
|
||||||
|
// Specifies the WebIdentity token the SDK should use to assume a role
|
||||||
|
// with.
|
||||||
|
//
|
||||||
|
// AWS_WEB_IDENTITY_TOKEN_FILE=file_path
|
||||||
|
WebIdentityTokenFilePath string
|
||||||
|
|
||||||
|
// Specifies the IAM role arn to use when assuming an role.
|
||||||
|
//
|
||||||
|
// AWS_ROLE_ARN=role_arn
|
||||||
|
RoleARN string
|
||||||
|
|
||||||
|
// Specifies the IAM role session name to use when assuming a role.
|
||||||
|
//
|
||||||
|
// AWS_ROLE_SESSION_NAME=session_name
|
||||||
|
RoleSessionName string
|
||||||
|
|
||||||
|
// Specifies if the S3 service should allow ARNs to direct the region
|
||||||
|
// the client's requests are sent to.
|
||||||
|
//
|
||||||
|
// AWS_S3_USE_ARN_REGION=true
|
||||||
|
S3UseARNRegion *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadEnvConfig reads configuration values from the OS's environment variables.
|
||||||
|
// Returning the a Config typed EnvConfig to satisfy the ConfigLoader func type.
|
||||||
|
func loadEnvConfig(ctx context.Context, cfgs configs) (Config, error) {
|
||||||
|
return NewEnvConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEnvConfig retrieves the SDK's environment configuration.
|
||||||
|
// See `EnvConfig` for the values that will be retrieved.
|
||||||
|
func NewEnvConfig() (EnvConfig, error) {
|
||||||
|
var cfg EnvConfig
|
||||||
|
|
||||||
|
creds := aws.Credentials{
|
||||||
|
Source: CredentialsSourceName,
|
||||||
|
}
|
||||||
|
setStringFromEnvVal(&creds.AccessKeyID, credAccessEnvKeys)
|
||||||
|
setStringFromEnvVal(&creds.SecretAccessKey, credSecretEnvKeys)
|
||||||
|
if creds.HasKeys() {
|
||||||
|
creds.SessionToken = os.Getenv(awsSessionTokenEnvVar)
|
||||||
|
cfg.Credentials = creds
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.ContainerCredentialsEndpoint = os.Getenv(awsContainerCredentialsEndpointEnvVar)
|
||||||
|
cfg.ContainerCredentialsRelativePath = os.Getenv(awsContainerCredentialsRelativePathEnvVar)
|
||||||
|
cfg.ContainerAuthorizationToken = os.Getenv(awsContainerPProviderAuthorizationEnvVar)
|
||||||
|
|
||||||
|
setStringFromEnvVal(&cfg.Region, regionEnvKeys)
|
||||||
|
setStringFromEnvVal(&cfg.SharedConfigProfile, profileEnvKeys)
|
||||||
|
|
||||||
|
cfg.SharedCredentialsFile = os.Getenv(awsSharedCredentialsFileEnvVar)
|
||||||
|
cfg.SharedConfigFile = os.Getenv(awsConfigFileEnvVar)
|
||||||
|
|
||||||
|
cfg.CustomCABundle = os.Getenv(awsCustomCABundleEnvVar)
|
||||||
|
|
||||||
|
cfg.WebIdentityTokenFilePath = os.Getenv(awsWebIdentityTokenFilePathEnvKey)
|
||||||
|
|
||||||
|
cfg.RoleARN = os.Getenv(awsRoleARNEnvKey)
|
||||||
|
cfg.RoleSessionName = os.Getenv(awsRoleSessionNameEnvKey)
|
||||||
|
|
||||||
|
if err := setBoolPtrFromEnvVal(&cfg.EnableEndpointDiscovery, []string{awsEnableEndpointDiscoveryEnvKey}); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setBoolPtrFromEnvVal(&cfg.S3UseARNRegion, []string{awsS3UseARNRegionEnvVar}); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegion returns the AWS Region if set in the environment. Returns an empty
|
||||||
|
// string if not set.
|
||||||
|
func (c EnvConfig) getRegion(ctx context.Context) (string, bool, error) {
|
||||||
|
if len(c.Region) == 0 {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
return c.Region, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSharedConfigProfile returns the shared config profile if set in the
|
||||||
|
// environment. Returns an empty string if not set.
|
||||||
|
func (c EnvConfig) getSharedConfigProfile(ctx context.Context) (string, bool, error) {
|
||||||
|
if len(c.SharedConfigProfile) == 0 {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SharedConfigProfile, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSharedConfigFiles returns a slice of filenames set in the environment.
|
||||||
|
//
|
||||||
|
// Will return the filenames in the order of:
|
||||||
|
// * Shared Config
|
||||||
|
func (c EnvConfig) getSharedConfigFiles(context.Context) ([]string, bool, error) {
|
||||||
|
var files []string
|
||||||
|
if v := c.SharedConfigFile; len(v) > 0 {
|
||||||
|
files = append(files, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(files) == 0 {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
return files, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSharedCredentialsFiles returns a slice of filenames set in the environment.
|
||||||
|
//
|
||||||
|
// Will return the filenames in the order of:
|
||||||
|
// * Shared Credentials
|
||||||
|
func (c EnvConfig) getSharedCredentialsFiles(context.Context) ([]string, bool, error) {
|
||||||
|
var files []string
|
||||||
|
if v := c.SharedCredentialsFile; len(v) > 0 {
|
||||||
|
files = append(files, v)
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
return files, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCustomCABundle returns the custom CA bundle's PEM bytes if the file was
|
||||||
|
func (c EnvConfig) getCustomCABundle(context.Context) (io.Reader, bool, error) {
|
||||||
|
if len(c.CustomCABundle) == 0 {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadFile(c.CustomCABundle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return bytes.NewReader(b), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetS3UseARNRegion returns whether to allow ARNs to direct the region
|
||||||
|
// the S3 client's requests are sent to.
|
||||||
|
func (c EnvConfig) GetS3UseARNRegion(ctx context.Context) (value, ok bool, err error) {
|
||||||
|
if c.S3UseARNRegion == nil {
|
||||||
|
return false, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return *c.S3UseARNRegion, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setStringFromEnvVal(dst *string, keys []string) {
|
||||||
|
for _, k := range keys {
|
||||||
|
if v := os.Getenv(k); len(v) > 0 {
|
||||||
|
*dst = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBoolPtrFromEnvVal(dst **bool, keys []string) error {
|
||||||
|
for _, k := range keys {
|
||||||
|
value := os.Getenv(k)
|
||||||
|
if len(value) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if *dst == nil {
|
||||||
|
*dst = new(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.EqualFold(value, "false"):
|
||||||
|
**dst = false
|
||||||
|
case strings.EqualFold(value, "true"):
|
||||||
|
**dst = true
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"invalid value for environment variable, %s=%s, need true or false",
|
||||||
|
k, value)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue