Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 371667c3a7 | |||
| f570852816 | |||
| 86b8ce8df0 | |||
| b51bf9250e | |||
| 2d5a32629a | |||
| 3e2db82cab | |||
| bc9dd69669 | |||
| 734e91b97c | |||
| ab0d1ee363 | |||
| cf94fd1778 | |||
| 15a2e59bba | |||
| 8c4eb5f4aa | |||
| 86788220a9 | |||
| e3bcb4f2ac | |||
| ccd0430fda | |||
| 49474f8f37 | |||
| e0614cabf4 | |||
| eaec3e5564 | |||
| eaaf22dcde | |||
| f2f33fa344 | |||
| 9189f1228e | |||
| 5e7b5729e6 | |||
| 9365c90c0b | |||
| f27bdf85f4 | |||
| 9f7bb4da25 | |||
| b1967e99c7 | |||
| 7efb41868f | |||
| 260906c3e4 | |||
| f65e1d5d55 | |||
| 33461126e2 |
@@ -1,3 +1,5 @@
|
||||
## 1.7.3 (Upcoming)
|
||||
|
||||
## 1.7.2 (April 05, 2021)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
|
||||
@@ -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"
|
||||
|
||||
"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"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
const defaultTestRegion = "cn-beijing"
|
||||
|
||||
@@ -30,8 +30,8 @@ import (
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
||||
|
||||
@@ -181,7 +181,6 @@ func NewAzureClient(subscriptionID, resourceGroupName string,
|
||||
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.Client.PollingDuration = SharedGalleryTimeout
|
||||
azureClient.GalleryImageVersionsClient.Client.PollingDuration = PollingDuration
|
||||
|
||||
azureClient.GalleryImagesClient = newCompute.NewGalleryImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
|
||||
azureClient.GalleryImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
|
||||
|
||||
@@ -28,7 +28,7 @@ package dtl
|
||||
import (
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
const DeviceLoginAcceptanceTest = "DEVICELOGIN_TEST"
|
||||
|
||||
@@ -80,10 +80,17 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
|
||||
|
||||
// Build the steps
|
||||
steps := []multistep.Step{
|
||||
&stepCreateSSHKey{
|
||||
Debug: b.config.PackerDebug,
|
||||
DebugKeyPath: fmt.Sprintf("do_%s.pem", b.config.PackerBuildName),
|
||||
&communicator.StepSSHKeyGen{
|
||||
CommConf: &b.config.Comm,
|
||||
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(stepDropletInfo),
|
||||
&communicator.StepConnect{
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,26 +2,16 @@ package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/uuid"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type stepCreateSSHKey struct {
|
||||
Debug bool
|
||||
DebugKeyPath string
|
||||
|
||||
keyId int
|
||||
}
|
||||
|
||||
@@ -30,31 +20,12 @@ func (s *stepCreateSSHKey) Run(ctx context.Context, state multistep.StateBag) mu
|
||||
ui := state.Get("ui").(packersdk.Ui)
|
||||
c := state.Get("config").(*Config)
|
||||
|
||||
ui.Say("Creating temporary ssh key for droplet...")
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2014)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("error generating RSA key: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
if c.Comm.SSHPublicKey == nil {
|
||||
ui.Say("No public SSH key found; skipping SSH public key import...")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
// ASN.1 DER encoded form
|
||||
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))
|
||||
ui.Say("Importing SSH public key...")
|
||||
|
||||
// The name of the public key on DO
|
||||
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!
|
||||
key, _, err := client.Keys.Create(context.TODO(), &godo.KeyCreateRequest{
|
||||
Name: name,
|
||||
PublicKey: pub_sshformat,
|
||||
PublicKey: string(c.Comm.SSHPublicKey),
|
||||
})
|
||||
if err != nil {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilder_implBuilder(t *testing.T) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
|
||||
@@ -4,7 +4,7 @@ package bsu
|
||||
import (
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package bsusurrogate
|
||||
import (
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
|
||||
@@ -4,7 +4,7 @@ package bsuvolume
|
||||
import (
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
|
||||
@@ -6,10 +6,9 @@ import (
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
ucloudcommon "github.com/hashicorp/packer/builder/ucloud/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_validateRegion(t *testing.T) {
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_Impl(t *testing.T) {
|
||||
var raw interface{} = &artifact{}
|
||||
|
||||
if _, ok := raw.(packersdk.Artifact); !ok {
|
||||
t.Fatalf("Artifact does not implement packersdk.Artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactId(t *testing.T) {
|
||||
a := &artifact{
|
||||
OutputDir: "/my/dir",
|
||||
BoxName: "package.box",
|
||||
Provider: "virtualbox",
|
||||
}
|
||||
|
||||
expected := "virtualbox"
|
||||
if a.Id() != expected {
|
||||
t.Fatalf("artifact ID should match: expected: %s received: %s", expected, a.Id())
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactString(t *testing.T) {
|
||||
a := &artifact{
|
||||
OutputDir: "/my/dir",
|
||||
BoxName: "package.box",
|
||||
Provider: "virtualbox",
|
||||
}
|
||||
expected := "Vagrant box 'package.box' for 'virtualbox' provider"
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = strings.Replace(expected, "/", "\\", -1)
|
||||
}
|
||||
|
||||
if strings.Compare(a.String(), expected) != 0 {
|
||||
t.Fatalf("artifact string should match: expected: %s received: %s", expected, a.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactState(t *testing.T) {
|
||||
expectedData := "this is the data"
|
||||
a := &artifact{
|
||||
StateData: map[string]interface{}{"state_data": expectedData},
|
||||
}
|
||||
|
||||
// Valid state
|
||||
result := a.State("state_data")
|
||||
if result != expectedData {
|
||||
t.Fatalf("Bad: State data was %s instead of %s", result, expectedData)
|
||||
}
|
||||
|
||||
// Invalid state
|
||||
result = a.State("invalid_key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for invalid state data name")
|
||||
}
|
||||
|
||||
// Nil StateData should not fail and should return nil
|
||||
a = &artifact{}
|
||||
result = a.State("key")
|
||||
if result != nil {
|
||||
t.Fatalf("Bad: State should be nil for nil StateData")
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packersdk.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_Prepare_ValidateSource(t *testing.T) {
|
||||
type testCase struct {
|
||||
config map[string]interface{}
|
||||
errExpected bool
|
||||
reason string
|
||||
}
|
||||
|
||||
cases := []testCase{
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"global_id": "a3559ec",
|
||||
},
|
||||
errExpected: true,
|
||||
reason: "Need to set SSH communicator.",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"global_id": "a3559ec",
|
||||
"communicator": "ssh",
|
||||
},
|
||||
errExpected: false,
|
||||
reason: "Shouldn't fail because we've set global_id",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
},
|
||||
errExpected: true,
|
||||
reason: "Should fail because we must set source_path or global_id",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"source_path": "./mybox",
|
||||
"communicator": "ssh",
|
||||
},
|
||||
errExpected: false,
|
||||
reason: "Source path is set; we should be fine",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"source_path": "./mybox",
|
||||
"communicator": "ssh",
|
||||
"global_id": "a3559ec",
|
||||
},
|
||||
errExpected: true,
|
||||
reason: "Both source path and global are set: we should error.",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
"global_id": "a3559ec",
|
||||
"teardown_method": "suspend",
|
||||
},
|
||||
errExpected: false,
|
||||
reason: "Valid argument for teardown method",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
"global_id": "a3559ec",
|
||||
"teardown_method": "surspernd",
|
||||
},
|
||||
errExpected: true,
|
||||
reason: "Inalid argument for teardown method",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
"source_path": "./my.box",
|
||||
},
|
||||
errExpected: true,
|
||||
reason: "Should fail because path does not exist",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
"source_path": "file://my.box",
|
||||
},
|
||||
errExpected: true,
|
||||
reason: "Should fail because path does not exist",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
"source_path": "http://my.box",
|
||||
},
|
||||
errExpected: false,
|
||||
reason: "Should pass because path is not local",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
"source_path": "https://my.box",
|
||||
},
|
||||
errExpected: false,
|
||||
reason: "Should pass because path is not local",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
"source_path": "smb://my.box",
|
||||
},
|
||||
errExpected: false,
|
||||
reason: "Should pass because path is not local",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
_, _, err := (&Builder{}).Prepare(tc.config)
|
||||
if (err != nil) != tc.errExpected {
|
||||
t.Fatalf("Unexpected behavior from test case %#v; %s.", tc.config, tc.reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepAdd_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepAddBox)
|
||||
if _, ok := raw.(multistep.Step); !ok {
|
||||
t.Fatalf("initialize should be a step")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepAddArgs(t *testing.T) {
|
||||
type testArgs struct {
|
||||
Step StepAddBox
|
||||
Expected []string
|
||||
}
|
||||
addTests := []testArgs{
|
||||
{
|
||||
Step: StepAddBox{
|
||||
SourceBox: "my_source_box.box",
|
||||
BoxName: "AWESOME BOX",
|
||||
},
|
||||
Expected: []string{"AWESOME BOX", "my_source_box.box"},
|
||||
},
|
||||
{
|
||||
Step: StepAddBox{
|
||||
SourceBox: "my_source_box",
|
||||
BoxName: "AWESOME BOX",
|
||||
},
|
||||
Expected: []string{"my_source_box"},
|
||||
},
|
||||
{
|
||||
Step: StepAddBox{
|
||||
BoxVersion: "eleventyone",
|
||||
CACert: "adfasdf",
|
||||
CAPath: "adfasdf",
|
||||
DownloadCert: "adfasdf",
|
||||
Clean: true,
|
||||
Force: true,
|
||||
Insecure: true,
|
||||
Provider: "virtualbox",
|
||||
SourceBox: "bananabox.box",
|
||||
BoxName: "bananas",
|
||||
},
|
||||
Expected: []string{"bananas", "bananabox.box", "--box-version", "eleventyone", "--cacert", "adfasdf", "--capath", "adfasdf", "--cert", "adfasdf", "--clean", "--force", "--insecure", "--provider", "virtualbox"},
|
||||
},
|
||||
}
|
||||
for _, addTest := range addTests {
|
||||
addArgs := addTest.Step.generateAddArgs()
|
||||
for i, val := range addTest.Expected {
|
||||
if strings.Compare(addArgs[i], val) != 0 {
|
||||
t.Fatalf("expected %#v but received %#v", addTest.Expected, addArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepCreateVagrantfile_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepCreateVagrantfile)
|
||||
if _, ok := raw.(multistep.Step); !ok {
|
||||
t.Fatalf("initialize should be a step")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateFile(t *testing.T) {
|
||||
testy := StepCreateVagrantfile{
|
||||
OutputDir: "./",
|
||||
SourceBox: "apples",
|
||||
BoxName: "bananas",
|
||||
}
|
||||
templatePath, err := testy.createVagrantfile()
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
defer os.Remove(templatePath)
|
||||
contents, err := ioutil.ReadFile(templatePath)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
actual := string(contents)
|
||||
expected := `Vagrant.configure("2") do |config|
|
||||
config.vm.define "source", autostart: false do |source|
|
||||
source.vm.box = "apples"
|
||||
config.ssh.insert_key = false
|
||||
end
|
||||
config.vm.define "output" do |output|
|
||||
output.vm.box = "bananas"
|
||||
output.vm.box_url = "file://package.box"
|
||||
config.ssh.insert_key = false
|
||||
end
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
end`
|
||||
if ok := strings.Compare(actual, expected); ok != 0 {
|
||||
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateFile_customSync(t *testing.T) {
|
||||
testy := StepCreateVagrantfile{
|
||||
OutputDir: "./",
|
||||
SyncedFolder: "myfolder/foldertimes",
|
||||
}
|
||||
templatePath, err := testy.createVagrantfile()
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
defer os.Remove(templatePath)
|
||||
contents, err := ioutil.ReadFile(templatePath)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
actual := string(contents)
|
||||
expected := `Vagrant.configure("2") do |config|
|
||||
config.vm.define "source", autostart: false do |source|
|
||||
source.vm.box = ""
|
||||
config.ssh.insert_key = false
|
||||
end
|
||||
config.vm.define "output" do |output|
|
||||
output.vm.box = ""
|
||||
output.vm.box_url = "file://package.box"
|
||||
config.ssh.insert_key = false
|
||||
end
|
||||
config.vm.synced_folder "myfolder/foldertimes", "/vagrant"
|
||||
end`
|
||||
if ok := strings.Compare(actual, expected); ok != 0 {
|
||||
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateFile_InsertKeyTrue(t *testing.T) {
|
||||
testy := StepCreateVagrantfile{
|
||||
OutputDir: "./",
|
||||
InsertKey: true,
|
||||
}
|
||||
templatePath, err := testy.createVagrantfile()
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
defer os.Remove(templatePath)
|
||||
contents, err := ioutil.ReadFile(templatePath)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
actual := string(contents)
|
||||
expected := `Vagrant.configure("2") do |config|
|
||||
config.vm.define "source", autostart: false do |source|
|
||||
source.vm.box = ""
|
||||
config.ssh.insert_key = true
|
||||
end
|
||||
config.vm.define "output" do |output|
|
||||
output.vm.box = ""
|
||||
output.vm.box_url = "file://package.box"
|
||||
config.ssh.insert_key = true
|
||||
end
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
end`
|
||||
if ok := strings.Compare(actual, expected); ok != 0 {
|
||||
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, actual)
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/communicator"
|
||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||
)
|
||||
|
||||
func TestStepSSHConfig_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepSSHConfig)
|
||||
if _, ok := raw.(multistep.Step); !ok {
|
||||
t.Fatalf("initialize should be a step")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepStepSSHConfig_sshOverrides(t *testing.T) {
|
||||
type testcase struct {
|
||||
name string
|
||||
inputSSHConfig communicator.SSH
|
||||
expectedSSHConfig communicator.SSH
|
||||
}
|
||||
tcs := []testcase{
|
||||
{
|
||||
// defaults to overriding with the ssh config from vagrant\
|
||||
name: "default",
|
||||
inputSSHConfig: communicator.SSH{},
|
||||
expectedSSHConfig: communicator.SSH{
|
||||
SSHHost: "127.0.0.1",
|
||||
SSHPort: 2222,
|
||||
SSHUsername: "vagrant",
|
||||
SSHPassword: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
// respects SSH host and port overrides independent of credential
|
||||
// overrides
|
||||
name: "host_override",
|
||||
inputSSHConfig: communicator.SSH{
|
||||
SSHHost: "123.45.67.8",
|
||||
SSHPort: 1234,
|
||||
},
|
||||
expectedSSHConfig: communicator.SSH{
|
||||
SSHHost: "123.45.67.8",
|
||||
SSHPort: 1234,
|
||||
SSHUsername: "vagrant",
|
||||
SSHPassword: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
// respects credential overrides
|
||||
name: "credential_override",
|
||||
inputSSHConfig: communicator.SSH{
|
||||
SSHUsername: "megan",
|
||||
SSHPassword: "SoSecure",
|
||||
},
|
||||
expectedSSHConfig: communicator.SSH{
|
||||
SSHHost: "127.0.0.1",
|
||||
SSHPort: 2222,
|
||||
SSHUsername: "megan",
|
||||
SSHPassword: "SoSecure",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
driver := &MockVagrantDriver{}
|
||||
config := &Config{
|
||||
Comm: communicator.Config{
|
||||
SSH: tc.inputSSHConfig,
|
||||
},
|
||||
}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("driver", driver)
|
||||
state.Put("config", config)
|
||||
|
||||
step := StepSSHConfig{}
|
||||
_ = step.Run(context.Background(), state)
|
||||
|
||||
if config.Comm.SSHHost != tc.expectedSSHConfig.SSHHost {
|
||||
t.Fatalf("unexpected sshconfig host: name: %s, recieved %s", tc.name, config.Comm.SSHHost)
|
||||
}
|
||||
if config.Comm.SSHPort != tc.expectedSSHConfig.SSHPort {
|
||||
t.Fatalf("unexpected sshconfig port: name: %s, recieved %d", tc.name, config.Comm.SSHPort)
|
||||
}
|
||||
if config.Comm.SSHUsername != tc.expectedSSHConfig.SSHUsername {
|
||||
t.Fatalf("unexpected sshconfig SSHUsername: name: %s, recieved %s", tc.name, config.Comm.SSHUsername)
|
||||
}
|
||||
if config.Comm.SSHPassword != tc.expectedSSHConfig.SSHPassword {
|
||||
t.Fatalf("unexpected sshconfig SSHUsername: name: %s, recieved %s", tc.name, config.Comm.SSHPassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepStepSSHConfig_GlobalID(t *testing.T) {
|
||||
driver := &MockVagrantDriver{}
|
||||
config := &Config{}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("driver", driver)
|
||||
state.Put("config", config)
|
||||
|
||||
step := StepSSHConfig{
|
||||
GlobalID: "adsfadf",
|
||||
}
|
||||
_ = step.Run(context.Background(), state)
|
||||
if driver.GlobalID != "adsfadf" {
|
||||
t.Fatalf("Should have called SSHConfig with GlobalID asdfasdf")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepStepSSHConfig_NoGlobalID(t *testing.T) {
|
||||
driver := &MockVagrantDriver{}
|
||||
config := &Config{}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("driver", driver)
|
||||
state.Put("config", config)
|
||||
|
||||
step := StepSSHConfig{}
|
||||
_ = step.Run(context.Background(), state)
|
||||
if driver.GlobalID != "source" {
|
||||
t.Fatalf("Should have called SSHConfig with GlobalID source")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepStepSSHConfig_SpacesInPath(t *testing.T) {
|
||||
driver := &MockVagrantDriver{}
|
||||
driver.ReturnSSHConfig = &VagrantSSHConfig{
|
||||
Hostname: "127.0.0.1",
|
||||
User: "vagrant",
|
||||
Port: "2222",
|
||||
UserKnownHostsFile: "/dev/null",
|
||||
StrictHostKeyChecking: false,
|
||||
PasswordAuthentication: false,
|
||||
IdentityFile: "\"/path with spaces/insecure_private_key\"",
|
||||
IdentitiesOnly: true,
|
||||
LogLevel: "FATAL"}
|
||||
|
||||
config := &Config{}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("driver", driver)
|
||||
state.Put("config", config)
|
||||
|
||||
step := StepSSHConfig{}
|
||||
_ = step.Run(context.Background(), state)
|
||||
expected := "/path with spaces/insecure_private_key"
|
||||
if config.Comm.SSHPrivateKeyFile != expected {
|
||||
t.Fatalf("Bad config private key. Recieved: %s; expected: %s.", config.Comm.SSHPrivateKeyFile, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepStepSSHConfig_NoSpacesInPath(t *testing.T) {
|
||||
driver := &MockVagrantDriver{}
|
||||
driver.ReturnSSHConfig = &VagrantSSHConfig{
|
||||
Hostname: "127.0.0.1",
|
||||
User: "vagrant",
|
||||
Port: "2222",
|
||||
UserKnownHostsFile: "/dev/null",
|
||||
StrictHostKeyChecking: false,
|
||||
PasswordAuthentication: false,
|
||||
IdentityFile: "/path/without/spaces/insecure_private_key",
|
||||
IdentitiesOnly: true,
|
||||
LogLevel: "FATAL"}
|
||||
|
||||
config := &Config{}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("driver", driver)
|
||||
state.Put("config", config)
|
||||
|
||||
step := StepSSHConfig{}
|
||||
_ = step.Run(context.Background(), state)
|
||||
expected := "/path/without/spaces/insecure_private_key"
|
||||
if config.Comm.SSHPrivateKeyFile != expected {
|
||||
t.Fatalf("Bad config private key. Recieved: %s; expected: %s.", config.Comm.SSHPrivateKeyFile, expected)
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPrepUpArgs(t *testing.T) {
|
||||
type testArgs struct {
|
||||
Step StepUp
|
||||
Expected []string
|
||||
}
|
||||
tests := []testArgs{
|
||||
{
|
||||
Step: StepUp{
|
||||
GlobalID: "foo",
|
||||
Provider: "bar",
|
||||
},
|
||||
Expected: []string{"foo", "--provider=bar"},
|
||||
},
|
||||
{
|
||||
Step: StepUp{},
|
||||
Expected: []string{"source"},
|
||||
},
|
||||
{
|
||||
Step: StepUp{
|
||||
Provider: "pro",
|
||||
},
|
||||
Expected: []string{"source", "--provider=pro"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
args := test.Step.generateArgs()
|
||||
for i, val := range test.Expected {
|
||||
if strings.Compare(args[i], val) != 0 {
|
||||
t.Fatalf("expected %#v but received %#v", test.Expected, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
||||
packerVersion "github.com/hashicorp/packer/version"
|
||||
)
|
||||
|
||||
var VagrantPluginVersion *version.PluginVersion
|
||||
|
||||
func init() {
|
||||
VagrantPluginVersion = version.InitializePluginVersion(
|
||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
func TestBuilderAcc_basic(t *testing.T) {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package iso
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"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) {
|
||||
@@ -15,8 +18,25 @@ func TestBuilderAcc_basic(t *testing.T) {
|
||||
t.Fatalf("failed to load template file %s", templatePath)
|
||||
}
|
||||
|
||||
builderT.Test(t, builderT.TestCase{
|
||||
Builder: &Builder{},
|
||||
testCase := &acctest.PluginTestCase{
|
||||
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),
|
||||
})
|
||||
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"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/hashicorp/packer-plugin-sdk/tmp"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
)
|
||||
|
||||
const vmxTestTemplate string = `{"builders":[{%s}],"provisioners":[{%s}]}`
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"builders": [
|
||||
{
|
||||
"type": "test",
|
||||
"type": "vmware-iso",
|
||||
"boot_command": [
|
||||
"<esc><wait>",
|
||||
"<esc><wait>",
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
"github.com/hashicorp/packer/builder/vsphere/common"
|
||||
commonT "github.com/hashicorp/packer/builder/vsphere/common/testing"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
commonT "github.com/hashicorp/packer/builder/vsphere/common/testing"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"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"
|
||||
|
||||
@@ -50,7 +50,6 @@ import (
|
||||
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
|
||||
tritonbuilder "github.com/hashicorp/packer/builder/triton"
|
||||
uclouduhostbuilder "github.com/hashicorp/packer/builder/ucloud/uhost"
|
||||
vagrantbuilder "github.com/hashicorp/packer/builder/vagrant"
|
||||
virtualboxisobuilder "github.com/hashicorp/packer/builder/virtualbox/iso"
|
||||
virtualboxovfbuilder "github.com/hashicorp/packer/builder/virtualbox/ovf"
|
||||
virtualboxvmbuilder "github.com/hashicorp/packer/builder/virtualbox/vm"
|
||||
@@ -69,8 +68,6 @@ import (
|
||||
manifestpostprocessor "github.com/hashicorp/packer/post-processor/manifest"
|
||||
shelllocalpostprocessor "github.com/hashicorp/packer/post-processor/shell-local"
|
||||
ucloudimportpostprocessor "github.com/hashicorp/packer/post-processor/ucloud-import"
|
||||
vagrantpostprocessor "github.com/hashicorp/packer/post-processor/vagrant"
|
||||
vagrantcloudpostprocessor "github.com/hashicorp/packer/post-processor/vagrant-cloud"
|
||||
vspherepostprocessor "github.com/hashicorp/packer/post-processor/vsphere"
|
||||
vspheretemplatepostprocessor "github.com/hashicorp/packer/post-processor/vsphere-template"
|
||||
yandexexportpostprocessor "github.com/hashicorp/packer/post-processor/yandex-export"
|
||||
@@ -137,7 +134,6 @@ var Builders = map[string]packersdk.Builder{
|
||||
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
|
||||
"triton": new(tritonbuilder.Builder),
|
||||
"ucloud-uhost": new(uclouduhostbuilder.Builder),
|
||||
"vagrant": new(vagrantbuilder.Builder),
|
||||
"virtualbox-iso": new(virtualboxisobuilder.Builder),
|
||||
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
|
||||
"virtualbox-vm": new(virtualboxvmbuilder.Builder),
|
||||
@@ -180,8 +176,6 @@ var PostProcessors = map[string]packersdk.PostProcessor{
|
||||
"manifest": new(manifestpostprocessor.PostProcessor),
|
||||
"shell-local": new(shelllocalpostprocessor.PostProcessor),
|
||||
"ucloud-import": new(ucloudimportpostprocessor.PostProcessor),
|
||||
"vagrant": new(vagrantpostprocessor.PostProcessor),
|
||||
"vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor),
|
||||
"vsphere": new(vspherepostprocessor.PostProcessor),
|
||||
"vsphere-template": new(vspheretemplatepostprocessor.PostProcessor),
|
||||
"yandex-export": new(yandexexportpostprocessor.PostProcessor),
|
||||
|
||||
@@ -20,6 +20,9 @@ import (
|
||||
dockerpushpostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-push"
|
||||
dockersavepostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-save"
|
||||
dockertagpostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-tag"
|
||||
vagrantbuilder "github.com/hashicorp/packer-plugin-vagrant/builder/vagrant"
|
||||
vagrantpostprocessor "github.com/hashicorp/packer-plugin-vagrant/post-processor/vagrant"
|
||||
vagrantcloudpostprocessor "github.com/hashicorp/packer-plugin-vagrant/post-processor/vagrant-cloud"
|
||||
)
|
||||
|
||||
// VendoredDatasources are datasource components that were once bundled with the
|
||||
@@ -38,6 +41,7 @@ var VendoredBuilders = map[string]packersdk.Builder{
|
||||
"amazon-ebssurrogate": new(amazonebssurrogatebuilder.Builder),
|
||||
"amazon-ebsvolume": new(amazonebsvolumebuilder.Builder),
|
||||
"amazon-instance": new(amazoninstancebuilder.Builder),
|
||||
"vagrant": new(vagrantbuilder.Builder),
|
||||
}
|
||||
|
||||
// VendoredProvisioners are provisioner components that were once bundled with the
|
||||
@@ -53,6 +57,8 @@ var VendoredPostProcessors = map[string]packersdk.PostProcessor{
|
||||
"docker-tag": new(dockertagpostprocessor.PostProcessor),
|
||||
"exoscale-import": new(exoscaleimportpostprocessor.PostProcessor),
|
||||
"amazon-import": new(anazibimportpostprocessor.PostProcessor),
|
||||
"vagrant": new(vagrantpostprocessor.PostProcessor),
|
||||
"vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor),
|
||||
}
|
||||
|
||||
// Upon init lets load up any plugins that were vendored manually into the default
|
||||
|
||||
@@ -25,7 +25,7 @@ require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f
|
||||
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/structtag v1.0.0
|
||||
github.com/go-ini/ini v1.25.4
|
||||
@@ -51,7 +51,8 @@ require (
|
||||
github.com/hashicorp/hcl/v2 v2.9.1
|
||||
github.com/hashicorp/packer-plugin-amazon v0.0.1
|
||||
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
|
||||
github.com/hashicorp/packer-plugin-vagrant v0.0.1
|
||||
github.com/hashicorp/vault/api v1.0.4
|
||||
github.com/hetznercloud/hcloud-go v1.15.1
|
||||
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4
|
||||
@@ -100,4 +101,6 @@ require (
|
||||
google.golang.org/grpc v1.32.0
|
||||
)
|
||||
|
||||
replace github.com/hashicorp/packer-plugin-vagrant => /Users/mmarsh/Projects/packer-plugin-vagrant
|
||||
|
||||
go 1.16
|
||||
|
||||
@@ -82,6 +82,7 @@ github.com/NaverCloudPlatform/ncloud-sdk-go-v2 v1.1.0/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/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-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/go.mod h1:ayPkdmEKnlssqLQ9K1BE1jlsaYhXVwkoduXI30oQF0I=
|
||||
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
|
||||
@@ -126,6 +127,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.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-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/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=
|
||||
@@ -185,8 +210,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.43.1 h1:Lhr0UOfg3t3Y56yh1DsYCjQuUHqFvsC8iUVqvub8+0Q=
|
||||
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.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/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
@@ -270,6 +296,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.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.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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
|
||||
@@ -401,6 +428,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.20210217093213-201869d627bf/go.mod h1:+EWPPcqee4h8S/y913Dnta1eJkgiqsGXBQgB75A2qV0=
|
||||
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/go.mod h1:12c9msibyHdId+Mk/pCbdRb1KaLIhaNyxeJ6n8bZt30=
|
||||
github.com/hashicorp/packer-plugin-docker v0.0.7 h1:hMTrH7vrkFIjphtbbtpuzffTzSjMNgxayo2DPLz9y+c=
|
||||
@@ -414,8 +442,10 @@ 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.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.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.2/go.mod h1:KRjczE1/c9NV5Re+PXt3myJsVTI/FxEHpZjRjOH0Fug=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.1.3 h1:oHTVlgoX2piUzL54+LBo9uIMfW+L/kY7or83dDStdIY=
|
||||
github.com/hashicorp/packer-plugin-sdk v0.1.3/go.mod h1:xePpgQgQYv/bamiypx3hH9ukidxDdcN8q0R0wLi8IEQ=
|
||||
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/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
@@ -460,8 +490,9 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v0.0.0-20160131094358-f86d2e6d8a77/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.11.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg=
|
||||
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=
|
||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/cpuid v0.0.0-20160106104451-349c67577817/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/crc32 v0.0.0-20160114101742-999f3125931f/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
|
||||
github.com/klauspost/crc32 v1.2.0 h1:0VuyqOCruD33/lJ/ojXNvzVyl8Zr5zdTmj9l9qLZ86I=
|
||||
|
||||
@@ -59,6 +59,8 @@ type Config struct {
|
||||
ImageLabels map[string]string `mapstructure:"image_labels"`
|
||||
//The unique name of the resulting image.
|
||||
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
|
||||
//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
|
||||
@@ -182,7 +184,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, artifa
|
||||
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 {
|
||||
return nil, false, false, err
|
||||
}
|
||||
@@ -295,7 +297,7 @@ func UploadToBucket(opts option.ClientOption, ui packersdk.Ui, artifact packersd
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
@@ -319,6 +321,7 @@ func CreateGceImage(opts option.ClientOption, ui packersdk.Ui, project string, r
|
||||
RawDisk: &compute.ImageRawDisk{Source: rawImageURL},
|
||||
SourceType: "RAW",
|
||||
ShieldedInstanceInitialState: shieldedVMStateConfig,
|
||||
StorageLocations: imageStorageLocations,
|
||||
}
|
||||
|
||||
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"`
|
||||
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"`
|
||||
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"`
|
||||
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"`
|
||||
@@ -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_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_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},
|
||||
"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},
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package vagrantcloud
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_ImplementsArtifact(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Artifact{}
|
||||
if _, ok := raw.(packersdk.Artifact); !ok {
|
||||
t.Fatalf("Artifact should be a Artifact")
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package vagrantcloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVagranCloudErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
resp string
|
||||
expected string
|
||||
}{
|
||||
{`{"Status":"422 Unprocessable Entity", "StatusCode":422, "errors":[]}`, ""},
|
||||
{`{"Status":"404 Artifact not found", "StatusCode":404, "errors":["error1", "error2"]}`, "error1. error2"},
|
||||
{`{"StatusCode":403, "errors":[{"message":"Bad credentials"}]}`, "message Bad credentials"},
|
||||
{`{"StatusCode":500, "errors":[["error in unexpected format"]]}`, "[error in unexpected format]"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
var cloudErrors VagrantCloudErrors
|
||||
err := json.NewDecoder(strings.NewReader(tc.resp)).Decode(&cloudErrors)
|
||||
if err != nil {
|
||||
t.Errorf("failed to decode error response: %s", err)
|
||||
}
|
||||
if got := cloudErrors.FormatErrors(); got != tc.expected {
|
||||
t.Errorf("failed to get expected response; expected %q, got %q", tc.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,850 +0,0 @@
|
||||
package vagrantcloud
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type stubResponse struct {
|
||||
Path string
|
||||
Method string
|
||||
Response string
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
type tarFiles []struct {
|
||||
Name, Body string
|
||||
}
|
||||
|
||||
func testGoodConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"access_token": "foo",
|
||||
"version_description": "bar",
|
||||
"box_tag": "hashicorp/precise64",
|
||||
"version": "0.5",
|
||||
}
|
||||
}
|
||||
|
||||
func testBadConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"access_token": "foo",
|
||||
"box_tag": "baz",
|
||||
"version_description": "bar",
|
||||
}
|
||||
}
|
||||
|
||||
func testNoAccessTokenProvidedConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"box_tag": "baz",
|
||||
"version_description": "bar",
|
||||
"version": "0.5",
|
||||
}
|
||||
}
|
||||
|
||||
func newStackServer(stack []stubResponse) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if len(stack) < 1 {
|
||||
rw.Header().Add("Error", fmt.Sprintf("Request stack is empty - Method: %s Path: %s", req.Method, req.URL.Path))
|
||||
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
match := stack[0]
|
||||
stack = stack[1:]
|
||||
if match.Method != "" && req.Method != match.Method {
|
||||
rw.Header().Add("Error", fmt.Sprintf("Request %s != %s", match.Method, req.Method))
|
||||
http.Error(rw, fmt.Sprintf("Request %s != %s", match.Method, req.Method), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if match.Path != "" && match.Path != req.URL.Path {
|
||||
rw.Header().Add("Error", fmt.Sprintf("Request %s != %s", match.Path, req.URL.Path))
|
||||
http.Error(rw, fmt.Sprintf("Request %s != %s", match.Path, req.URL.Path), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rw.Header().Add("Complete", fmt.Sprintf("Method: %s Path: %s", match.Method, match.Path))
|
||||
rw.WriteHeader(match.StatusCode)
|
||||
if match.Response != "" {
|
||||
_, err := rw.Write([]byte(match.Response))
|
||||
if err != nil {
|
||||
panic("failed to write response: " + err.Error())
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func newSecureServer(token string, handler http.HandlerFunc) *httptest.Server {
|
||||
token = fmt.Sprintf("Bearer %s", token)
|
||||
return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Header.Get("authorization") != token {
|
||||
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if handler != nil {
|
||||
handler(rw, req)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func newSelfSignedSslServer(token string, handler http.HandlerFunc) *httptest.Server {
|
||||
token = fmt.Sprintf("Bearer %s", token)
|
||||
return httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Header.Get("authorization") != token {
|
||||
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if handler != nil {
|
||||
handler(rw, req)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func newNoAuthServer(handler http.HandlerFunc) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Header.Get("authorization") != "" {
|
||||
http.Error(rw, "Authorization header was provider", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if handler != nil {
|
||||
handler(rw, req)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func TestPostProcessor_Insecure_Ssl(t *testing.T) {
|
||||
var p PostProcessor
|
||||
server := newSelfSignedSslServer("foo", nil)
|
||||
defer server.Close()
|
||||
|
||||
config := testGoodConfig()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
config["insecure_skip_tls_verify"] = true
|
||||
if err := p.Configure(config); err != nil {
|
||||
t.Fatalf("Expected TLS to skip certificate validation: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_Configure_fromVagrantEnv(t *testing.T) {
|
||||
var p PostProcessor
|
||||
config := testGoodConfig()
|
||||
server := newSecureServer("bar", nil)
|
||||
defer server.Close()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
config["access_token"] = ""
|
||||
os.Setenv("VAGRANT_CLOUD_TOKEN", "bar")
|
||||
defer func() {
|
||||
os.Setenv("VAGRANT_CLOUD_TOKEN", "")
|
||||
}()
|
||||
|
||||
if err := p.Configure(config); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.AccessToken != "bar" {
|
||||
t.Fatalf("Expected to get token from VAGRANT_CLOUD_TOKEN env var. Got '%s' instead",
|
||||
p.config.AccessToken)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_Configure_fromAtlasEnv(t *testing.T) {
|
||||
var p PostProcessor
|
||||
config := testGoodConfig()
|
||||
config["access_token"] = ""
|
||||
server := newSecureServer("foo", nil)
|
||||
defer server.Close()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
os.Setenv("ATLAS_TOKEN", "foo")
|
||||
defer func() {
|
||||
os.Setenv("ATLAS_TOKEN", "")
|
||||
}()
|
||||
|
||||
if err := p.Configure(config); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if p.config.AccessToken != "foo" {
|
||||
t.Fatalf("Expected to get token from ATLAS_TOKEN env var. Got '%s' instead",
|
||||
p.config.AccessToken)
|
||||
}
|
||||
|
||||
if !p.warnAtlasToken {
|
||||
t.Fatal("Expected warn flag to be set when getting token from atlas env var.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_Configure_Good(t *testing.T) {
|
||||
config := testGoodConfig()
|
||||
server := newSecureServer("foo", nil)
|
||||
defer server.Close()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
var p PostProcessor
|
||||
if err := p.Configure(config); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_Configure_Bad(t *testing.T) {
|
||||
config := testBadConfig()
|
||||
server := newSecureServer("foo", nil)
|
||||
defer server.Close()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
var p PostProcessor
|
||||
if err := p.Configure(config); err == nil {
|
||||
t.Fatalf("should have err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_Configure_checkAccessTokenIsRequiredByDefault(t *testing.T) {
|
||||
var p PostProcessor
|
||||
server := newSecureServer("foo", nil)
|
||||
defer server.Close()
|
||||
|
||||
config := testNoAccessTokenProvidedConfig()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
if err := p.Configure(config); err == nil {
|
||||
t.Fatalf("Expected access token to be required.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_Configure_checkAccessTokenIsNotRequiredForOverridenVagrantCloud(t *testing.T) {
|
||||
var p PostProcessor
|
||||
server := newNoAuthServer(nil)
|
||||
defer server.Close()
|
||||
|
||||
config := testNoAccessTokenProvidedConfig()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
if err := p.Configure(config); err != nil {
|
||||
t.Fatalf("Expected blank access token to be allowed and authenticate to pass: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_PostProcess_checkArtifactType(t *testing.T) {
|
||||
artifact := &packersdk.MockArtifact{
|
||||
BuilderIdValue: "invalid.builder",
|
||||
}
|
||||
|
||||
config := testGoodConfig()
|
||||
server := newSecureServer("foo", nil)
|
||||
defer server.Close()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
var p PostProcessor
|
||||
|
||||
p.Configure(config)
|
||||
_, _, _, err := p.PostProcess(context.Background(), testUi(), artifact)
|
||||
if !strings.Contains(err.Error(), "Unknown artifact type") {
|
||||
t.Fatalf("Should error with message 'Unknown artifact type...' with BuilderId: %s", artifact.BuilderIdValue)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_PostProcess_checkArtifactFileIsBox(t *testing.T) {
|
||||
artifact := &packersdk.MockArtifact{
|
||||
BuilderIdValue: "mitchellh.post-processor.vagrant", // good
|
||||
FilesValue: []string{"invalid.boxfile"}, // should have .box extension
|
||||
}
|
||||
|
||||
config := testGoodConfig()
|
||||
server := newSecureServer("foo", nil)
|
||||
defer server.Close()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
var p PostProcessor
|
||||
|
||||
p.Configure(config)
|
||||
_, _, _, err := p.PostProcess(context.Background(), testUi(), artifact)
|
||||
if !strings.Contains(err.Error(), "Unknown files in artifact") {
|
||||
t.Fatalf("Should error with message 'Unknown files in artifact...' with artifact file: %s",
|
||||
artifact.FilesValue[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_PostProcess_uploadsAndReleases(t *testing.T) {
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", `{"provider": "virtualbox"}`},
|
||||
}
|
||||
boxfile, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
artifact := &packersdk.MockArtifact{
|
||||
BuilderIdValue: "mitchellh.post-processor.vagrant",
|
||||
FilesValue: []string{boxfile.Name()},
|
||||
}
|
||||
|
||||
s := newStackServer([]stubResponse{stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"}})
|
||||
defer s.Close()
|
||||
|
||||
stack := []stubResponse{
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"},
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`},
|
||||
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`},
|
||||
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`},
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload", Response: `{"upload_path": "` + s.URL + `/box-upload-path"}`},
|
||||
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box/hashicorp/precise64/version/0.5/release"},
|
||||
}
|
||||
|
||||
server := newStackServer(stack)
|
||||
defer server.Close()
|
||||
config := testGoodConfig()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
config["no_direct_upload"] = true
|
||||
|
||||
var p PostProcessor
|
||||
|
||||
err = p.Configure(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
_, _, _, err = p.PostProcess(context.Background(), testUi(), artifact)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_PostProcess_uploadsAndNoRelease(t *testing.T) {
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", `{"provider": "virtualbox"}`},
|
||||
}
|
||||
boxfile, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
artifact := &packersdk.MockArtifact{
|
||||
BuilderIdValue: "mitchellh.post-processor.vagrant",
|
||||
FilesValue: []string{boxfile.Name()},
|
||||
}
|
||||
|
||||
s := newStackServer([]stubResponse{stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"}})
|
||||
defer s.Close()
|
||||
|
||||
stack := []stubResponse{
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"},
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`},
|
||||
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`},
|
||||
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`},
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload", Response: `{"upload_path": "` + s.URL + `/box-upload-path"}`},
|
||||
}
|
||||
|
||||
server := newStackServer(stack)
|
||||
defer server.Close()
|
||||
config := testGoodConfig()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
config["no_direct_upload"] = true
|
||||
config["no_release"] = true
|
||||
|
||||
var p PostProcessor
|
||||
|
||||
err = p.Configure(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
_, _, _, err = p.PostProcess(context.Background(), testUi(), artifact)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_PostProcess_directUpload5GFile(t *testing.T) {
|
||||
// Disable test on Windows due to unreliable sparse file creation
|
||||
if runtime.GOOS == "windows" {
|
||||
return
|
||||
}
|
||||
|
||||
// Boxes up to 5GB are supported for direct upload so
|
||||
// set the box asset to be 5GB exactly
|
||||
fSize := int64(5368709120)
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", `{"provider": "virtualbox"}`},
|
||||
}
|
||||
f, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if err := expandFile(f, fSize); err != nil {
|
||||
t.Fatalf("failed to expand box file - %s", err)
|
||||
}
|
||||
|
||||
artifact := &packersdk.MockArtifact{
|
||||
BuilderIdValue: "mitchellh.post-processor.vagrant",
|
||||
FilesValue: []string{f.Name()},
|
||||
}
|
||||
f.Close()
|
||||
|
||||
s := newStackServer(
|
||||
[]stubResponse{
|
||||
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"},
|
||||
},
|
||||
)
|
||||
defer s.Close()
|
||||
|
||||
stack := []stubResponse{
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"},
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`},
|
||||
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`},
|
||||
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`},
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload/direct"},
|
||||
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-complete"},
|
||||
}
|
||||
|
||||
server := newStackServer(stack)
|
||||
defer server.Close()
|
||||
config := testGoodConfig()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
config["no_release"] = true
|
||||
|
||||
// Set response here so we have API server URL available
|
||||
stack[4].Response = `{"upload_path": "` + s.URL + `/box-upload-path", "callback": "` + server.URL + `/box-upload-complete"}`
|
||||
|
||||
var p PostProcessor
|
||||
|
||||
err = p.Configure(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
_, _, _, err = p.PostProcess(context.Background(), testUi(), artifact)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_PostProcess_directUploadOver5GFile(t *testing.T) {
|
||||
// Disable test on Windows due to unreliable sparse file creation
|
||||
if runtime.GOOS == "windows" {
|
||||
return
|
||||
}
|
||||
|
||||
// Boxes over 5GB are not supported for direct upload so
|
||||
// set the box asset to be one byte over 5GB
|
||||
fSize := int64(5368709121)
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", `{"provider": "virtualbox"}`},
|
||||
}
|
||||
f, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if err := expandFile(f, fSize); err != nil {
|
||||
t.Fatalf("failed to expand box file - %s", err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
artifact := &packersdk.MockArtifact{
|
||||
BuilderIdValue: "mitchellh.post-processor.vagrant",
|
||||
FilesValue: []string{f.Name()},
|
||||
}
|
||||
|
||||
s := newStackServer(
|
||||
[]stubResponse{
|
||||
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"},
|
||||
},
|
||||
)
|
||||
defer s.Close()
|
||||
|
||||
stack := []stubResponse{
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"},
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`},
|
||||
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`},
|
||||
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`},
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload", Response: `{"upload_path": "` + s.URL + `/box-upload-path"}`},
|
||||
}
|
||||
|
||||
server := newStackServer(stack)
|
||||
defer server.Close()
|
||||
config := testGoodConfig()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
config["no_release"] = true
|
||||
|
||||
var p PostProcessor
|
||||
|
||||
err = p.Configure(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
_, _, _, err = p.PostProcess(context.Background(), testUi(), artifact)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_PostProcess_uploadsDirectAndReleases(t *testing.T) {
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", `{"provider": "virtualbox"}`},
|
||||
}
|
||||
boxfile, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
artifact := &packersdk.MockArtifact{
|
||||
BuilderIdValue: "mitchellh.post-processor.vagrant",
|
||||
FilesValue: []string{boxfile.Name()},
|
||||
}
|
||||
|
||||
s := newStackServer(
|
||||
[]stubResponse{
|
||||
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-path"},
|
||||
},
|
||||
)
|
||||
defer s.Close()
|
||||
|
||||
stack := []stubResponse{
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/authenticate"},
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64", Response: `{"tag": "hashicorp/precise64"}`},
|
||||
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/versions", Response: `{}`},
|
||||
stubResponse{StatusCode: 200, Method: "POST", Path: "/box/hashicorp/precise64/version/0.5/providers", Response: `{}`},
|
||||
stubResponse{StatusCode: 200, Method: "GET", Path: "/box/hashicorp/precise64/version/0.5/provider/id/upload/direct"},
|
||||
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box-upload-complete"},
|
||||
stubResponse{StatusCode: 200, Method: "PUT", Path: "/box/hashicorp/precise64/version/0.5/release"},
|
||||
}
|
||||
|
||||
server := newStackServer(stack)
|
||||
defer server.Close()
|
||||
config := testGoodConfig()
|
||||
config["vagrant_cloud_url"] = server.URL
|
||||
|
||||
// Set response here so we have API server URL available
|
||||
stack[4].Response = `{"upload_path": "` + s.URL + `/box-upload-path", "callback": "` + server.URL + `/box-upload-complete"}`
|
||||
|
||||
var p PostProcessor
|
||||
|
||||
err = p.Configure(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
_, _, _, err = p.PostProcess(context.Background(), testUi(), artifact)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testUi() *packersdk.BasicUi {
|
||||
return &packersdk.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var _ packersdk.PostProcessor = new(PostProcessor)
|
||||
}
|
||||
|
||||
func TestProviderFromBuilderName(t *testing.T) {
|
||||
if providerFromBuilderName("foobar") != "foobar" {
|
||||
t.Fatal("should copy unknown provider")
|
||||
}
|
||||
|
||||
if providerFromBuilderName("vmware") != "vmware_desktop" {
|
||||
t.Fatal("should convert provider")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderFromVagrantBox_missing_box(t *testing.T) {
|
||||
// Bad: Box does not exist
|
||||
boxfile := "i_dont_exist.box"
|
||||
_, err := providerFromVagrantBox(boxfile)
|
||||
if err == nil {
|
||||
t.Fatal("Should have error as box file does not exist")
|
||||
}
|
||||
t.Logf("%s", err)
|
||||
}
|
||||
|
||||
func TestProviderFromVagrantBox_empty_box(t *testing.T) {
|
||||
// Bad: Empty box file
|
||||
boxfile, err := newBoxFile()
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
_, err = providerFromVagrantBox(boxfile.Name())
|
||||
if err == nil {
|
||||
t.Fatal("Should have error as box file is empty")
|
||||
}
|
||||
t.Logf("%s", err)
|
||||
}
|
||||
|
||||
func TestProviderFromVagrantBox_gzip_only_box(t *testing.T) {
|
||||
boxfile, err := newBoxFile()
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
// Bad: Box is just a plain gzip file
|
||||
aw := gzip.NewWriter(boxfile)
|
||||
_, err = aw.Write([]byte("foo content"))
|
||||
if err != nil {
|
||||
t.Fatal("Error zipping test box file")
|
||||
}
|
||||
aw.Close() // Flush the gzipped contents to file
|
||||
|
||||
_, err = providerFromVagrantBox(boxfile.Name())
|
||||
if err == nil {
|
||||
t.Fatalf("Should have error as box file is a plain gzip file: %s", err)
|
||||
}
|
||||
t.Logf("%s", err)
|
||||
}
|
||||
|
||||
func TestProviderFromVagrantBox_no_files_in_archive(t *testing.T) {
|
||||
// Bad: Box contains no files
|
||||
boxfile, err := createBox(tarFiles{})
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating test box: %s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
_, err = providerFromVagrantBox(boxfile.Name())
|
||||
if err == nil {
|
||||
t.Fatalf("Should have error as box file has no contents")
|
||||
}
|
||||
t.Logf("%s", err)
|
||||
}
|
||||
|
||||
func TestProviderFromVagrantBox_no_metadata(t *testing.T) {
|
||||
// Bad: Box contains no metadata/metadata.json file
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
}
|
||||
boxfile, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating test box: %s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
_, err = providerFromVagrantBox(boxfile.Name())
|
||||
if err == nil {
|
||||
t.Fatalf("Should have error as box file does not include metadata.json file")
|
||||
}
|
||||
t.Logf("%s", err)
|
||||
}
|
||||
|
||||
func TestProviderFromVagrantBox_metadata_empty(t *testing.T) {
|
||||
// Bad: Create a box with an empty metadata.json file
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", ""},
|
||||
}
|
||||
boxfile, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating test box: %s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
_, err = providerFromVagrantBox(boxfile.Name())
|
||||
if err == nil {
|
||||
t.Fatalf("Should have error as box files metadata.json file is empty")
|
||||
}
|
||||
t.Logf("%s", err)
|
||||
}
|
||||
|
||||
func TestProviderFromVagrantBox_metadata_bad_json(t *testing.T) {
|
||||
// Bad: Create a box with bad JSON in the metadata.json file
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", "{provider: badjson}"},
|
||||
}
|
||||
boxfile, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating test box: %s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
_, err = providerFromVagrantBox(boxfile.Name())
|
||||
if err == nil {
|
||||
t.Fatalf("Should have error as box files metadata.json file contains badly formatted JSON")
|
||||
}
|
||||
t.Logf("%s", err)
|
||||
}
|
||||
|
||||
func TestProviderFromVagrantBox_metadata_no_provider_key(t *testing.T) {
|
||||
// Bad: Create a box with no 'provider' key in the metadata.json file
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", `{"cows":"moo"}`},
|
||||
}
|
||||
boxfile, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating test box: %s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
_, err = providerFromVagrantBox(boxfile.Name())
|
||||
if err == nil {
|
||||
t.Fatalf("Should have error as provider key/value pair is missing from boxes metadata.json file")
|
||||
}
|
||||
t.Logf("%s", err)
|
||||
}
|
||||
|
||||
func TestProviderFromVagrantBox_metadata_provider_value_empty(t *testing.T) {
|
||||
// Bad: The boxes metadata.json file 'provider' key has an empty value
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", `{"provider":""}`},
|
||||
}
|
||||
boxfile, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating test box: %s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
_, err = providerFromVagrantBox(boxfile.Name())
|
||||
if err == nil {
|
||||
t.Fatalf("Should have error as value associated with 'provider' key in boxes metadata.json file is empty")
|
||||
}
|
||||
t.Logf("%s", err)
|
||||
}
|
||||
|
||||
func TestProviderFromVagrantBox_metadata_ok(t *testing.T) {
|
||||
// Good: The boxes metadata.json file has the 'provider' key/value pair
|
||||
expectedProvider := "virtualbox"
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", `{"provider":"` + expectedProvider + `"}`},
|
||||
}
|
||||
boxfile, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating test box: %s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
provider, err := providerFromVagrantBox(boxfile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("error getting provider from vagrant box %s:%v", boxfile.Name(), err)
|
||||
}
|
||||
assert.Equal(t, expectedProvider, provider, "Error: Expected provider: '%s'. Got '%s'", expectedProvider, provider)
|
||||
t.Logf("Expected provider '%s'. Got provider '%s'", expectedProvider, provider)
|
||||
}
|
||||
|
||||
func TestGetProvider_artifice(t *testing.T) {
|
||||
expectedProvider := "virtualbox"
|
||||
files := tarFiles{
|
||||
{"foo.txt", "This is a foo file"},
|
||||
{"bar.txt", "This is a bar file"},
|
||||
{"metadata.json", `{"provider":"` + expectedProvider + `"}`},
|
||||
}
|
||||
boxfile, err := createBox(files)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating test box: %s", err)
|
||||
}
|
||||
defer os.Remove(boxfile.Name())
|
||||
|
||||
provider, err := getProvider("", boxfile.Name(), "artifice")
|
||||
if err != nil {
|
||||
t.Fatalf("error getting provider %s:%v", boxfile.Name(), err)
|
||||
}
|
||||
assert.Equal(t, expectedProvider, provider, "Error: Expected provider: '%s'. Got '%s'", expectedProvider, provider)
|
||||
t.Logf("Expected provider '%s'. Got provider '%s'", expectedProvider, provider)
|
||||
}
|
||||
|
||||
func TestGetProvider_other(t *testing.T) {
|
||||
expectedProvider := "virtualbox"
|
||||
|
||||
provider, _ := getProvider(expectedProvider, "foo.box", "other")
|
||||
assert.Equal(t, expectedProvider, provider, "Error: Expected provider: '%s'. Got '%s'", expectedProvider, provider)
|
||||
t.Logf("Expected provider '%s'. Got provider '%s'", expectedProvider, provider)
|
||||
}
|
||||
|
||||
func newBoxFile() (boxfile *os.File, err error) {
|
||||
boxfile, err = ioutil.TempFile(os.TempDir(), "test*.box")
|
||||
if err != nil {
|
||||
return boxfile, fmt.Errorf("Error creating test box file: %s", err)
|
||||
}
|
||||
return boxfile, nil
|
||||
}
|
||||
|
||||
func createBox(files tarFiles) (boxfile *os.File, err error) {
|
||||
boxfile, err = newBoxFile()
|
||||
if err != nil {
|
||||
return boxfile, err
|
||||
}
|
||||
|
||||
// Box files are gzipped tar archives
|
||||
aw := gzip.NewWriter(boxfile)
|
||||
tw := tar.NewWriter(aw)
|
||||
|
||||
// Add each file to the box
|
||||
for _, file := range files {
|
||||
// Create and write the tar file header
|
||||
hdr := &tar.Header{
|
||||
Name: file.Name,
|
||||
Mode: 0644,
|
||||
Size: int64(len(file.Body)),
|
||||
}
|
||||
err = tw.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return boxfile, fmt.Errorf("Error writing box tar file header: %s", err)
|
||||
}
|
||||
// Write the file contents
|
||||
_, err = tw.Write([]byte(file.Body))
|
||||
if err != nil {
|
||||
return boxfile, fmt.Errorf("Error writing box tar file contents: %s", err)
|
||||
}
|
||||
}
|
||||
// Flush and close each writer
|
||||
err = tw.Close()
|
||||
if err != nil {
|
||||
return boxfile, fmt.Errorf("Error flushing tar file contents: %s", err)
|
||||
}
|
||||
err = aw.Close()
|
||||
if err != nil {
|
||||
return boxfile, fmt.Errorf("Error flushing gzip file contents: %s", err)
|
||||
}
|
||||
|
||||
return boxfile, nil
|
||||
}
|
||||
|
||||
func expandFile(f *os.File, finalSize int64) (err error) {
|
||||
s, err := f.Stat()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size := finalSize - s.Size()
|
||||
if size < 1 {
|
||||
return
|
||||
}
|
||||
if _, err = f.Seek(size-1, 2); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = f.Write([]byte{0}); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
||||
packerVersion "github.com/hashicorp/packer/version"
|
||||
)
|
||||
|
||||
var VagrantCloudPluginVersion *version.PluginVersion
|
||||
|
||||
func init() {
|
||||
VagrantCloudPluginVersion = version.InitializePluginVersion(
|
||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_ImplementsArtifact(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Artifact{}
|
||||
if _, ok := raw.(packersdk.Artifact); !ok {
|
||||
t.Fatalf("Artifact should be a Artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifact_Id(t *testing.T) {
|
||||
artifact := NewArtifact("vmware", "./")
|
||||
if artifact.Id() != "vmware" {
|
||||
t.Fatalf("should return name as Id")
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestAWSProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(AWSProvider)
|
||||
}
|
||||
|
||||
func TestAWSProvider_KeepInputArtifact(t *testing.T) {
|
||||
p := new(AWSProvider)
|
||||
|
||||
if !p.KeepInputArtifact() {
|
||||
t.Fatal("should keep input artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAWSProvider_ArtifactId(t *testing.T) {
|
||||
p := new(AWSProvider)
|
||||
ui := testUi()
|
||||
artifact := &packersdk.MockArtifact{
|
||||
IdValue: "us-east-1:ami-1234",
|
||||
}
|
||||
|
||||
vagrantfile, _, err := p.Process(ui, artifact, "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
result := `aws.region_config "us-east-1", ami: "ami-1234"`
|
||||
if !strings.Contains(vagrantfile, result) {
|
||||
t.Fatalf("wrong substitution: %s", vagrantfile)
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestAzureProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(AzureProvider)
|
||||
}
|
||||
|
||||
func TestAzureProvider_KeepInputArtifact(t *testing.T) {
|
||||
p := new(AzureProvider)
|
||||
|
||||
if !p.KeepInputArtifact() {
|
||||
t.Fatal("should keep input artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAzureProvider_ManagedImage(t *testing.T) {
|
||||
p := new(AzureProvider)
|
||||
ui := testUi()
|
||||
artifact := &packersdk.MockArtifact{
|
||||
StringValue: `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
ManagedImageResourceGroupName: packerruns
|
||||
ManagedImageName: packer-1533651633
|
||||
ManagedImageId: /subscriptions/e6229913-d9c3-4ddd-99a4-9e1ef3beaa1b/resourceGroups/packerruns/providers/Microsoft.Compute/images/packer-1533675589
|
||||
ManagedImageLocation: westus`,
|
||||
}
|
||||
|
||||
vagrantfile, _, err := p.Process(ui, artifact, "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
result := `azure.location = "westus"`
|
||||
if !strings.Contains(vagrantfile, result) {
|
||||
t.Fatalf("wrong substitution: %s", vagrantfile)
|
||||
}
|
||||
result = `azure.vm_managed_image_id = "/subscriptions/e6229913-d9c3-4ddd-99a4-9e1ef3beaa1b/resourceGroups/packerruns/providers/Microsoft.Compute/images/packer-1533675589"`
|
||||
if !strings.Contains(vagrantfile, result) {
|
||||
t.Fatalf("wrong substitution: %s", vagrantfile)
|
||||
}
|
||||
// DO NOT set resource group in Vagrantfile, it should be separate from the image
|
||||
result = `azure.resource_group_name`
|
||||
if strings.Contains(vagrantfile, result) {
|
||||
t.Fatalf("wrong substitution: %s", vagrantfile)
|
||||
}
|
||||
result = `azure.vm_operating_system`
|
||||
if strings.Contains(vagrantfile, result) {
|
||||
t.Fatalf("wrong substitution: %s", vagrantfile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAzureProvider_VHD(t *testing.T) {
|
||||
p := new(AzureProvider)
|
||||
ui := testUi()
|
||||
artifact := &packersdk.MockArtifact{
|
||||
IdValue: "https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd",
|
||||
StringValue: `Azure.ResourceManagement.VMImage:
|
||||
|
||||
OSType: Linux
|
||||
StorageAccountLocation: westus
|
||||
OSDiskUri: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd
|
||||
OSDiskUriReadOnlySas: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd?se=2018-09-07T18%3A36%3A34Z&sig=xUiFvwAviPYoP%2Bc91vErqvwYR1eK4x%2BAx7YLMe84zzU%3D&sp=r&sr=b&sv=2016-05-31
|
||||
TemplateUri: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.96ed2120-591d-4900-95b0-ee8e985f2213.json
|
||||
TemplateUriReadOnlySas: https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-vmTemplate.96ed2120-591d-4900-95b0-ee8e985f2213.json?se=2018-09-07T18%3A36%3A34Z&sig=lDxePyAUCZbfkB5ddiofimXfwk5INn%2F9E2BsnqIKC9Q%3D&sp=r&sr=b&sv=2016-05-31`,
|
||||
}
|
||||
|
||||
vagrantfile, _, err := p.Process(ui, artifact, "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
result := `azure.location = "westus"`
|
||||
if !strings.Contains(vagrantfile, result) {
|
||||
t.Fatalf("wrong substitution: %s", vagrantfile)
|
||||
}
|
||||
result = `azure.vm_vhd_uri = "https://packerbuildswest.blob.core.windows.net/system/Microsoft.Compute/Images/images/packer-osDisk.96ed2120-591d-4900-95b0-ee8e985f2213.vhd"`
|
||||
if !strings.Contains(vagrantfile, result) {
|
||||
t.Fatalf("wrong substitution: %s", vagrantfile)
|
||||
}
|
||||
result = `azure.vm_operating_system = "Linux"`
|
||||
if !strings.Contains(vagrantfile, result) {
|
||||
t.Fatalf("wrong substitution: %s", vagrantfile)
|
||||
}
|
||||
// DO NOT set resource group in Vagrantfile, it should be separate from the image
|
||||
result = `azure.resource_group_name`
|
||||
if strings.Contains(vagrantfile, result) {
|
||||
t.Fatalf("wrong substitution: %s", vagrantfile)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestDigitalOceanProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(DigitalOceanProvider)
|
||||
}
|
||||
|
||||
func TestDigitalOceanProvider_KeepInputArtifact(t *testing.T) {
|
||||
p := new(DigitalOceanProvider)
|
||||
|
||||
if !p.KeepInputArtifact() {
|
||||
t.Fatal("should keep input artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigitalOceanProvider_ArtifactId(t *testing.T) {
|
||||
p := new(DigitalOceanProvider)
|
||||
ui := testUi()
|
||||
artifact := &packersdk.MockArtifact{
|
||||
IdValue: "San Francisco:42",
|
||||
}
|
||||
|
||||
vagrantfile, _, err := p.Process(ui, artifact, "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
image := `digital_ocean.image = "42"`
|
||||
if !strings.Contains(vagrantfile, image) {
|
||||
t.Fatalf("wrong image substitution: %s", vagrantfile)
|
||||
}
|
||||
region := `digital_ocean.region = "San Francisco"`
|
||||
if !strings.Contains(vagrantfile, region) {
|
||||
t.Fatalf("wrong region substitution: %s", vagrantfile)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDockerProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(DockerProvider)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func TestGoogleProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(GoogleProvider)
|
||||
}
|
||||
|
||||
func TestGoogleProvider_KeepInputArtifact(t *testing.T) {
|
||||
p := new(GoogleProvider)
|
||||
|
||||
if !p.KeepInputArtifact() {
|
||||
t.Fatal("should keep input artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoogleProvider_ArtifactId(t *testing.T) {
|
||||
p := new(GoogleProvider)
|
||||
ui := testUi()
|
||||
artifact := &packersdk.MockArtifact{
|
||||
IdValue: "packer-1234",
|
||||
}
|
||||
|
||||
vagrantfile, _, err := p.Process(ui, artifact, "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
result := `google.image = "packer-1234"`
|
||||
if !strings.Contains(vagrantfile, result) {
|
||||
t.Fatalf("wrong substitution: %s", vagrantfile)
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertSizeInMegabytes(t *testing.T, size string, expected uint64) {
|
||||
actual := sizeInMegabytes(size)
|
||||
if actual != expected {
|
||||
t.Fatalf("the size `%s` was converted to `%d` but expected `%d`", size, actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sizeInMegabytes_WithInvalidUnitMustPanic(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Fatalf("expected a panic but got none")
|
||||
}
|
||||
}()
|
||||
|
||||
sizeInMegabytes("1234x")
|
||||
}
|
||||
|
||||
func Test_sizeInMegabytes_WithoutUnitMustDefaultToMegabytes(t *testing.T) {
|
||||
assertSizeInMegabytes(t, "1234", 1234)
|
||||
}
|
||||
|
||||
func Test_sizeInMegabytes_WithBytesUnit(t *testing.T) {
|
||||
assertSizeInMegabytes(t, fmt.Sprintf("%db", 1234*1024*1024), 1234)
|
||||
assertSizeInMegabytes(t, fmt.Sprintf("%dB", 1234*1024*1024), 1234)
|
||||
assertSizeInMegabytes(t, "1B", 0)
|
||||
}
|
||||
|
||||
func Test_sizeInMegabytes_WithKiloBytesUnit(t *testing.T) {
|
||||
assertSizeInMegabytes(t, fmt.Sprintf("%dk", 1234*1024), 1234)
|
||||
assertSizeInMegabytes(t, fmt.Sprintf("%dK", 1234*1024), 1234)
|
||||
assertSizeInMegabytes(t, "1K", 0)
|
||||
}
|
||||
|
||||
func Test_sizeInMegabytes_WithMegabytesUnit(t *testing.T) {
|
||||
assertSizeInMegabytes(t, "1234m", 1234)
|
||||
assertSizeInMegabytes(t, "1234M", 1234)
|
||||
assertSizeInMegabytes(t, "1M", 1)
|
||||
}
|
||||
|
||||
func Test_sizeInMegabytes_WithGigabytesUnit(t *testing.T) {
|
||||
assertSizeInMegabytes(t, "1234g", 1234*1024)
|
||||
assertSizeInMegabytes(t, "1234G", 1234*1024)
|
||||
assertSizeInMegabytes(t, "1G", 1*1024)
|
||||
}
|
||||
|
||||
func Test_sizeInMegabytes_WithTerabytesUnit(t *testing.T) {
|
||||
assertSizeInMegabytes(t, "1234t", 1234*1024*1024)
|
||||
assertSizeInMegabytes(t, "1234T", 1234*1024*1024)
|
||||
assertSizeInMegabytes(t, "1T", 1*1024*1024)
|
||||
}
|
||||
|
||||
func Test_sizeInMegabytes_WithPetabytesUnit(t *testing.T) {
|
||||
assertSizeInMegabytes(t, "1234p", 1234*1024*1024*1024)
|
||||
assertSizeInMegabytes(t, "1234P", 1234*1024*1024*1024)
|
||||
assertSizeInMegabytes(t, "1P", 1*1024*1024*1024)
|
||||
}
|
||||
|
||||
func Test_sizeInMegabytes_WithExabytesUnit(t *testing.T) {
|
||||
assertSizeInMegabytes(t, "1234e", 1234*1024*1024*1024*1024)
|
||||
assertSizeInMegabytes(t, "1234E", 1234*1024*1024*1024*1024)
|
||||
assertSizeInMegabytes(t, "1E", 1*1024*1024*1024*1024)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLXCProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(LXCProvider)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParallelsProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(ParallelsProvider)
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func testPP(t *testing.T) *PostProcessor {
|
||||
var p PostProcessor
|
||||
if err := p.Configure(testConfig()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func testUi() *packersdk.BasicUi {
|
||||
return &packersdk.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var _ packersdk.PostProcessor = new(PostProcessor)
|
||||
}
|
||||
|
||||
func TestPostProcessorPrepare_compressionLevel(t *testing.T) {
|
||||
var p PostProcessor
|
||||
|
||||
// Default
|
||||
c := testConfig()
|
||||
delete(c, "compression_level")
|
||||
if err := p.Configure(c); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config := p.config
|
||||
if config.CompressionLevel != flate.DefaultCompression {
|
||||
t.Fatalf("bad: %#v", config.CompressionLevel)
|
||||
}
|
||||
|
||||
// Set
|
||||
c = testConfig()
|
||||
c["compression_level"] = 7
|
||||
if err := p.Configure(c); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
config = p.config
|
||||
if config.CompressionLevel != 7 {
|
||||
t.Fatalf("bad: %#v", config.CompressionLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessorPrepare_outputPath(t *testing.T) {
|
||||
var p PostProcessor
|
||||
|
||||
// Default
|
||||
c := testConfig()
|
||||
delete(c, "output")
|
||||
err := p.Configure(c)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Bad template
|
||||
c["output"] = "bad {{{{.Template}}}}"
|
||||
err = p.Configure(c)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecificConfig(t *testing.T) {
|
||||
var p PostProcessor
|
||||
|
||||
// Default
|
||||
c := testConfig()
|
||||
c["compression_level"] = 1
|
||||
c["output"] = "folder"
|
||||
c["override"] = map[string]interface{}{
|
||||
"aws": map[string]interface{}{
|
||||
"compression_level": 7,
|
||||
},
|
||||
}
|
||||
if err := p.Configure(c); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// overrides config
|
||||
config, err := p.specificConfig("aws")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if config.CompressionLevel != 7 {
|
||||
t.Fatalf("bad: %#v", config.CompressionLevel)
|
||||
}
|
||||
|
||||
if config.OutputPath != "folder" {
|
||||
t.Fatalf("bad: %#v", config.OutputPath)
|
||||
}
|
||||
|
||||
// does NOT overrides config
|
||||
config, err = p.specificConfig("virtualbox")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if config.CompressionLevel != 1 {
|
||||
t.Fatalf("bad: %#v", config.CompressionLevel)
|
||||
}
|
||||
|
||||
if config.OutputPath != "folder" {
|
||||
t.Fatalf("bad: %#v", config.OutputPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessorPrepare_vagrantfileTemplateExists(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
name := f.Name()
|
||||
c := testConfig()
|
||||
c["vagrantfile_template"] = name
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
var p PostProcessor
|
||||
|
||||
if err := p.Configure(c); err != nil {
|
||||
t.Fatal("no error expected as vagrantfile_template exists")
|
||||
}
|
||||
|
||||
if err := os.Remove(name); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := p.Configure(c); err == nil {
|
||||
t.Fatal("expected error since vagrantfile_template does not exist and vagrantfile_template_generated is unset")
|
||||
}
|
||||
|
||||
// The vagrantfile_template will be generated during the build process
|
||||
c["vagrantfile_template_generated"] = true
|
||||
|
||||
if err := p.Configure(c); err != nil {
|
||||
t.Fatal("no error expected due to missing vagrantfile_template as vagrantfile_template_generated is set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessorPrepare_ProviderOverrideExists(t *testing.T) {
|
||||
c := testConfig()
|
||||
c["provider_override"] = "foo"
|
||||
|
||||
var p PostProcessor
|
||||
|
||||
if err := p.Configure(c); err == nil {
|
||||
t.Fatal("Should have errored since foo is not a valid vagrant provider")
|
||||
}
|
||||
|
||||
c = testConfig()
|
||||
c["provider_override"] = "aws"
|
||||
|
||||
if err := p.Configure(c); err != nil {
|
||||
t.Fatal("Should not have errored since aws is a valid vagrant provider")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessorPostProcess_badId(t *testing.T) {
|
||||
artifact := &packersdk.MockArtifact{
|
||||
BuilderIdValue: "invalid.packer",
|
||||
}
|
||||
|
||||
_, _, _, err := testPP(t).PostProcess(context.Background(), testUi(), artifact)
|
||||
if !strings.Contains(err.Error(), "artifact type") {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessorPostProcess_vagrantfileUserVariable(t *testing.T) {
|
||||
var p PostProcessor
|
||||
|
||||
f, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
c := map[string]interface{}{
|
||||
"packer_user_variables": map[string]string{
|
||||
"foo": f.Name(),
|
||||
},
|
||||
|
||||
"vagrantfile_template": "{{user `foo`}}",
|
||||
}
|
||||
err = p.Configure(c)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
a := &packersdk.MockArtifact{
|
||||
BuilderIdValue: "packer.parallels",
|
||||
}
|
||||
a2, _, _, err := p.PostProcess(context.Background(), testUi(), a)
|
||||
if a2 != nil {
|
||||
for _, fn := range a2.Files() {
|
||||
defer os.Remove(fn)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderForName(t *testing.T) {
|
||||
if v, ok := providerForName("virtualbox").(*VBoxProvider); !ok {
|
||||
t.Fatalf("bad: %#v", v)
|
||||
}
|
||||
|
||||
if providerForName("nope") != nil {
|
||||
t.Fatal("should be nil if bad provider")
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,13 +0,0 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer-plugin-sdk/version"
|
||||
packerVersion "github.com/hashicorp/packer/version"
|
||||
)
|
||||
|
||||
var VagrantPostprocessorVersion *version.PluginVersion
|
||||
|
||||
func init() {
|
||||
VagrantPostprocessorVersion = version.InitializePluginVersion(
|
||||
packerVersion.Version, packerVersion.VersionPrerelease)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-sdk/tmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVBoxProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(VBoxProvider)
|
||||
}
|
||||
|
||||
func TestDecomressOVA(t *testing.T) {
|
||||
td, err := tmp.Dir("pp-vagrant-virtualbox")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
fixture := "./test-fixtures/decompress-tar/outside_parent.tar"
|
||||
err = DecompressOva(td, fixture)
|
||||
assert.NoError(t, err)
|
||||
_, err = os.Stat(filepath.Join(filepath.Base(td), "demo.poc"))
|
||||
assert.Error(t, err)
|
||||
_, err = os.Stat(filepath.Join(td, "demo.poc"))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVMwareProvider_impl(t *testing.T) {
|
||||
var _ Provider = new(VMwareProvider)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
//go:generate mapstructure-to-hcl2 -type Config
|
||||
//go:generate struct-markdown
|
||||
|
||||
package ansiblelocal
|
||||
|
||||
@@ -23,51 +24,126 @@ const DefaultStagingDir = "/tmp/packer-provisioner-ansible-local"
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
ctx interpolate.Context
|
||||
|
||||
// The command to run ansible
|
||||
Command string
|
||||
|
||||
// Extra options to pass to the ansible command
|
||||
// The command to invoke ansible. Defaults to
|
||||
// `ansible-playbook`. If you would like to provide a more complex command,
|
||||
// for example, something that sets up a virtual environment before calling
|
||||
// ansible, take a look at the ansible wrapper guide below for inspiration.
|
||||
// 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"`
|
||||
|
||||
// Path to group_vars directory
|
||||
// A path to the directory containing ansible group
|
||||
// variables on your local system to be copied to the remote machine. By
|
||||
// default, this is empty.
|
||||
GroupVars string `mapstructure:"group_vars"`
|
||||
|
||||
// Path to host_vars directory
|
||||
// A path to the directory containing ansible host variables on your local
|
||||
// system to be copied to the remote machine. By default, this is empty.
|
||||
HostVars string `mapstructure:"host_vars"`
|
||||
|
||||
// The playbook dir to upload.
|
||||
// A path to the complete ansible directory structure on your local system
|
||||
// to be copied to the remote machine as the `staging_directory` before all
|
||||
// other files and directories.
|
||||
PlaybookDir string `mapstructure:"playbook_dir"`
|
||||
|
||||
// The main playbook file to execute.
|
||||
// The playbook file to be executed by ansible. This file must exist on your
|
||||
// local system and will be uploaded to the remote machine. This option is
|
||||
// exclusive with `playbook_files`.
|
||||
PlaybookFile string `mapstructure:"playbook_file"`
|
||||
|
||||
// The playbook files to execute.
|
||||
// The playbook files to be executed by ansible. These files must exist on
|
||||
// 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"`
|
||||
|
||||
// An array of local paths of playbook files to upload.
|
||||
// An array of directories of playbook files on your local system. These
|
||||
// will be uploaded to the remote machine under `staging_directory`/playbooks.
|
||||
// By default, this is empty.
|
||||
PlaybookPaths []string `mapstructure:"playbook_paths"`
|
||||
|
||||
// An array of local paths of roles to upload.
|
||||
// An array of paths to role directories on your local system. These will be
|
||||
// uploaded to the remote machine under `staging_directory`/roles. By default,
|
||||
// this is empty.
|
||||
RolePaths []string `mapstructure:"role_paths"`
|
||||
|
||||
// The directory where files will be uploaded. Packer requires write
|
||||
// permissions in this directory.
|
||||
// The directory where all the configuration of Ansible by Packer will be placed.
|
||||
// By default this is `/tmp/packer-provisioner-ansible-local/<uuid>`, where
|
||||
// `<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"`
|
||||
|
||||
// If true, staging directory is removed after executing ansible.
|
||||
// If set to `true`, the content of the `staging_directory` will be removed after
|
||||
// executing ansible. By default this is set to `false`.
|
||||
CleanStagingDir bool `mapstructure:"clean_staging_directory"`
|
||||
|
||||
// The optional inventory file
|
||||
// The inventory file to be used by ansible. This
|
||||
// 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"`
|
||||
|
||||
// The optional inventory groups
|
||||
// `inventory_groups` (string) - A comma-separated list of groups to which
|
||||
// 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"`
|
||||
|
||||
// The optional ansible-galaxy requirements file
|
||||
// A requirements file which provides a way to
|
||||
// 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"`
|
||||
|
||||
// The command to run ansible-galaxy
|
||||
// The command to invoke ansible-galaxy. By default, this is
|
||||
// `ansible-galaxy`.
|
||||
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"`
|
||||
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"`
|
||||
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"`
|
||||
GroupVars *string `mapstructure:"group_vars" cty:"group_vars" hcl:"group_vars"`
|
||||
HostVars *string `mapstructure:"host_vars" cty:"host_vars" hcl:"host_vars"`
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/hashicorp/packer-plugin-docker/builder/docker"
|
||||
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
|
||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||
builderT "github.com/hashicorp/packer/acctest"
|
||||
"github.com/hashicorp/packer/provisioner/file"
|
||||
)
|
||||
|
||||
|
||||
+202
@@ -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.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
AWS SDK for Go
|
||||
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Copyright 2014-2015 Stripe, Inc.
|
||||
+92
@@ -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
|
||||
}
|
||||
+88
@@ -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
|
||||
}
|
||||
+22
@@ -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
|
||||
}
|
||||
+139
@@ -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))
|
||||
}
|
||||
+127
@@ -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)
|
||||
}
|
||||
+62
@@ -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 .
|
||||
+113
@@ -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)
|
||||
}
|
||||
+9
@@ -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"
|
||||
}
|
||||
+344
@@ -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)
|
||||
}
|
||||
+84
@@ -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
|
||||
}
|
||||
+77
@@ -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)
|
||||
}
|
||||
}
|
||||
+167
@@ -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)
|
||||
}
|
||||
+168
@@ -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{})
|
||||
}
|
||||
+27
@@ -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
@@ -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
|
||||
}
|
||||
+256
@@ -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)
|
||||
}
|
||||
+61
@@ -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)
|
||||
}
|
||||
+80
@@ -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))
|
||||
}
|
||||
+78
@@ -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
@@ -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)
|
||||
}
|
||||
+56
@@ -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)
|
||||
}
|
||||
+106
@@ -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
@@ -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
@@ -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"`
|
||||
}
|
||||
+51
@@ -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
@@ -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)
|
||||
}
|
||||
+25
@@ -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)
|
||||
}
|
||||
+80
@@ -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
|
||||
+20
@@ -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
|
||||
}
|
||||
+49
@@ -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
|
||||
}
|
||||
+52
@@ -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)
|
||||
}
|
||||
+273
@@ -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
|
||||
}
|
||||
+72
@@ -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)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user