commit
e4a189ce5c
|
@ -26,10 +26,9 @@ build_script:
|
|||
- git rev-parse HEAD
|
||||
# go test $(go list ./... | grep -v vendor)
|
||||
- ps: |
|
||||
go.exe test -timeout=2m (go.exe list ./... `
|
||||
go.exe test -v -timeout=2m (go.exe list ./... `
|
||||
|? { -not $_.Contains('/vendor/') } `
|
||||
|? { $_ -ne 'github.com/hashicorp/packer/builder/parallels/common' } `
|
||||
|? { $_ -ne 'github.com/hashicorp/packer/common' }`
|
||||
|? { $_ -ne 'github.com/hashicorp/packer/provisioner/ansible' })
|
||||
|
||||
test: off
|
||||
|
|
|
@ -76,7 +76,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
|
||||
client, err := b.config.Client()
|
||||
if err != nil {
|
||||
|
|
|
@ -190,7 +190,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warns, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil, errors.New("The amazon-chroot builder only works on Linux environments.")
|
||||
}
|
||||
|
|
|
@ -85,7 +85,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
|
||||
session, err := b.config.Session()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -100,7 +100,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
session, err := b.config.Session()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -89,7 +89,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
session, err := b.config.Session()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -170,7 +170,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
session, err := b.config.Session()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -51,7 +51,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, errs
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
|
||||
ui.Say("Running builder ...")
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
// Run implements the packer.Builder interface.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
b.ui = ui
|
||||
|
||||
// Create a CloudStack API client.
|
||||
|
|
|
@ -35,7 +35,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
client := godo.NewClient(oauth2.NewClient(oauth2.NoContext, &apiTokenSource{
|
||||
AccessToken: b.config.APIToken,
|
||||
}))
|
||||
|
|
|
@ -29,7 +29,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
driver := &DockerDriver{Ctx: &b.config.ctx, Ui: ui}
|
||||
if err := driver.Verify(); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -21,7 +21,6 @@ func TestCommunicator_impl(t *testing.T) {
|
|||
// TestUploadDownload verifies that basic upload / download functionality works
|
||||
func TestUploadDownload(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||
|
||||
tpl, err := template.Parse(strings.NewReader(dockerBuilderConfig))
|
||||
if err != nil {
|
||||
|
@ -76,7 +75,7 @@ func TestUploadDownload(t *testing.T) {
|
|||
hook := &packer.DispatchHook{Mapping: hooks}
|
||||
|
||||
// Run things
|
||||
artifact, err := builder.Run(ui, hook, cache)
|
||||
artifact, err := builder.Run(ui, hook)
|
||||
if err != nil {
|
||||
t.Fatalf("Error running build %s", err)
|
||||
}
|
||||
|
@ -105,7 +104,6 @@ func TestUploadDownload(t *testing.T) {
|
|||
// only intermittently.
|
||||
func TestLargeDownload(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||
|
||||
tpl, err := template.Parse(strings.NewReader(dockerLargeBuilderConfig))
|
||||
if err != nil {
|
||||
|
@ -166,7 +164,7 @@ func TestLargeDownload(t *testing.T) {
|
|||
hook := &packer.DispatchHook{Mapping: hooks}
|
||||
|
||||
// Run things
|
||||
artifact, err := builder.Run(ui, hook, cache)
|
||||
artifact, err := builder.Run(ui, hook)
|
||||
if err != nil {
|
||||
t.Fatalf("Error running build %s", err)
|
||||
}
|
||||
|
@ -210,7 +208,6 @@ func TestLargeDownload(t *testing.T) {
|
|||
// TestFixUploadOwner verifies that owner of uploaded files is the user the container is running as.
|
||||
func TestFixUploadOwner(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||
|
||||
tpl, err := template.Parse(strings.NewReader(testFixUploadOwnerTemplate))
|
||||
if err != nil {
|
||||
|
@ -275,7 +272,7 @@ func TestFixUploadOwner(t *testing.T) {
|
|||
}
|
||||
hook := &packer.DispatchHook{Mapping: hooks}
|
||||
|
||||
artifact, err := builder.Run(ui, hook, cache)
|
||||
artifact, err := builder.Run(ui, hook)
|
||||
if err != nil {
|
||||
t.Fatalf("Error running build %s", err)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
}
|
||||
|
||||
// Run is where the actual build should take place. It takes a Build and a Ui.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
artifact := new(FileArtifact)
|
||||
|
||||
if b.config.Source != "" {
|
||||
|
|
|
@ -33,7 +33,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
|
||||
// Run executes a googlecompute Packer build and returns a packer.Artifact
|
||||
// representing a GCE machine image.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
driver, err := NewDriverGCE(
|
||||
ui, b.config.ProjectId, &b.config.Account)
|
||||
if err != nil {
|
||||
|
|
|
@ -31,7 +31,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
opts := []hcloud.ClientOption{
|
||||
hcloud.WithToken(b.config.HCloudToken),
|
||||
hcloud.WithEndpoint(b.config.Endpoint),
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/hyperonecom/h1-client-go"
|
||||
openapi "github.com/hyperonecom/h1-client-go"
|
||||
)
|
||||
|
||||
const BuilderID = "hyperone.builder"
|
||||
|
@ -50,7 +50,7 @@ type wrappedCommandTemplate struct {
|
|||
Command string
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
wrappedCommand := func(command string) (string, error) {
|
||||
ctx := b.config.ctx
|
||||
ctx.Data = &wrappedCommandTemplate{Command: command}
|
||||
|
|
|
@ -356,7 +356,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
|
||||
// Run executes a Packer build and returns a packer.Artifact representing
|
||||
// a Hyperv appliance.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
// Create the driver that we'll use to communicate with Hyperv
|
||||
driver, err := hypervcommon.NewHypervPS4Driver()
|
||||
if err != nil {
|
||||
|
@ -365,7 +365,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
|
||||
// Set up the state.
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("cache", cache)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("debug", b.config.PackerDebug)
|
||||
state.Put("driver", driver)
|
||||
|
|
|
@ -5,7 +5,6 @@ package iso
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
@ -287,9 +286,6 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) {
|
|||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ISOChecksum != "foo" {
|
||||
t.Fatalf("should've lowercased: %s", b.config.ISOChecksum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
||||
|
@ -302,8 +298,8 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
|||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test good
|
||||
|
@ -329,7 +325,7 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
t.Log("should error in prepare but go-getter doesn't let us validate yet. This will fail before dl.")
|
||||
}
|
||||
|
||||
// Test none
|
||||
|
@ -606,13 +602,11 @@ func TestUserVariablesInBootCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
ui := packer.TestUi(t)
|
||||
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||
hook := &packer.MockHook{}
|
||||
driver := &hypervcommon.DriverMock{}
|
||||
|
||||
// Set up the state.
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("cache", cache)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("driver", driver)
|
||||
state.Put("hook", hook)
|
||||
|
|
|
@ -372,7 +372,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
|
||||
// Run executes a Packer build and returns a packer.Artifact representing
|
||||
// a Hyperv appliance.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
// Create the driver that we'll use to communicate with Hyperv
|
||||
driver, err := hypervcommon.NewHypervPS4Driver()
|
||||
if err != nil {
|
||||
|
@ -381,7 +381,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
|
||||
// Set up the state.
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("cache", cache)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("debug", b.config.PackerDebug)
|
||||
state.Put("driver", driver)
|
||||
|
|
|
@ -203,9 +203,6 @@ func TestBuilderPrepare_ISOChecksum(t *testing.T) {
|
|||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if b.config.ISOChecksum != "foo" {
|
||||
t.Fatalf("should've lowercased: %s", b.config.ISOChecksum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
||||
|
@ -226,8 +223,8 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
|||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test good
|
||||
|
@ -245,17 +242,6 @@ func TestBuilderPrepare_ISOChecksumType(t *testing.T) {
|
|||
t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType)
|
||||
}
|
||||
|
||||
// Test unknown
|
||||
config["iso_checksum_type"] = "fake"
|
||||
b = Builder{}
|
||||
warns, err = b.Prepare(config)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test none
|
||||
config["iso_checksum_type"] = "none"
|
||||
b = Builder{}
|
||||
|
@ -517,13 +503,11 @@ func TestUserVariablesInBootCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
ui := packer.TestUi(t)
|
||||
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||
hook := &packer.MockHook{}
|
||||
driver := &hypervcommon.DriverMock{}
|
||||
|
||||
// Set up the state.
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("cache", cache)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("driver", driver)
|
||||
state.Put("hook", hook)
|
||||
|
|
|
@ -33,7 +33,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
wrappedCommand := func(command string) (string, error) {
|
||||
b.config.ctx.Data = &wrappedCommandTemplate{Command: command}
|
||||
return interpolate.Render(b.config.CommandWrapper, &b.config.ctx)
|
||||
|
@ -52,7 +52,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
// Setup the state bag
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", b.config)
|
||||
state.Put("cache", cache)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
|
||||
|
|
|
@ -31,7 +31,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
wrappedCommand := func(command string) (string, error) {
|
||||
b.config.ctx.Data = &wrappedCommandTemplate{Command: command}
|
||||
return interpolate.Render(b.config.CommandWrapper, &b.config.ctx)
|
||||
|
@ -46,7 +46,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
// Setup the state bag
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", b.config)
|
||||
state.Put("cache", cache)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
state.Put("wrappedCommand", CommandWrapper(wrappedCommand))
|
||||
|
|
|
@ -27,7 +27,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
ui.Message("Creating Naver Cloud Platform Connection ...")
|
||||
conn := ncloud.NewConnection(b.config.AccessKey, b.config.SecretKey)
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
steps := []multistep.Step{}
|
||||
|
||||
if b.config.CommConfig.Type != "none" {
|
||||
|
|
|
@ -28,7 +28,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
|
||||
state := new(multistep.BasicStateBag)
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
computeClient, err := b.config.computeV2Client()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error initializing compute client: %s", err)
|
||||
|
|
|
@ -41,7 +41,7 @@ func (b *Builder) Prepare(rawConfig ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
loggingEnabled := os.Getenv("PACKER_OCI_CLASSIC_LOGGING") != ""
|
||||
httpClient := cleanhttp.DefaultClient()
|
||||
config := &opc.Config{
|
||||
|
|
|
@ -36,7 +36,7 @@ func (b *Builder) Prepare(rawConfig ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
driver, err := NewDriverOCI(b.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -141,7 +141,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
// Create the driver that we'll use to communicate with Parallels
|
||||
driver, err := parallelscommon.NewDriver()
|
||||
if err != nil {
|
||||
|
@ -229,7 +229,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
|
||||
// Setup the state bag
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("cache", cache)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("debug", b.config.PackerDebug)
|
||||
state.Put("driver", driver)
|
||||
|
|
|
@ -32,7 +32,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
|
||||
// Run executes a Packer build and returns a packer.Artifact representing
|
||||
// a Parallels appliance.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
// Create the driver that we'll use to communicate with Parallels
|
||||
driver, err := parallelscommon.NewDriver()
|
||||
if err != nil {
|
||||
|
|
|
@ -27,7 +27,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
state := new(multistep.BasicStateBag)
|
||||
|
||||
state.Put("config", b.config)
|
||||
|
|
|
@ -354,7 +354,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
// Create the driver that we'll use to communicate with Qemu
|
||||
driver, err := b.newDriver(b.config.QemuBinary)
|
||||
if err != nil {
|
||||
|
@ -448,7 +448,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
|
||||
// Setup the state bag
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("cache", cache)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("debug", b.config.PackerDebug)
|
||||
state.Put("driver", driver)
|
||||
|
|
|
@ -33,7 +33,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
client, err := api.NewScalewayAPI(b.config.Organization, b.config.Token, b.config.UserAgent, b.config.Region)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -58,7 +58,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
cvmClient, vpcClient, err := b.config.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -45,7 +45,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return nil, errs.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
config := b.config
|
||||
|
||||
driver, err := NewDriverTriton(ui, config)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -123,14 +124,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You may either set global_id or source_path but not both"))
|
||||
}
|
||||
if strings.HasSuffix(b.config.SourceBox, ".box") {
|
||||
b.config.SourceBox, err = common.ValidatedURL(b.config.SourceBox)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is invalid: %s", err))
|
||||
}
|
||||
fileOK := common.FileExistsLocally(b.config.SourceBox)
|
||||
if !fileOK {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Source file '%s' needs to exist at time of config validation!", b.config.SourceBox))
|
||||
if _, err := os.Stat(b.config.SourceBox); err != nil {
|
||||
packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Source box '%s' needs to exist at time of config validation! %v", b.config.SourceBox, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +162,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
|
||||
// Run executes a Packer build and returns a packer.Artifact representing
|
||||
// a VirtualBox appliance.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
// Create the driver that we'll use to communicate with VirtualBox
|
||||
VagrantCWD, err := filepath.Abs(b.config.OutputDir)
|
||||
if err != nil {
|
||||
|
@ -182,7 +178,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
state.Put("config", b.config)
|
||||
state.Put("debug", b.config.PackerDebug)
|
||||
state.Put("driver", driver)
|
||||
state.Put("cache", cache)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
|
|
|
@ -121,15 +121,6 @@ func (s *StepDownloadGuestAdditions) Run(ctx context.Context, state multistep.St
|
|||
}
|
||||
}
|
||||
|
||||
// Convert the file/url to an actual URL for step_download to process.
|
||||
url, err = common.ValidatedURL(url)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error preparing guest additions url: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
log.Printf("Guest additions URL: %s", url)
|
||||
|
||||
// We're good, so let's go ahead and download this thing..
|
||||
|
@ -139,6 +130,7 @@ func (s *StepDownloadGuestAdditions) Run(ctx context.Context, state multistep.St
|
|||
Description: "Guest additions",
|
||||
ResultKey: "guest_additions_path",
|
||||
Url: []string{url},
|
||||
Extension: "iso",
|
||||
}
|
||||
|
||||
return downStep.Run(ctx, state)
|
||||
|
|
|
@ -191,7 +191,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
// Create the driver that we'll use to communicate with VirtualBox
|
||||
driver, err := vboxcommon.NewDriver()
|
||||
if err != nil {
|
||||
|
@ -311,7 +311,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
|
||||
// Setup the state bag
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("cache", cache)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("debug", b.config.PackerDebug)
|
||||
state.Put("driver", driver)
|
||||
|
|
|
@ -32,7 +32,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
|
||||
// Run executes a Packer build and returns a packer.Artifact representing
|
||||
// a VirtualBox appliance.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
// Create the driver that we'll use to communicate with VirtualBox
|
||||
driver, err := vboxcommon.NewDriver()
|
||||
if err != nil {
|
||||
|
@ -44,7 +44,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
state.Put("config", b.config)
|
||||
state.Put("debug", b.config.PackerDebug)
|
||||
state.Put("driver", driver)
|
||||
state.Put("cache", cache)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package ovf
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
vboxcommon "github.com/hashicorp/packer/builder/virtualbox/common"
|
||||
|
@ -102,17 +103,11 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
|||
|
||||
if c.SourcePath == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required"))
|
||||
} else {
|
||||
c.SourcePath, err = common.ValidatedURL(c.SourcePath)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is invalid: %s", err))
|
||||
}
|
||||
fileOK := common.FileExistsLocally(c.SourcePath)
|
||||
if !fileOK {
|
||||
packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Source file '%s' needs to exist at time of config validation!", c.SourcePath))
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(c.SourcePath); err != nil {
|
||||
packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Source file '%s' needs to exist at time of config validation! %v", c.SourcePath, err))
|
||||
}
|
||||
|
||||
validMode := false
|
||||
|
|
|
@ -30,7 +30,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
return warnings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
driver, err := vmwcommon.NewDriver(&b.config.DriverConfig, &b.config.SSHConfig, b.config.VMName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
|
||||
|
@ -56,7 +56,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
|
||||
// Setup the state bag
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("cache", cache)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("debug", b.config.PackerDebug)
|
||||
state.Put("dir", dir)
|
||||
|
|
|
@ -174,8 +174,7 @@ func setupVMwareBuild(t *testing.T, builderConfig map[string]string, provisioner
|
|||
}
|
||||
|
||||
// and then finally build it
|
||||
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||
artifacts, err := b.Run(ui, cache)
|
||||
artifacts, err := b.Run(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build artifact: %s", err)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
|
||||
// Run executes a Packer build and returns a packer.Artifact representing
|
||||
// a VMware image.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
driver, err := vmwcommon.NewDriver(&b.config.DriverConfig, &b.config.SSHConfig, b.config.VMName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed creating VMware driver: %s", err)
|
||||
|
|
|
@ -177,7 +177,7 @@ func (c *BuildCommand) Run(args []string) int {
|
|||
name := b.Name()
|
||||
log.Printf("Starting build run: %s", name)
|
||||
ui := buildUis[name]
|
||||
runArtifacts, err := b.Run(ui, c.Cache)
|
||||
runArtifacts, err := b.Run(ui)
|
||||
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Build '%s' errored: %s", name, err))
|
||||
|
|
|
@ -26,7 +26,6 @@ const (
|
|||
// Packer command inherits.
|
||||
type Meta struct {
|
||||
CoreConfig *packer.CoreConfig
|
||||
Cache packer.Cache
|
||||
Ui packer.Ui
|
||||
Version string
|
||||
|
||||
|
|
194
common/config.go
194
common/config.go
|
@ -1,16 +1,7 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// PackerKeyEnv is used to specify the key interval (delay) between keystrokes
|
||||
|
@ -21,188 +12,3 @@ const PackerKeyEnv = "PACKER_KEY_INTERVAL"
|
|||
// PackerKeyDefault 100ms is appropriate for shared build infrastructure while a
|
||||
// shorter delay (e.g. 10ms) can be used on a workstation. See PackerKeyEnv.
|
||||
const PackerKeyDefault = 100 * time.Millisecond
|
||||
|
||||
// ChooseString returns the first non-empty value.
|
||||
func ChooseString(vals ...string) string {
|
||||
for _, el := range vals {
|
||||
if el != "" {
|
||||
return el
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// SupportedProtocol verifies that the url passed is actually supported or not
|
||||
// This will also validate that the protocol is one that's actually implemented.
|
||||
func SupportedProtocol(u *url.URL) bool {
|
||||
// url.Parse shouldn't return nil except on error....but it can.
|
||||
if u == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// build a dummy NewDownloadClient since this is the only place that valid
|
||||
// protocols are actually exposed.
|
||||
cli := NewDownloadClient(&DownloadConfig{}, new(packer.NoopUi))
|
||||
|
||||
// Iterate through each downloader to see if a protocol was found.
|
||||
ok := false
|
||||
for scheme := range cli.config.DownloaderMap {
|
||||
if strings.ToLower(u.Scheme) == strings.ToLower(scheme) {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// DownloadableURL processes a URL that may also be a file path and returns
|
||||
// a completely valid URL representing the requested file. For example,
|
||||
// the original URL might be "local/file.iso" which isn't a valid URL,
|
||||
// and so DownloadableURL will return "file://local/file.iso"
|
||||
// No other transformations are done to the path.
|
||||
func DownloadableURL(original string) (string, error) {
|
||||
var absPrefix, result string
|
||||
|
||||
absPrefix = ""
|
||||
if runtime.GOOS == "windows" {
|
||||
absPrefix = "/"
|
||||
}
|
||||
|
||||
// Check that the user specified a UNC path, and promote it to an smb:// uri.
|
||||
if strings.HasPrefix(original, "\\\\") && len(original) > 2 && original[2] != '?' {
|
||||
result = filepath.ToSlash(original[2:])
|
||||
return fmt.Sprintf("smb://%s", result), nil
|
||||
}
|
||||
|
||||
// Fix the url if it's using bad characters commonly mistaken with a path.
|
||||
original = filepath.ToSlash(original)
|
||||
|
||||
// Check to see that this is a parseable URL with a scheme and a host.
|
||||
// If so, then just pass it through.
|
||||
if u, err := url.Parse(original); err == nil && u.Scheme != "" && u.Host != "" {
|
||||
return original, nil
|
||||
}
|
||||
|
||||
// If it's a file scheme, then convert it back to a regular path so the next
|
||||
// case which forces it to an absolute path, will correct it.
|
||||
if u, err := url.Parse(original); err == nil && strings.ToLower(u.Scheme) == "file" {
|
||||
original = u.Path
|
||||
}
|
||||
|
||||
// If we're on Windows and we start with a slash, then this absolute path
|
||||
// is wrong. Fix it up, so the next case can figure out the absolute path.
|
||||
if rpath := strings.SplitN(original, "/", 2); rpath[0] == "" && runtime.GOOS == "windows" {
|
||||
result = rpath[1]
|
||||
} else {
|
||||
result = original
|
||||
}
|
||||
|
||||
// Since we should be some kind of path (relative or absolute), check
|
||||
// that the file exists, then make it an absolute path so we can return an
|
||||
// absolute uri.
|
||||
if _, err := os.Stat(result); err == nil {
|
||||
result, err = filepath.Abs(filepath.FromSlash(result))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result, err = filepath.EvalSymlinks(result)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result = filepath.Clean(result)
|
||||
return fmt.Sprintf("file://%s%s", absPrefix, filepath.ToSlash(result)), nil
|
||||
}
|
||||
|
||||
// Otherwise, check if it was originally an absolute path, and fix it if so.
|
||||
if strings.HasPrefix(original, "/") {
|
||||
return fmt.Sprintf("file://%s%s", absPrefix, result), nil
|
||||
}
|
||||
|
||||
// Anything left should be a non-existent relative path. So fix it up here.
|
||||
result = filepath.ToSlash(filepath.Clean(result))
|
||||
return fmt.Sprintf("file://./%s", result), nil
|
||||
}
|
||||
|
||||
// Force the parameter into a url. This will transform the parameter into
|
||||
// a proper url, removing slashes, adding the proper prefix, etc.
|
||||
func ValidatedURL(original string) (string, error) {
|
||||
|
||||
// See if the user failed to give a url
|
||||
if ok, _ := regexp.MatchString("(?m)^[^[:punct:]]+://", original); !ok {
|
||||
|
||||
// So since no magic was found, this must be a path.
|
||||
result, err := DownloadableURL(original)
|
||||
if err == nil {
|
||||
return ValidatedURL(result)
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Verify that the url is parseable...just in case.
|
||||
u, err := url.Parse(original)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// We should now have a url, so verify that it's a protocol we support.
|
||||
if !SupportedProtocol(u) {
|
||||
return "", fmt.Errorf("Unsupported protocol scheme! (%#v)", u)
|
||||
}
|
||||
|
||||
// We should now have a properly formatted and supported url
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// FileExistsLocally takes the URL output from DownloadableURL, and determines
|
||||
// whether it is present on the file system.
|
||||
// example usage:
|
||||
//
|
||||
// myFile, err = common.DownloadableURL(c.SourcePath)
|
||||
// ...
|
||||
// fileExists := common.StatURL(myFile)
|
||||
// possible output:
|
||||
// true -- should occur if the file is present, or if the file is not present,
|
||||
// but is not supposed to be (e.g. the schema is http://, not file://)
|
||||
// false -- should occur if there was an error stating the file, so the
|
||||
// file is not present when it should be.
|
||||
|
||||
func FileExistsLocally(original string) bool {
|
||||
// original should be something like file://C:/my/path.iso
|
||||
u, _ := url.Parse(original)
|
||||
|
||||
// First create a dummy downloader so we can figure out which
|
||||
// protocol to use.
|
||||
cli := NewDownloadClient(&DownloadConfig{}, new(packer.NoopUi))
|
||||
d, ok := cli.config.DownloaderMap[u.Scheme]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check to see that it's got a Local way of doing things.
|
||||
local, ok := d.(LocalDownloader)
|
||||
if !ok {
|
||||
return true // XXX: Remote URLs short-circuit this logic.
|
||||
}
|
||||
|
||||
// Figure out where we're at.
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Now figure out the real path to the file.
|
||||
realpath, err := local.toPath(wd, *u)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Finally we can seek the truth via os.Stat.
|
||||
_, err = os.Stat(realpath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -1,312 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChooseString(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input []string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
[]string{"", "foo", ""},
|
||||
"foo",
|
||||
},
|
||||
{
|
||||
[]string{"", "foo", "bar"},
|
||||
"foo",
|
||||
},
|
||||
{
|
||||
[]string{"", "", ""},
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
result := ChooseString(tc.Input...)
|
||||
if result != tc.Output {
|
||||
t.Fatalf("bad: %#v", tc.Input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatedURL(t *testing.T) {
|
||||
// Invalid URL: has hex code in host
|
||||
_, err := ValidatedURL("http://what%20.com")
|
||||
if err == nil {
|
||||
t.Fatalf("expected err : %s", err)
|
||||
}
|
||||
|
||||
// Invalid: unsupported scheme
|
||||
_, err = ValidatedURL("ftp://host.com/path")
|
||||
if err == nil {
|
||||
t.Fatalf("expected err : %s", err)
|
||||
}
|
||||
|
||||
// Valid: http
|
||||
u, err := ValidatedURL("HTTP://packer.io/path")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if u != "http://packer.io/path" {
|
||||
t.Fatalf("bad: %s", u)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
InputString string
|
||||
OutputURL string
|
||||
ErrExpected bool
|
||||
}{
|
||||
// Invalid URL: has hex code in host
|
||||
{"http://what%20.com", "", true},
|
||||
// Valid: http
|
||||
{"HTTP://packer.io/path", "http://packer.io/path", false},
|
||||
// No path
|
||||
{"HTTP://packer.io", "http://packer.io", false},
|
||||
// Invalid: unsupported scheme
|
||||
{"ftp://host.com/path", "", true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
u, err := ValidatedURL(tc.InputString)
|
||||
if u != tc.OutputURL {
|
||||
t.Fatal(fmt.Sprintf("Error with URL %s: got %s but expected %s",
|
||||
tc.InputString, tc.OutputURL, u))
|
||||
}
|
||||
if (err != nil) != tc.ErrExpected {
|
||||
if tc.ErrExpected == true {
|
||||
t.Fatal(fmt.Sprintf("Error with URL %s: we expected "+
|
||||
"ValidatedURL to return an error but didn't get one.",
|
||||
tc.InputString))
|
||||
} else {
|
||||
t.Fatal(fmt.Sprintf("Error with URL %s: we did not expect an "+
|
||||
" error from ValidatedURL but we got: %s",
|
||||
tc.InputString, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetNativePathToTestFixtures(t *testing.T) string {
|
||||
const path = "./test-fixtures"
|
||||
res, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
t.Fatalf("err converting test-fixtures path into an absolute path : %s", err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func GetPortablePathToTestFixtures(t *testing.T) string {
|
||||
res := GetNativePathToTestFixtures(t)
|
||||
return filepath.ToSlash(res)
|
||||
}
|
||||
|
||||
func TestDownloadableURL_WindowsFiles(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
portablepath := GetPortablePathToTestFixtures(t)
|
||||
nativepath := GetNativePathToTestFixtures(t)
|
||||
|
||||
dirCases := []struct {
|
||||
InputString string
|
||||
OutputURL string
|
||||
ErrExpected bool
|
||||
}{ // TODO: add different directories
|
||||
{
|
||||
fmt.Sprintf("%s\\SomeDir\\myfile.txt", nativepath),
|
||||
fmt.Sprintf("file:///%s/SomeDir/myfile.txt", portablepath),
|
||||
false,
|
||||
},
|
||||
{ // without the drive makes this native path a relative file:// uri
|
||||
"test-fixtures\\SomeDir\\myfile.txt",
|
||||
fmt.Sprintf("file:///%s/SomeDir/myfile.txt", portablepath),
|
||||
false,
|
||||
},
|
||||
{ // without the drive makes this native path a relative file:// uri
|
||||
"test-fixtures/SomeDir/myfile.txt",
|
||||
fmt.Sprintf("file:///%s/SomeDir/myfile.txt", portablepath),
|
||||
false,
|
||||
},
|
||||
{ // UNC paths being promoted to smb:// uri scheme.
|
||||
fmt.Sprintf("\\\\localhost\\C$\\%s\\SomeDir\\myfile.txt", nativepath),
|
||||
fmt.Sprintf("smb://localhost/C$/%s/SomeDir/myfile.txt", portablepath),
|
||||
false,
|
||||
},
|
||||
{ // Absolute uri (incorrect slash type)
|
||||
fmt.Sprintf("file:///%s\\SomeDir\\myfile.txt", nativepath),
|
||||
fmt.Sprintf("file:///%s/SomeDir/myfile.txt", portablepath),
|
||||
false,
|
||||
},
|
||||
{ // Absolute uri (existing and mis-spelled)
|
||||
fmt.Sprintf("file:///%s/Somedir/myfile.txt", nativepath),
|
||||
fmt.Sprintf("file:///%s/SomeDir/myfile.txt", portablepath),
|
||||
false,
|
||||
},
|
||||
{ // Absolute path (non-existing)
|
||||
"\\absolute\\path\\to\\non-existing\\file.txt",
|
||||
"file:///absolute/path/to/non-existing/file.txt",
|
||||
false,
|
||||
},
|
||||
{ // Absolute paths (existing)
|
||||
fmt.Sprintf("%s/SomeDir/myfile.txt", nativepath),
|
||||
fmt.Sprintf("file:///%s/SomeDir/myfile.txt", portablepath),
|
||||
false,
|
||||
},
|
||||
{ // Relative path (non-existing)
|
||||
"./nonexisting/relative/path/to/file.txt",
|
||||
"file://./nonexisting/relative/path/to/file.txt",
|
||||
false,
|
||||
},
|
||||
{ // Relative path (existing)
|
||||
"./test-fixtures/SomeDir/myfile.txt",
|
||||
fmt.Sprintf("file:///%s/SomeDir/myfile.txt", portablepath),
|
||||
false,
|
||||
},
|
||||
{ // Absolute uri (existing and with `/` prefix)
|
||||
fmt.Sprintf("file:///%s/SomeDir/myfile.txt", portablepath),
|
||||
fmt.Sprintf("file:///%s/SomeDir/myfile.txt", portablepath),
|
||||
false,
|
||||
},
|
||||
{ // Absolute uri (non-existing and with `/` prefix)
|
||||
"file:///path/to/non-existing/file.txt",
|
||||
"file:///path/to/non-existing/file.txt",
|
||||
false,
|
||||
},
|
||||
{ // Absolute uri (non-existing and missing `/` prefix)
|
||||
"file://path/to/non-existing/file.txt",
|
||||
"file://path/to/non-existing/file.txt",
|
||||
false,
|
||||
},
|
||||
{ // Absolute uri and volume (non-existing and with `/` prefix)
|
||||
"file:///T:/path/to/non-existing/file.txt",
|
||||
"file:///T:/path/to/non-existing/file.txt",
|
||||
false,
|
||||
},
|
||||
{ // Absolute uri and volume (non-existing and missing `/` prefix)
|
||||
"file://T:/path/to/non-existing/file.txt",
|
||||
"file://T:/path/to/non-existing/file.txt",
|
||||
false,
|
||||
},
|
||||
}
|
||||
// Run through test cases to make sure they all parse correctly
|
||||
for idx, tc := range dirCases {
|
||||
u, err := DownloadableURL(tc.InputString)
|
||||
if (err != nil) != tc.ErrExpected {
|
||||
t.Fatalf("Test Case %d failed: Expected err = %#v, err = %#v, input = %s",
|
||||
idx, tc.ErrExpected, err, tc.InputString)
|
||||
}
|
||||
if u != tc.OutputURL {
|
||||
t.Fatalf("Test Case %d failed: Expected %s but received %s from input %s",
|
||||
idx, tc.OutputURL, u, tc.InputString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadableURL_FilePaths(t *testing.T) {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("tempfile err: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
tf.Close()
|
||||
|
||||
tfPath, err := filepath.EvalSymlinks(tf.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("tempfile err: %s", err)
|
||||
}
|
||||
|
||||
tfPath = filepath.Clean(tfPath)
|
||||
filePrefix := "file://"
|
||||
|
||||
// If we're running windows, then absolute URIs are `/`-prefixed.
|
||||
platformPrefix := ""
|
||||
if runtime.GOOS == "windows" {
|
||||
platformPrefix = "/"
|
||||
}
|
||||
|
||||
// Relative filepath. We run this test in a func so that
|
||||
// the defers run right away.
|
||||
func() {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("getwd err: %s", err)
|
||||
}
|
||||
|
||||
err = os.Chdir(filepath.Dir(tfPath))
|
||||
if err != nil {
|
||||
t.Fatalf("chdir err: %s", err)
|
||||
}
|
||||
defer os.Chdir(wd)
|
||||
|
||||
filename := filepath.Base(tfPath)
|
||||
u, err := DownloadableURL(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf("%s%s%s",
|
||||
filePrefix,
|
||||
platformPrefix,
|
||||
strings.Replace(tfPath, `\`, `/`, -1))
|
||||
if u != expected {
|
||||
t.Fatalf("unexpected: %#v != %#v", u, expected)
|
||||
}
|
||||
}()
|
||||
|
||||
// Test some cases with and without a schema prefix
|
||||
for _, prefix := range []string{"", filePrefix + platformPrefix} {
|
||||
// Nonexistent file
|
||||
_, err = DownloadableURL(prefix + "i/dont/exist")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Good file (absolute)
|
||||
u, err := DownloadableURL(prefix + tfPath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf("%s%s%s",
|
||||
filePrefix,
|
||||
platformPrefix,
|
||||
strings.Replace(tfPath, `\`, `/`, -1))
|
||||
if u != expected {
|
||||
t.Fatalf("unexpected: %s != %s", u, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileExistsLocally(t *testing.T) {
|
||||
portablepath := GetPortablePathToTestFixtures(t)
|
||||
|
||||
dirCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
// file exists locally
|
||||
{fmt.Sprintf("file://%s/SomeDir/myfile.txt", portablepath), true},
|
||||
// remote protocols short-circuit and are considered to exist locally
|
||||
{"https://myfile.iso", true},
|
||||
// non-existent protocols do not exist and hence fail
|
||||
{"nonexistent-protocol://myfile.iso", false},
|
||||
// file does not exist locally
|
||||
{"file:///C/i/dont/exist", false},
|
||||
}
|
||||
// Run through test cases to make sure they all parse correctly
|
||||
for _, tc := range dirCases {
|
||||
fileOK := FileExistsLocally(tc.Input)
|
||||
if fileOK != tc.Output {
|
||||
t.Fatalf("Test Case failed: Expected %#v, received = %#v, input = %s",
|
||||
tc.Output, fileOK, tc.Input)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,564 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// DownloadConfig is the configuration given to instantiate a new
|
||||
// download instance. Once a configuration is used to instantiate
|
||||
// a download client, it must not be modified.
|
||||
type DownloadConfig struct {
|
||||
// The source URL in the form of a string.
|
||||
Url string
|
||||
|
||||
// This is the path to download the file to.
|
||||
TargetPath string
|
||||
|
||||
// DownloaderMap maps a schema to a Download.
|
||||
DownloaderMap map[string]Downloader
|
||||
|
||||
// If true, this will copy even a local file to the target
|
||||
// location. If false, then it will "download" the file by just
|
||||
// returning the local path to the file.
|
||||
CopyFile bool
|
||||
|
||||
// The hashing implementation to use to checksum the downloaded file.
|
||||
Hash hash.Hash
|
||||
|
||||
// The checksum for the downloaded file. The hash implementation configuration
|
||||
// for the downloader will be used to verify with this checksum after
|
||||
// it is downloaded.
|
||||
Checksum []byte
|
||||
|
||||
// What to use for the user agent for HTTP requests. If set to "", use the
|
||||
// default user agent provided by Go.
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
// A DownloadClient helps download, verify checksums, etc.
|
||||
type DownloadClient struct {
|
||||
config *DownloadConfig
|
||||
}
|
||||
|
||||
// HashForType returns the Hash implementation for the given string
|
||||
// type, or nil if the type is not supported.
|
||||
func HashForType(t string) hash.Hash {
|
||||
switch t {
|
||||
case "md5":
|
||||
return md5.New()
|
||||
case "sha1":
|
||||
return sha1.New()
|
||||
case "sha256":
|
||||
return sha256.New()
|
||||
case "sha512":
|
||||
return sha512.New()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewDownloadClient returns a new DownloadClient for the given
|
||||
// configuration.
|
||||
func NewDownloadClient(c *DownloadConfig, ui packer.Ui) *DownloadClient {
|
||||
// Create downloader map if it hasn't been specified already.
|
||||
if c.DownloaderMap == nil {
|
||||
c.DownloaderMap = map[string]Downloader{
|
||||
"file": &FileDownloader{Ui: ui, bufferSize: nil},
|
||||
"http": &HTTPDownloader{Ui: ui, userAgent: c.UserAgent},
|
||||
"https": &HTTPDownloader{Ui: ui, userAgent: c.UserAgent},
|
||||
"smb": &SMBDownloader{Ui: ui, bufferSize: nil},
|
||||
}
|
||||
}
|
||||
return &DownloadClient{config: c}
|
||||
}
|
||||
|
||||
// Downloader defines what capabilities a downloader should have.
|
||||
type Downloader interface {
|
||||
Resume()
|
||||
Cancel()
|
||||
ProgressBar() packer.ProgressBar
|
||||
}
|
||||
|
||||
// A LocalDownloader is responsible for converting a uri to a local path
|
||||
// that the platform can open directly.
|
||||
type LocalDownloader interface {
|
||||
toPath(string, url.URL) (string, error)
|
||||
}
|
||||
|
||||
// A RemoteDownloader is responsible for actually taking a remote URL and
|
||||
// downloading it.
|
||||
type RemoteDownloader interface {
|
||||
Download(*os.File, *url.URL) error
|
||||
}
|
||||
|
||||
func (d *DownloadClient) Cancel() {
|
||||
// TODO(mitchellh): Implement
|
||||
}
|
||||
|
||||
func (d *DownloadClient) Get(ui packer.Ui) (string, error) {
|
||||
// If we already have the file and it matches, then just return the target path.
|
||||
if verify, _ := d.VerifyChecksum(ui, d.config.TargetPath); verify {
|
||||
log.Println("[DEBUG] Initial checksum matched, no download needed.")
|
||||
return d.config.TargetPath, nil
|
||||
}
|
||||
|
||||
/* parse the configuration url into a net/url object */
|
||||
u, err := url.Parse(d.config.Url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Printf("Parsed URL: %#v", u)
|
||||
|
||||
/* use the current working directory as the base for relative uri's */
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Determine which is the correct downloader to use
|
||||
var finalPath string
|
||||
|
||||
var ok bool
|
||||
downloader, ok := d.config.DownloaderMap[u.Scheme]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("No downloader for scheme: %s", u.Scheme)
|
||||
}
|
||||
|
||||
remote, ok := downloader.(RemoteDownloader)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Unable to treat uri scheme %s as a Downloader. : %T", u.Scheme, downloader)
|
||||
}
|
||||
|
||||
local, ok := downloader.(LocalDownloader)
|
||||
if !ok && !d.config.CopyFile {
|
||||
d.config.CopyFile = true
|
||||
}
|
||||
|
||||
// If we're copying the file, then just use the actual downloader
|
||||
if d.config.CopyFile {
|
||||
var f *os.File
|
||||
finalPath = d.config.TargetPath
|
||||
|
||||
f, err = os.OpenFile(finalPath, os.O_RDWR|os.O_CREATE, os.FileMode(0666))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Downloading: %s", u.String())
|
||||
err = remote.Download(f, u)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Otherwise if our Downloader is a LocalDownloader we can just use the
|
||||
// path after transforming it.
|
||||
} else {
|
||||
finalPath, err = local.toPath(cwd, *u)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Using local file: %s", finalPath)
|
||||
}
|
||||
|
||||
if d.config.Hash != nil {
|
||||
var verify bool
|
||||
verify, err = d.VerifyChecksum(ui, finalPath)
|
||||
if err == nil && !verify {
|
||||
// Only delete the file if we made a copy or downloaded it
|
||||
if d.config.CopyFile {
|
||||
os.Remove(finalPath)
|
||||
}
|
||||
|
||||
err = fmt.Errorf(
|
||||
"checksums didn't match. expected %s and got %s",
|
||||
hex.EncodeToString(d.config.Checksum),
|
||||
hex.EncodeToString(d.config.Hash.Sum(nil)))
|
||||
}
|
||||
}
|
||||
|
||||
return finalPath, err
|
||||
}
|
||||
|
||||
// VerifyChecksum tests that the path matches the checksum for the
|
||||
// download.
|
||||
func (d *DownloadClient) VerifyChecksum(ui packer.Ui, path string) (bool, error) {
|
||||
if d.config.Checksum == nil || d.config.Hash == nil {
|
||||
return false, errors.New("Checksum or Hash isn't set on download.")
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
ui.Message(fmt.Sprintf("Verifying checksum of %s", path))
|
||||
d.config.Hash.Reset()
|
||||
io.Copy(d.config.Hash, f)
|
||||
return bytes.Equal(d.config.Hash.Sum(nil), d.config.Checksum), nil
|
||||
}
|
||||
|
||||
// HTTPDownloader is an implementation of Downloader that downloads
|
||||
// files over HTTP.
|
||||
type HTTPDownloader struct {
|
||||
userAgent string
|
||||
|
||||
Ui packer.Ui
|
||||
}
|
||||
|
||||
func (d *HTTPDownloader) Cancel() {
|
||||
// TODO(mitchellh): Implement
|
||||
}
|
||||
|
||||
func (d *HTTPDownloader) Resume() {
|
||||
// TODO(mitchellh): Implement
|
||||
}
|
||||
|
||||
func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error {
|
||||
log.Printf("Starting download over HTTP: %s", src.String())
|
||||
|
||||
// Seek to the beginning by default
|
||||
if _, err := dst.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var current int64
|
||||
|
||||
// Make the request. We first make a HEAD request so we can check
|
||||
// if the server supports range queries. If the server/URL doesn't
|
||||
// support HEAD requests, we just fall back to GET.
|
||||
req, err := http.NewRequest("HEAD", src.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.userAgent != "" {
|
||||
req.Header.Set("User-Agent", d.userAgent)
|
||||
}
|
||||
|
||||
httpClient := commonhelper.HttpClientWithEnvironmentProxy()
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil || resp == nil {
|
||||
|
||||
if resp == nil {
|
||||
log.Printf("[DEBUG] (download) HTTP connection error: %s", err.Error())
|
||||
|
||||
} else if resp.StatusCode >= 400 && resp.StatusCode < 600 {
|
||||
log.Printf("[DEBUG] (download) Non-successful HTTP status code (%s) while making HEAD request: %s", resp.Status, err.Error())
|
||||
|
||||
} else {
|
||||
log.Printf("[DEBUG] (download) Error making HTTP HEAD request (%s): %s", resp.Status, err.Error())
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
// If the HEAD request succeeded, then attempt to set the range
|
||||
// query if we can.
|
||||
|
||||
if resp.Header.Get("Accept-Ranges") == "bytes" {
|
||||
if fi, err := dst.Stat(); err == nil {
|
||||
if _, err = dst.Seek(0, os.SEEK_END); err == nil {
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", fi.Size()))
|
||||
|
||||
current = fi.Size()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Printf("[DEBUG] (download) Unexpected HTTP response during HEAD request: %s", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the request to GET now, and redo the query to download
|
||||
req.Method = "GET"
|
||||
|
||||
resp, err = httpClient.Do(req)
|
||||
if err == nil && (resp.StatusCode >= 400 && resp.StatusCode < 600) {
|
||||
return fmt.Errorf("Error making HTTP GET request: %s", resp.Status)
|
||||
|
||||
} else if err != nil {
|
||||
if resp == nil {
|
||||
return fmt.Errorf("HTTP connection error: %s", err.Error())
|
||||
|
||||
} else if resp.StatusCode >= 400 && resp.StatusCode < 600 {
|
||||
return fmt.Errorf("HTTP %s error: %s", resp.Status, err.Error())
|
||||
}
|
||||
return fmt.Errorf("HTTP error: %s", err.Error())
|
||||
}
|
||||
|
||||
total := current + resp.ContentLength
|
||||
|
||||
bar := d.ProgressBar()
|
||||
bar.Start(total)
|
||||
defer bar.Finish()
|
||||
bar.Add(current)
|
||||
|
||||
body := bar.NewProxyReader(resp.Body)
|
||||
|
||||
var buffer [4096]byte
|
||||
for {
|
||||
n, err := body.Read(buffer[:])
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, werr := dst.Write(buffer[:n]); werr != nil {
|
||||
return werr
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileDownloader is an implementation of Downloader that downloads
|
||||
// files using the regular filesystem.
|
||||
type FileDownloader struct {
|
||||
bufferSize *uint
|
||||
|
||||
active bool
|
||||
Ui packer.Ui
|
||||
}
|
||||
|
||||
func (d *FileDownloader) Cancel() {
|
||||
d.active = false
|
||||
}
|
||||
|
||||
func (d *FileDownloader) Resume() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
func (d *FileDownloader) toPath(base string, uri url.URL) (string, error) {
|
||||
var result string
|
||||
|
||||
// absolute path -- file://c:/absolute/path -> c:/absolute/path
|
||||
if strings.HasSuffix(uri.Host, ":") {
|
||||
result = path.Join(uri.Host, uri.Path)
|
||||
|
||||
// semi-absolute path (current drive letter)
|
||||
// -- file:///absolute/path -> drive:/absolute/path
|
||||
} else if uri.Host == "" && strings.HasPrefix(uri.Path, "/") {
|
||||
apath := uri.Path
|
||||
components := strings.Split(apath, "/")
|
||||
volume := filepath.VolumeName(base)
|
||||
|
||||
// semi-absolute absolute path (includes volume letter)
|
||||
// -- file://drive:/path -> drive:/absolute/path
|
||||
if len(components) > 1 && strings.HasSuffix(components[1], ":") {
|
||||
volume = components[1]
|
||||
apath = path.Join(components[2:]...)
|
||||
}
|
||||
|
||||
result = path.Join(volume, apath)
|
||||
|
||||
// relative path -- file://./relative/path -> ./relative/path
|
||||
} else if uri.Host == "." {
|
||||
result = path.Join(base, uri.Path)
|
||||
|
||||
// relative path -- file://relative/path -> ./relative/path
|
||||
} else {
|
||||
result = path.Join(base, uri.Host, uri.Path)
|
||||
}
|
||||
return filepath.ToSlash(result), nil
|
||||
}
|
||||
|
||||
func (d *FileDownloader) Download(dst *os.File, src *url.URL) error {
|
||||
d.active = false
|
||||
|
||||
/* check the uri's scheme to make sure it matches */
|
||||
if src == nil || src.Scheme != "file" {
|
||||
return fmt.Errorf("Unexpected uri scheme: %s", src.Scheme)
|
||||
}
|
||||
uri := src
|
||||
|
||||
/* use the current working directory as the base for relative uri's */
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/* determine which uri format is being used and convert to a real path */
|
||||
realpath, err := d.toPath(cwd, *uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/* download the file using the operating system's facilities */
|
||||
d.active = true
|
||||
|
||||
f, err := os.Open(realpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// get the file size
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bar := d.ProgressBar()
|
||||
|
||||
bar.Start(fi.Size())
|
||||
defer bar.Finish()
|
||||
fProxy := bar.NewProxyReader(f)
|
||||
|
||||
// no bufferSize specified, so copy synchronously.
|
||||
if d.bufferSize == nil {
|
||||
_, err = io.Copy(dst, fProxy)
|
||||
d.active = false
|
||||
|
||||
// use a goro in case someone else wants to enable cancel/resume
|
||||
} else {
|
||||
errch := make(chan error)
|
||||
go func(d *FileDownloader, r io.Reader, w io.Writer, e chan error) {
|
||||
for d.active {
|
||||
_, err := io.CopyN(w, r, int64(*d.bufferSize))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
d.active = false
|
||||
e <- err
|
||||
}(d, fProxy, dst, errch)
|
||||
|
||||
// ...and we spin until it's done
|
||||
err = <-errch
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SMBDownloader is an implementation of Downloader that downloads
|
||||
// files using the "\\" path format on Windows
|
||||
type SMBDownloader struct {
|
||||
bufferSize *uint
|
||||
|
||||
active bool
|
||||
Ui packer.Ui
|
||||
}
|
||||
|
||||
func (d *SMBDownloader) Cancel() {
|
||||
d.active = false
|
||||
}
|
||||
|
||||
func (d *SMBDownloader) Resume() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
func (d *SMBDownloader) toPath(base string, uri url.URL) (string, error) {
|
||||
const UNCPrefix = string(os.PathSeparator) + string(os.PathSeparator)
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
return "", fmt.Errorf("Support for SMB based uri's are not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
return UNCPrefix + filepath.ToSlash(path.Join(uri.Host, uri.Path)), nil
|
||||
}
|
||||
|
||||
func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error {
|
||||
|
||||
/* first we warn the world if we're not running windows */
|
||||
if runtime.GOOS != "windows" {
|
||||
return fmt.Errorf("Support for SMB based uri's are not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
d.active = false
|
||||
|
||||
/* convert the uri using the net/url module to a UNC path */
|
||||
if src == nil || src.Scheme != "smb" {
|
||||
return fmt.Errorf("Unexpected uri scheme: %s", src.Scheme)
|
||||
}
|
||||
uri := src
|
||||
|
||||
/* use the current working directory as the base for relative uri's */
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/* convert uri to an smb-path */
|
||||
realpath, err := d.toPath(cwd, *uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/* Open up the "\\"-prefixed path using the Windows filesystem */
|
||||
d.active = true
|
||||
|
||||
f, err := os.Open(realpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// get the file size (at the risk of performance)
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bar := d.ProgressBar()
|
||||
|
||||
bar.Start(fi.Size())
|
||||
defer bar.Finish()
|
||||
fProxy := bar.NewProxyReader(f)
|
||||
|
||||
// no bufferSize specified, so copy synchronously.
|
||||
if d.bufferSize == nil {
|
||||
_, err = io.Copy(dst, fProxy)
|
||||
d.active = false
|
||||
|
||||
// use a goro in case someone else wants to enable cancel/resume
|
||||
} else {
|
||||
errch := make(chan error)
|
||||
go func(d *SMBDownloader, r io.Reader, w io.Writer, e chan error) {
|
||||
for d.active {
|
||||
_, err := io.CopyN(w, r, int64(*d.bufferSize))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
d.active = false
|
||||
e <- err
|
||||
}(d, fProxy, dst, errch)
|
||||
|
||||
// ...and as usual we spin until it's done
|
||||
err = <-errch
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *HTTPDownloader) ProgressBar() packer.ProgressBar { return d.Ui.ProgressBar() }
|
||||
func (d *FileDownloader) ProgressBar() packer.ProgressBar { return d.Ui.ProgressBar() }
|
||||
func (d *SMBDownloader) ProgressBar() packer.ProgressBar { return d.Ui.ProgressBar() }
|
|
@ -1,535 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestDownloadClientVerifyChecksum(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("tempfile error: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
// "foo"
|
||||
checksum, err := hex.DecodeString("acbd18db4cc2f85cedef654fccc4a4d8")
|
||||
if err != nil {
|
||||
t.Fatalf("decode err: %s", err)
|
||||
}
|
||||
|
||||
// Write the file
|
||||
tf.Write([]byte("foo"))
|
||||
tf.Close()
|
||||
|
||||
config := &DownloadConfig{
|
||||
Hash: md5.New(),
|
||||
Checksum: checksum,
|
||||
}
|
||||
|
||||
d := NewDownloadClient(config, new(packer.NoopUi))
|
||||
result, err := d.VerifyChecksum(ui, tf.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("Verify err: %s", err)
|
||||
}
|
||||
|
||||
if !result {
|
||||
t.Fatal("didn't verify")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadClient_basic(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
tf, _ := ioutil.TempFile("", "packer")
|
||||
tf.Close()
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
ts := httptest.NewServer(http.FileServer(http.Dir("./test-fixtures/root")))
|
||||
defer ts.Close()
|
||||
|
||||
client := NewDownloadClient(&DownloadConfig{
|
||||
Url: ts.URL + "/basic.txt",
|
||||
TargetPath: tf.Name(),
|
||||
CopyFile: true,
|
||||
}, new(packer.NoopUi))
|
||||
|
||||
path, err := client.Get(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if string(raw) != "hello\n" {
|
||||
t.Fatalf("bad: %s", string(raw))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadClient_checksumBad(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
checksum, err := hex.DecodeString("b2946ac92492d2347c6235b4d2611184")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
tf, _ := ioutil.TempFile("", "packer")
|
||||
tf.Close()
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
ts := httptest.NewServer(http.FileServer(http.Dir("./test-fixtures/root")))
|
||||
defer ts.Close()
|
||||
|
||||
client := NewDownloadClient(&DownloadConfig{
|
||||
Url: ts.URL + "/basic.txt",
|
||||
TargetPath: tf.Name(),
|
||||
Hash: HashForType("md5"),
|
||||
Checksum: checksum,
|
||||
CopyFile: true,
|
||||
}, new(packer.NoopUi))
|
||||
|
||||
if _, err := client.Get(ui); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadClient_checksumGood(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
checksum, err := hex.DecodeString("b1946ac92492d2347c6235b4d2611184")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
tf, _ := ioutil.TempFile("", "packer")
|
||||
tf.Close()
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
ts := httptest.NewServer(http.FileServer(http.Dir("./test-fixtures/root")))
|
||||
defer ts.Close()
|
||||
|
||||
client := NewDownloadClient(&DownloadConfig{
|
||||
Url: ts.URL + "/basic.txt",
|
||||
TargetPath: tf.Name(),
|
||||
Hash: HashForType("md5"),
|
||||
Checksum: checksum,
|
||||
CopyFile: true,
|
||||
}, new(packer.NoopUi))
|
||||
|
||||
path, err := client.Get(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if string(raw) != "hello\n" {
|
||||
t.Fatalf("bad: %s", string(raw))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadClient_checksumNoDownload(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
checksum, err := hex.DecodeString("3740570a423feec44c2a759225a9fcf9")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.FileServer(http.Dir("./test-fixtures/root")))
|
||||
defer ts.Close()
|
||||
|
||||
client := NewDownloadClient(&DownloadConfig{
|
||||
Url: ts.URL + "/basic.txt",
|
||||
TargetPath: "./test-fixtures/root/another.txt",
|
||||
Hash: HashForType("md5"),
|
||||
Checksum: checksum,
|
||||
CopyFile: true,
|
||||
}, new(packer.NoopUi))
|
||||
path, err := client.Get(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// If this says "hello" it means we downloaded it. We faked out
|
||||
// the downloader above by giving it the checksum for "another", but
|
||||
// requested the download of "hello"
|
||||
if string(raw) != "another\n" {
|
||||
t.Fatalf("bad: %s", string(raw))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadClient_notFound(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
tf, _ := ioutil.TempFile("", "packer")
|
||||
tf.Close()
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
ts := httptest.NewServer(http.FileServer(http.Dir("./test-fixtures/root")))
|
||||
defer ts.Close()
|
||||
|
||||
client := NewDownloadClient(&DownloadConfig{
|
||||
Url: ts.URL + "/not-found.txt",
|
||||
TargetPath: tf.Name(),
|
||||
}, new(packer.NoopUi))
|
||||
|
||||
if _, err := client.Get(ui); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadClient_resume(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
tf, _ := ioutil.TempFile("", "packer")
|
||||
tf.Write([]byte("w"))
|
||||
tf.Close()
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
rw.Header().Set("Accept-Ranges", "bytes")
|
||||
rw.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(rw, r, "./test-fixtures/root/basic.txt")
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
client := NewDownloadClient(&DownloadConfig{
|
||||
Url: ts.URL,
|
||||
TargetPath: tf.Name(),
|
||||
CopyFile: true,
|
||||
}, new(packer.NoopUi))
|
||||
|
||||
path, err := client.Get(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if string(raw) != "wello\n" {
|
||||
t.Fatalf("bad: %s", string(raw))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadClient_usesDefaultUserAgent(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("tempfile error: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
defaultUserAgent := ""
|
||||
asserted := false
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if defaultUserAgent == "" {
|
||||
defaultUserAgent = r.UserAgent()
|
||||
} else {
|
||||
incomingUserAgent := r.UserAgent()
|
||||
if incomingUserAgent != defaultUserAgent {
|
||||
t.Fatalf("Expected user agent %s, got: %s", defaultUserAgent, incomingUserAgent)
|
||||
}
|
||||
|
||||
asserted = true
|
||||
}
|
||||
}))
|
||||
|
||||
req, err := http.NewRequest("GET", server.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
httpClient := commonhelper.HttpClientWithEnvironmentProxy()
|
||||
|
||||
_, err = httpClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := &DownloadConfig{
|
||||
Url: server.URL,
|
||||
TargetPath: tf.Name(),
|
||||
CopyFile: true,
|
||||
}
|
||||
|
||||
client := NewDownloadClient(config, new(packer.NoopUi))
|
||||
_, err = client.Get(ui)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !asserted {
|
||||
t.Fatal("User-Agent never observed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadClient_setsUserAgent(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("tempfile error: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
asserted := false
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
asserted = true
|
||||
if r.UserAgent() != "fancy user agent" {
|
||||
t.Fatalf("Expected useragent fancy user agent, got: %s", r.UserAgent())
|
||||
}
|
||||
}))
|
||||
config := &DownloadConfig{
|
||||
Url: server.URL,
|
||||
TargetPath: tf.Name(),
|
||||
UserAgent: "fancy user agent",
|
||||
CopyFile: true,
|
||||
}
|
||||
|
||||
client := NewDownloadClient(config, new(packer.NoopUi))
|
||||
_, err = client.Get(ui)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !asserted {
|
||||
t.Fatal("HTTP request never made")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashForType(t *testing.T) {
|
||||
if h := HashForType("md5"); h == nil {
|
||||
t.Fatalf("md5 hash is nil")
|
||||
} else {
|
||||
h.Write([]byte("foo"))
|
||||
result := h.Sum(nil)
|
||||
|
||||
expected := "acbd18db4cc2f85cedef654fccc4a4d8"
|
||||
actual := hex.EncodeToString(result)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad hash: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
if h := HashForType("sha1"); h == nil {
|
||||
t.Fatalf("sha1 hash is nil")
|
||||
} else {
|
||||
h.Write([]byte("foo"))
|
||||
result := h.Sum(nil)
|
||||
|
||||
expected := "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
|
||||
actual := hex.EncodeToString(result)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad hash: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
if h := HashForType("sha256"); h == nil {
|
||||
t.Fatalf("sha256 hash is nil")
|
||||
} else {
|
||||
h.Write([]byte("foo"))
|
||||
result := h.Sum(nil)
|
||||
|
||||
expected := "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
|
||||
actual := hex.EncodeToString(result)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad hash: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
if h := HashForType("sha512"); h == nil {
|
||||
t.Fatalf("sha512 hash is nil")
|
||||
} else {
|
||||
h.Write([]byte("foo"))
|
||||
result := h.Sum(nil)
|
||||
|
||||
expected := "f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7"
|
||||
actual := hex.EncodeToString(result)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad hash: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
if HashForType("fake") != nil {
|
||||
t.Fatalf("fake hash is not nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDownloadFileUrl tests a special case where we use a local file for
|
||||
// iso_url. In this case we can still verify the checksum but we should not
|
||||
// delete the file if the checksum fails. Instead we'll just error and let the
|
||||
// user fix the checksum.
|
||||
func TestDownloadFileUrl(t *testing.T) {
|
||||
ui := packer.TestUi(t)
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to detect working directory: %s", err)
|
||||
}
|
||||
cwd = filepath.ToSlash(cwd)
|
||||
|
||||
// source_path is a file path and source is a network path
|
||||
sourcePath := fmt.Sprintf("%s/test-fixtures/fileurl/%s", cwd, "cake")
|
||||
|
||||
filePrefix := "file://"
|
||||
if runtime.GOOS == "windows" {
|
||||
filePrefix += "/"
|
||||
}
|
||||
|
||||
source := fmt.Sprintf(filePrefix + sourcePath)
|
||||
t.Logf("Trying to download %s", source)
|
||||
|
||||
config := &DownloadConfig{
|
||||
Url: source,
|
||||
// This should be wrong. We want to make sure we don't delete
|
||||
Checksum: []byte("nope"),
|
||||
Hash: HashForType("sha256"),
|
||||
CopyFile: false,
|
||||
}
|
||||
|
||||
client := NewDownloadClient(config, new(packer.NoopUi))
|
||||
|
||||
// Verify that we fail to match the checksum
|
||||
_, err = client.Get(ui)
|
||||
if err.Error() != "checksums didn't match. expected 6e6f7065 and got 606f1945f81a022d0ed0bd99edfd4f99081c1cb1f97fae087291ee14e945e608" {
|
||||
t.Fatalf("Unexpected failure; expected checksum not to match. Error was \"%v\"", err)
|
||||
}
|
||||
|
||||
if _, err = os.Stat(sourcePath); err != nil {
|
||||
t.Errorf("Could not stat source file: %s", sourcePath)
|
||||
}
|
||||
}
|
||||
|
||||
// SimulateFileUriDownload is a simple utility function that converts a uri
|
||||
// into a testable file path whilst ignoring a correct checksum match, stripping
|
||||
// UNC path info, and then calling stat to ensure the correct file exists.
|
||||
// (used by TestFileUriTransforms)
|
||||
func SimulateFileUriDownload(t *testing.T, uri string) (string, error) {
|
||||
ui := packer.TestUi(t)
|
||||
// source_path is a file path and source is a network path
|
||||
source := fmt.Sprintf(uri)
|
||||
t.Logf("Trying to download %s", source)
|
||||
|
||||
config := &DownloadConfig{
|
||||
Url: source,
|
||||
// This should be wrong. We want to make sure we don't delete
|
||||
Checksum: []byte("nope"),
|
||||
Hash: HashForType("sha256"),
|
||||
CopyFile: false,
|
||||
}
|
||||
|
||||
// go go go
|
||||
client := NewDownloadClient(config, new(packer.NoopUi))
|
||||
path, err := client.Get(ui)
|
||||
|
||||
// ignore any non-important checksum errors if it's not a unc path
|
||||
if !strings.HasPrefix(path, "\\\\") && err.Error() != "checksums didn't match. expected 6e6f7065 and got 606f1945f81a022d0ed0bd99edfd4f99081c1cb1f97fae087291ee14e945e608" {
|
||||
t.Fatalf("Unexpected failure; expected checksum not to match")
|
||||
}
|
||||
|
||||
// if it's a unc path, then remove the host and share name so we don't have
|
||||
// to force the user to enable ADMIN$ and Windows File Sharing
|
||||
if strings.HasPrefix(path, "\\\\") {
|
||||
res := strings.SplitN(path, "/", 3)
|
||||
path = "/" + res[2]
|
||||
}
|
||||
|
||||
if _, err = os.Stat(path); err != nil {
|
||||
t.Errorf("Could not stat source file: %s", path)
|
||||
}
|
||||
return path, err
|
||||
}
|
||||
|
||||
// TestFileUriTransforms tests the case where we use a local file uri
|
||||
// for iso_url. There's a few different formats that a file uri can exist as
|
||||
// and so we try to test the most useful and common ones.
|
||||
func TestFileUriTransforms(t *testing.T) {
|
||||
const testpath = /* have your */ "test-fixtures/fileurl/cake" /* and eat it too */
|
||||
const host = "localhost"
|
||||
|
||||
var cwd string
|
||||
var volume string
|
||||
var share string
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to detect working directory: %s", err)
|
||||
return
|
||||
}
|
||||
cwd = filepath.ToSlash(cwd)
|
||||
volume = filepath.VolumeName(cwd)
|
||||
share = volume
|
||||
|
||||
// if a volume was found (on windows), replace the ':' from
|
||||
// C: to C$ to convert it into a hidden windows share.
|
||||
if len(share) > 1 && share[len(share)-1] == ':' {
|
||||
share = share[:len(share)-1] + "$"
|
||||
}
|
||||
cwd = cwd[len(volume):]
|
||||
|
||||
t.Logf("TestFileUriTransforms : Running with cwd : '%s'", cwd)
|
||||
t.Logf("TestFileUriTransforms : Running with volume : '%s'", volume)
|
||||
|
||||
// ./relative/path -> ./relative/path
|
||||
// /absolute/path -> /absolute/path
|
||||
// c:/windows/absolute -> c:/windows/absolute
|
||||
testcases := []string{
|
||||
"./%s",
|
||||
cwd + "/%s",
|
||||
volume + cwd + "/%s",
|
||||
}
|
||||
|
||||
// all regular slashed testcases
|
||||
for _, testcase := range testcases {
|
||||
uri := "file://" + fmt.Sprintf(testcase, testpath)
|
||||
t.Logf("TestFileUriTransforms : Trying Uri '%s'", uri)
|
||||
res, err := SimulateFileUriDownload(t, uri)
|
||||
if err != nil {
|
||||
t.Errorf("Unable to transform uri '%s' into a path : %v", uri, err)
|
||||
}
|
||||
t.Logf("TestFileUriTransforms : Result Path '%s'", res)
|
||||
}
|
||||
|
||||
// smb protocol depends on platform support which currently
|
||||
// only exists on windows.
|
||||
if runtime.GOOS == "windows" {
|
||||
// ...and finally the oddball windows native path
|
||||
// smb://host/sharename/file -> \\host\sharename\file
|
||||
testcase := host + "/" + share + "/" + cwd[1:] + "/%s"
|
||||
uri := "smb://" + fmt.Sprintf(testcase, testpath)
|
||||
t.Logf("TestFileUriTransforms : Trying Uri '%s'", uri)
|
||||
res, err := SimulateFileUriDownload(t, uri)
|
||||
if err != nil {
|
||||
t.Errorf("Unable to transform uri '%s' into a path", uri)
|
||||
return
|
||||
}
|
||||
t.Logf("TestFileUriTransforms : Result Path '%s'", res)
|
||||
}
|
||||
}
|
|
@ -1,15 +1,8 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
|
@ -27,103 +20,24 @@ type ISOConfig struct {
|
|||
}
|
||||
|
||||
func (c *ISOConfig) Prepare(ctx *interpolate.Context) (warnings []string, errs []error) {
|
||||
if c.RawSingleISOUrl == "" && len(c.ISOUrls) == 0 {
|
||||
if len(c.ISOUrls) != 0 && c.RawSingleISOUrl != "" {
|
||||
errs = append(
|
||||
errs, errors.New("One of iso_url or iso_urls must be specified."))
|
||||
errs, errors.New("Only one of iso_url or iso_urls must be specified"))
|
||||
return
|
||||
} else if c.RawSingleISOUrl != "" && len(c.ISOUrls) > 0 {
|
||||
}
|
||||
|
||||
if c.RawSingleISOUrl != "" {
|
||||
// make sure only array is set
|
||||
c.ISOUrls = append([]string{c.RawSingleISOUrl}, c.ISOUrls...)
|
||||
c.RawSingleISOUrl = ""
|
||||
}
|
||||
if len(c.ISOUrls) == 0 {
|
||||
errs = append(
|
||||
errs, errors.New("Only one of iso_url or iso_urls may be specified."))
|
||||
errs, errors.New("One of iso_url or iso_urls must be specified"))
|
||||
return
|
||||
} else if c.RawSingleISOUrl != "" {
|
||||
c.ISOUrls = []string{c.RawSingleISOUrl}
|
||||
}
|
||||
|
||||
if c.ISOChecksumType == "" {
|
||||
errs = append(
|
||||
errs, errors.New("The iso_checksum_type must be specified."))
|
||||
} else {
|
||||
c.ISOChecksumType = strings.ToLower(c.ISOChecksumType)
|
||||
if c.ISOChecksumType != "none" {
|
||||
if c.ISOChecksum == "" && c.ISOChecksumURL == "" {
|
||||
errs = append(
|
||||
errs, errors.New("Due to large file sizes, an iso_checksum is required"))
|
||||
return warnings, errs
|
||||
} else {
|
||||
if h := HashForType(c.ISOChecksumType); h == nil {
|
||||
errs = append(
|
||||
errs, fmt.Errorf("Unsupported checksum type: %s", c.ISOChecksumType))
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
// If iso_checksum has no value use iso_checksum_url instead.
|
||||
if c.ISOChecksum == "" {
|
||||
if strings.HasSuffix(strings.ToLower(c.ISOChecksumURL), ".iso") {
|
||||
errs = append(errs, fmt.Errorf("Error parsing checksum:"+
|
||||
" .iso is not a valid checksum extension"))
|
||||
return warnings, errs
|
||||
}
|
||||
u, err := url.Parse(c.ISOChecksumURL)
|
||||
if err != nil {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("Error parsing checksum: %s", err))
|
||||
return warnings, errs
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "http", "https":
|
||||
res, err := http.Get(c.ISOChecksumURL)
|
||||
c.ISOChecksum = ""
|
||||
if err != nil {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("Error getting checksum from url: %s", c.ISOChecksumURL))
|
||||
return warnings, errs
|
||||
}
|
||||
defer res.Body.Close()
|
||||
err = c.parseCheckSumFile(bufio.NewReader(res.Body))
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return warnings, errs
|
||||
}
|
||||
case "file":
|
||||
path := u.Path
|
||||
|
||||
if runtime.GOOS == "windows" && len(path) > 2 && path[0] == '/' && path[2] == ':' {
|
||||
path = strings.TrimLeft(path, "/")
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return warnings, errs
|
||||
}
|
||||
err = c.parseCheckSumFile(bufio.NewReader(file))
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return warnings, errs
|
||||
}
|
||||
case "":
|
||||
break
|
||||
default:
|
||||
errs = append(errs,
|
||||
fmt.Errorf("Error parsing checksum url: %s, scheme not supported: %s", c.ISOChecksumURL, u.Scheme))
|
||||
return warnings, errs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.ISOChecksum = strings.ToLower(c.ISOChecksum)
|
||||
|
||||
for i, url := range c.ISOUrls {
|
||||
url, err := ValidatedURL(url)
|
||||
if err != nil {
|
||||
errs = append(
|
||||
errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err))
|
||||
} else {
|
||||
c.ISOUrls[i] = url
|
||||
}
|
||||
}
|
||||
c.ISOChecksumType = strings.ToLower(c.ISOChecksumType)
|
||||
|
||||
if c.TargetExtension == "" {
|
||||
c.TargetExtension = "iso"
|
||||
|
@ -135,69 +49,22 @@ func (c *ISOConfig) Prepare(ctx *interpolate.Context) (warnings []string, errs [
|
|||
warnings = append(warnings,
|
||||
"A checksum type of 'none' was specified. Since ISO files are so big,\n"+
|
||||
"a checksum is highly recommended.")
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
if c.ISOChecksumURL != "" {
|
||||
if strings.HasSuffix(strings.ToLower(c.ISOChecksumURL), ".iso") {
|
||||
errs = append(errs, fmt.Errorf("Error parsing checksum:"+
|
||||
" .iso is not a valid checksum extension"))
|
||||
}
|
||||
// go-getter auto-parses checksum files
|
||||
c.ISOChecksumType = "file"
|
||||
c.ISOChecksum = c.ISOChecksumURL
|
||||
}
|
||||
|
||||
if c.ISOChecksum == "" {
|
||||
errs = append(errs, fmt.Errorf("A checksum must be specified"))
|
||||
}
|
||||
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
func (c *ISOConfig) parseCheckSumFile(rd *bufio.Reader) error {
|
||||
u, err := url.Parse(c.ISOUrls[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checksumurl, err := url.Parse(c.ISOChecksumURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs(u.Path)
|
||||
if err != nil {
|
||||
log.Printf("Unable to generate absolute path from provided iso_url: %s", err)
|
||||
absPath = ""
|
||||
}
|
||||
|
||||
relpath, err := filepath.Rel(filepath.Dir(checksumurl.Path), absPath)
|
||||
if err != nil {
|
||||
log.Printf("Unable to determine relative pathing; continuing with abspath.")
|
||||
relpath = ""
|
||||
}
|
||||
|
||||
filename := filepath.Base(u.Path)
|
||||
|
||||
errNotFound := fmt.Errorf("No checksum for %q, %q or %q found at: %s",
|
||||
filename, relpath, u.Path, c.ISOChecksumURL)
|
||||
for {
|
||||
line, err := rd.ReadString('\n')
|
||||
if err != nil && line == "" {
|
||||
break
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
options := []string{filename, relpath, "./" + relpath, absPath}
|
||||
if strings.ToLower(parts[0]) == c.ISOChecksumType {
|
||||
// BSD-style checksum
|
||||
for _, match := range options {
|
||||
if parts[1] == fmt.Sprintf("(%s)", match) {
|
||||
c.ISOChecksum = parts[3]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Standard checksum
|
||||
if parts[1][0] == '*' {
|
||||
// Binary mode
|
||||
parts[1] = parts[1][1:]
|
||||
}
|
||||
for _, match := range options {
|
||||
if parts[1] == match {
|
||||
c.ISOChecksum = parts[0]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errNotFound
|
||||
}
|
||||
|
|
|
@ -3,13 +3,9 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -74,12 +70,9 @@ func TestISOConfigPrepare_ISOChecksum(t *testing.T) {
|
|||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if i.ISOChecksum != "foo" {
|
||||
t.Fatalf("should've lowercased: %s", i.ISOChecksum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) {
|
||||
func TestISOConfigPrepare_ISOChecksumURLBad(t *testing.T) {
|
||||
i := testISOConfig()
|
||||
i.ISOChecksumURL = "file:///not_read"
|
||||
|
||||
|
@ -89,157 +82,6 @@ func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) {
|
|||
t.Fatalf("bad: %#v, %#v", warns, err)
|
||||
}
|
||||
|
||||
// Test good - ISOChecksumURL BSD style
|
||||
i = testISOConfig()
|
||||
i.ISOChecksum = ""
|
||||
cs_file, _ := ioutil.TempFile("", "packer-test-")
|
||||
defer os.Remove(cs_file.Name())
|
||||
defer cs_file.Close()
|
||||
ioutil.WriteFile(cs_file.Name(), []byte(cs_bsd_style), 0666)
|
||||
filePrefix := "file://"
|
||||
if runtime.GOOS == "windows" {
|
||||
filePrefix += "/"
|
||||
}
|
||||
i.ISOChecksumURL = fmt.Sprintf("%s%s", filePrefix, cs_file.Name())
|
||||
warns, err = i.Prepare(nil)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if i.ISOChecksum != "baz" {
|
||||
t.Fatalf("should've found \"baz\" got: %s", i.ISOChecksum)
|
||||
}
|
||||
|
||||
// Test good - ISOChecksumURL GNU style
|
||||
i = testISOConfig()
|
||||
i.ISOChecksum = ""
|
||||
cs_file, _ = ioutil.TempFile("", "packer-test-")
|
||||
defer os.Remove(cs_file.Name())
|
||||
defer cs_file.Close()
|
||||
ioutil.WriteFile(cs_file.Name(), []byte(cs_gnu_style), 0666)
|
||||
i.ISOChecksumURL = fmt.Sprintf("%s%s", filePrefix, cs_file.Name())
|
||||
warns, err = i.Prepare(nil)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if i.ISOChecksum != "bar0" {
|
||||
t.Fatalf("should've found \"bar0\" got: %s", i.ISOChecksum)
|
||||
}
|
||||
|
||||
// Test good - ISOChecksumURL BSD style no newline
|
||||
i = testISOConfig()
|
||||
i.ISOChecksum = ""
|
||||
cs_file, _ = ioutil.TempFile("", "packer-test-")
|
||||
defer os.Remove(cs_file.Name())
|
||||
defer cs_file.Close()
|
||||
ioutil.WriteFile(cs_file.Name(), []byte(cs_bsd_style_no_newline), 0666)
|
||||
i.ISOChecksumURL = fmt.Sprintf("file://%s", cs_file.Name())
|
||||
warns, err = i.Prepare(nil)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if i.ISOChecksum != "baz" {
|
||||
t.Fatalf("should've found \"baz\" got: %s", i.ISOChecksum)
|
||||
}
|
||||
|
||||
// Test good - ISOChecksumURL BSD style with relative path
|
||||
i = testISOConfig()
|
||||
i.ISOChecksum = ""
|
||||
|
||||
cs_dir, _ := ioutil.TempDir("", "packer-testdir-")
|
||||
cs_file, _ = ioutil.TempFile(cs_dir, "packer-test-")
|
||||
defer os.RemoveAll(cs_dir) // Removes the file as well
|
||||
defer cs_file.Close()
|
||||
ioutil.WriteFile(cs_file.Name(), []byte(cs_bsd_style_subdir), 0666)
|
||||
i.ISOChecksumURL = fmt.Sprintf("%s%s", filePrefix, cs_file.Name())
|
||||
i.RawSingleISOUrl = fmt.Sprintf("%s%s", cs_dir, "/subdir/the-OS.iso")
|
||||
warns, err = i.Prepare(nil)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if i.ISOChecksum != "baz" {
|
||||
t.Fatalf("should've found \"baz\" got: %s", i.ISOChecksum)
|
||||
}
|
||||
|
||||
// Test good - ISOChecksumURL GNU style no newline
|
||||
i = testISOConfig()
|
||||
i.ISOChecksum = ""
|
||||
cs_file, _ = ioutil.TempFile("", "packer-test-")
|
||||
defer os.Remove(cs_file.Name())
|
||||
defer cs_file.Close()
|
||||
ioutil.WriteFile(cs_file.Name(), []byte(cs_gnu_style_no_newline), 0666)
|
||||
i.ISOChecksumURL = fmt.Sprintf("file://%s", cs_file.Name())
|
||||
warns, err = i.Prepare(nil)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if i.ISOChecksum != "bar0" {
|
||||
t.Fatalf("should've found \"bar0\" got: %s", i.ISOChecksum)
|
||||
}
|
||||
|
||||
// Test good - ISOChecksumURL GNU style with query parameters
|
||||
i = testISOConfig()
|
||||
i.ISOChecksum = ""
|
||||
i.RawSingleISOUrl = "http://www.packer.io/the-OS.iso?stuff=boo"
|
||||
|
||||
cs_file, _ = ioutil.TempFile("", "packer-test-")
|
||||
defer os.Remove(cs_file.Name())
|
||||
defer cs_file.Close()
|
||||
ioutil.WriteFile(cs_file.Name(), []byte(cs_gnu_style), 0666)
|
||||
i.ISOChecksumURL = fmt.Sprintf("%s%s", filePrefix, cs_file.Name())
|
||||
warns, err = i.Prepare(nil)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if i.ISOChecksum != "bar0" {
|
||||
t.Fatalf("should've found \"bar0\" got: %s", i.ISOChecksum)
|
||||
}
|
||||
|
||||
// Test good - ISOChecksumURL GNU style with relative path
|
||||
i = testISOConfig()
|
||||
i.ISOChecksum = ""
|
||||
|
||||
cs_file, _ = ioutil.TempFile(cs_dir, "packer-test-")
|
||||
defer os.Remove(cs_file.Name())
|
||||
defer cs_file.Close()
|
||||
ioutil.WriteFile(cs_file.Name(), []byte(cs_gnu_style_subdir), 0666)
|
||||
i.ISOChecksumURL = fmt.Sprintf("%s%s", filePrefix, cs_file.Name())
|
||||
i.RawSingleISOUrl = fmt.Sprintf("%s%s", cs_dir, "/subdir/the-OS.iso")
|
||||
warns, err = i.Prepare(nil)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if i.ISOChecksum != "bar0" {
|
||||
t.Fatalf("should've found \"bar0\" got: %s", i.ISOChecksum)
|
||||
}
|
||||
|
||||
// Test that we won't try to read an iso into memory because of a user
|
||||
// error
|
||||
i = testISOConfig()
|
||||
|
@ -249,6 +91,7 @@ func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Fatalf("should have error because iso is bad filetype: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestISOConfigPrepare_ISOChecksumType(t *testing.T) {
|
||||
|
@ -260,8 +103,8 @@ func TestISOConfigPrepare_ISOChecksumType(t *testing.T) {
|
|||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
// Test good
|
||||
|
@ -279,17 +122,6 @@ func TestISOConfigPrepare_ISOChecksumType(t *testing.T) {
|
|||
t.Fatalf("should've lowercased: %s", i.ISOChecksumType)
|
||||
}
|
||||
|
||||
// Test unknown
|
||||
i = testISOConfig()
|
||||
i.ISOChecksumType = "fake"
|
||||
warns, err = i.Prepare(nil)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("bad: %#v", warns)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test none
|
||||
i = testISOConfig()
|
||||
i.ISOChecksumType = "none"
|
||||
|
|
|
@ -6,10 +6,12 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
"os"
|
||||
|
||||
"github.com/gofrs/flock"
|
||||
getter "github.com/hashicorp/go-getter"
|
||||
urlhelper "github.com/hashicorp/go-getter/helper/url"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/helper/useragent"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
|
@ -47,129 +49,114 @@ type StepDownload struct {
|
|||
Extension string
|
||||
}
|
||||
|
||||
func (s *StepDownload) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
cache := state.Get("cache").(packer.Cache)
|
||||
func (s *StepDownload) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
var checksum []byte
|
||||
if s.Checksum != "" {
|
||||
var err error
|
||||
checksum, err = hex.DecodeString(s.Checksum)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error parsing checksum: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
defer ui.Say(fmt.Sprintf("leaving retrieve loop for %s", s.Description))
|
||||
|
||||
ui.Say(fmt.Sprintf("Retrieving %s", s.Description))
|
||||
|
||||
// First try to use any already downloaded file
|
||||
// If it fails, proceed to regular download logic
|
||||
|
||||
var downloadConfigs = make([]*DownloadConfig, len(s.Url))
|
||||
var finalPath string
|
||||
for i, url := range s.Url {
|
||||
targetPath := s.TargetPath
|
||||
if targetPath == "" {
|
||||
// Determine a cache key. This is normally just the URL but
|
||||
// if we force a certain extension we hash the URL and add
|
||||
// the extension to force it.
|
||||
cacheKey := url
|
||||
if s.Extension != "" {
|
||||
hash := sha1.Sum([]byte(url))
|
||||
cacheKey = fmt.Sprintf(
|
||||
"%s.%s", hex.EncodeToString(hash[:]), s.Extension)
|
||||
}
|
||||
|
||||
log.Printf("Acquiring lock to download: %s", url)
|
||||
targetPath = cache.Lock(cacheKey)
|
||||
defer cache.Unlock(cacheKey)
|
||||
var errs []error
|
||||
for _, source := range s.Url {
|
||||
if ctx.Err() != nil {
|
||||
state.Put("error", fmt.Errorf("Download cancelled: %v", errs))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
config := &DownloadConfig{
|
||||
Url: url,
|
||||
TargetPath: targetPath,
|
||||
CopyFile: false,
|
||||
Hash: HashForType(s.ChecksumType),
|
||||
Checksum: checksum,
|
||||
UserAgent: useragent.String(),
|
||||
}
|
||||
downloadConfigs[i] = config
|
||||
|
||||
if match, _ := NewDownloadClient(config, ui).VerifyChecksum(ui, config.TargetPath); match {
|
||||
ui.Message(fmt.Sprintf("Found already downloaded, initial checksum matched, no download needed: %s", url))
|
||||
finalPath = config.TargetPath
|
||||
break
|
||||
ui.Say(fmt.Sprintf("Trying %s", source))
|
||||
dst, err := s.download(ctx, ui, source)
|
||||
if err == nil {
|
||||
state.Put(s.ResultKey, dst)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
// may be another url will work
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if finalPath == "" {
|
||||
for i := range s.Url {
|
||||
config := downloadConfigs[i]
|
||||
state.Put("error", fmt.Errorf("Downloading file: %v", errs))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
path, err, retry := s.download(config, state)
|
||||
if err != nil {
|
||||
ui.Message(fmt.Sprintf("Error downloading: %s", err))
|
||||
}
|
||||
func (s *StepDownload) download(ctx context.Context, ui packer.Ui, source string) (string, error) {
|
||||
u, err := urlhelper.Parse(source)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("url parse: %s", err)
|
||||
}
|
||||
if checksum := u.Query().Get("checksum"); checksum != "" {
|
||||
s.Checksum = checksum
|
||||
}
|
||||
if s.ChecksumType != "" && s.ChecksumType != "none" {
|
||||
// add checksum to url query params as go getter will checksum for us
|
||||
q := u.Query()
|
||||
q.Set("checksum", s.ChecksumType+":"+s.Checksum)
|
||||
u.RawQuery = q.Encode()
|
||||
} else if s.Checksum != "" {
|
||||
q := u.Query()
|
||||
q.Set("checksum", s.Checksum)
|
||||
u.RawQuery = q.Encode()
|
||||
} else if s.ChecksumType != "none" {
|
||||
return "", fmt.Errorf("Empty checksum")
|
||||
}
|
||||
|
||||
if !retry {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
finalPath = path
|
||||
break
|
||||
}
|
||||
targetPath := s.TargetPath
|
||||
if targetPath == "" {
|
||||
// store file under sha1(hash) if set
|
||||
// hash can sometimes be a checksum url
|
||||
// otherwise, use sha1(source_url)
|
||||
var shaSum [20]byte
|
||||
if s.Checksum != "" {
|
||||
shaSum = sha1.Sum([]byte(s.Checksum))
|
||||
} else {
|
||||
shaSum = sha1.Sum([]byte(u.String()))
|
||||
}
|
||||
targetPath = hex.EncodeToString(shaSum[:])
|
||||
if s.Extension != "" {
|
||||
targetPath += "." + s.Extension
|
||||
}
|
||||
}
|
||||
targetPath, err = packer.CachePath(targetPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("CachePath: %s", err)
|
||||
}
|
||||
lockFile := targetPath + ".lock"
|
||||
|
||||
if finalPath == "" {
|
||||
err := fmt.Errorf("%s download failed.", s.Description)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
log.Printf("Acquiring lock for: %s (%s)", u.String(), lockFile)
|
||||
lock := flock.New(lockFile)
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Printf("get working directory: %v", err)
|
||||
// here we ignore the error in case the
|
||||
// working directory is not needed.
|
||||
// It would be better if the go-getter
|
||||
// could guess it only in cases it is
|
||||
// necessary.
|
||||
}
|
||||
|
||||
state.Put(s.ResultKey, finalPath)
|
||||
return multistep.ActionContinue
|
||||
ui.Say(fmt.Sprintf("Trying %s", u.String()))
|
||||
gc := getter.Client{
|
||||
Ctx: ctx,
|
||||
Dst: targetPath,
|
||||
Src: u.String(),
|
||||
ProgressListener: ui,
|
||||
Pwd: wd,
|
||||
Dir: false,
|
||||
}
|
||||
|
||||
switch err := gc.Get(); err.(type) {
|
||||
case nil: // success !
|
||||
ui.Say(fmt.Sprintf("%s => %s", u.String(), targetPath))
|
||||
return targetPath, nil
|
||||
case *getter.ChecksumError:
|
||||
ui.Say(fmt.Sprintf("Checksum did not match, removing %s", targetPath))
|
||||
if err := os.Remove(targetPath); err != nil {
|
||||
ui.Error(fmt.Sprintf("Failed to remove cache file. Please remove manually: %s", targetPath))
|
||||
}
|
||||
return "", err
|
||||
default:
|
||||
ui.Say(fmt.Sprintf("Download failed %s", err))
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StepDownload) Cleanup(multistep.StateBag) {}
|
||||
|
||||
func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag) (string, error, bool) {
|
||||
var path string
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Create download client with config
|
||||
download := NewDownloadClient(config, ui)
|
||||
|
||||
downloadCompleteCh := make(chan error, 1)
|
||||
go func() {
|
||||
var err error
|
||||
path, err = download.Get(ui)
|
||||
downloadCompleteCh <- err
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-downloadCompleteCh:
|
||||
|
||||
if err != nil {
|
||||
return "", err, true
|
||||
}
|
||||
if download.config.CopyFile {
|
||||
ui.Message(fmt.Sprintf("Transferred: %s", config.Url))
|
||||
} else {
|
||||
ui.Message(fmt.Sprintf("Using file in-place: %s", config.Url))
|
||||
}
|
||||
|
||||
return path, nil, true
|
||||
|
||||
case <-time.After(1 * time.Second):
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
ui.Say("Interrupt received. Cancelling download...")
|
||||
return "", nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,254 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
urlhelper "github.com/hashicorp/go-getter/helper/url"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer/tmp"
|
||||
)
|
||||
|
||||
func TestStepDownload_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepDownload)
|
||||
if _, ok := raw.(multistep.Step); !ok {
|
||||
t.Fatalf("download should be a step")
|
||||
var _ multistep.Step = new(StepDownload)
|
||||
|
||||
func toSha1(in string) string {
|
||||
b := sha1.Sum([]byte(in))
|
||||
return hex.EncodeToString(b[:])
|
||||
}
|
||||
|
||||
func abs(t *testing.T, path string) string {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
u, err := urlhelper.Parse(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func TestStepDownload_Run(t *testing.T) {
|
||||
srvr := httptest.NewServer(http.FileServer(http.Dir("test-fixtures")))
|
||||
defer srvr.Close()
|
||||
|
||||
cs := map[string]string{
|
||||
"/root/basic.txt": "f572d396fae9206628714fb2ce00f72e94f2258f",
|
||||
"/root/another.txt": "7c6e5dd1bacb3b48fdffba2ed096097eb172497d",
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
Checksum string
|
||||
ChecksumType string
|
||||
Description string
|
||||
ResultKey string
|
||||
TargetPath string
|
||||
Url []string
|
||||
Extension string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want multistep.StepAction
|
||||
wantFiles []string
|
||||
}{
|
||||
{"not passing a checksum fails",
|
||||
fields{Url: []string{abs(t, "./test-fixtures/root/another.txt")}},
|
||||
multistep.ActionHalt,
|
||||
nil,
|
||||
},
|
||||
{"none checksum works, without a checksum",
|
||||
fields{Url: []string{abs(t, "./test-fixtures/root/another.txt")}, ChecksumType: "none"},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(abs(t, "./test-fixtures/root/another.txt")),
|
||||
toSha1(abs(t, "./test-fixtures/root/another.txt")) + ".lock",
|
||||
},
|
||||
},
|
||||
{"bad checksum removes file - checksum from string - no Checksum Type",
|
||||
fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/another.txt")}, Checksum: cs["/root/basic.txt"]},
|
||||
multistep.ActionHalt,
|
||||
[]string{
|
||||
toSha1(cs["/root/basic.txt"]) + ".txt.lock", // a lock file is created & deleted on mac for each download
|
||||
},
|
||||
},
|
||||
{"bad checksum removes file - checksum from string - Checksum Type",
|
||||
fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/another.txt")}, ChecksumType: "sha1", Checksum: cs["/root/basic.txt"]},
|
||||
multistep.ActionHalt,
|
||||
[]string{
|
||||
toSha1(cs["/root/basic.txt"]) + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"bad checksum removes file - checksum from url - Checksum Type",
|
||||
fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/basic.txt")}, Checksum: srvr.URL + "/root/another.txt.sha1sum", ChecksumType: "file"},
|
||||
multistep.ActionHalt,
|
||||
[]string{
|
||||
toSha1(srvr.URL+"/root/another.txt.sha1sum") + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull http dl - checksum from http file - parameter",
|
||||
fields{Extension: "txt", Url: []string{srvr.URL + "/root/another.txt"}, Checksum: srvr.URL + "/root/another.txt.sha1sum", ChecksumType: "file"},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(srvr.URL+"/root/another.txt.sha1sum") + ".txt",
|
||||
toSha1(srvr.URL+"/root/another.txt.sha1sum") + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull http dl - checksum from http file - url",
|
||||
fields{Extension: "txt", Url: []string{srvr.URL + "/root/another.txt?checksum=file:" + srvr.URL + "/root/another.txt.sha1sum"}},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1("file:"+srvr.URL+"/root/another.txt.sha1sum") + ".txt",
|
||||
toSha1("file:"+srvr.URL+"/root/another.txt.sha1sum") + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull http dl - checksum from url",
|
||||
fields{Extension: "txt", Url: []string{srvr.URL + "/root/another.txt?checksum=" + cs["/root/another.txt"]}},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(cs["/root/another.txt"]) + ".txt",
|
||||
toSha1(cs["/root/another.txt"]) + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull http dl - checksum from parameter - no checksum type",
|
||||
fields{Extension: "txt", Url: []string{srvr.URL + "/root/another.txt?"}, Checksum: cs["/root/another.txt"]},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(cs["/root/another.txt"]) + ".txt",
|
||||
toSha1(cs["/root/another.txt"]) + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull http dl - checksum from parameter - checksum type",
|
||||
fields{Extension: "txt", Url: []string{srvr.URL + "/root/another.txt?"}, ChecksumType: "sha1", Checksum: cs["/root/another.txt"]},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(cs["/root/another.txt"]) + ".txt",
|
||||
toSha1(cs["/root/another.txt"]) + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull relative symlink - checksum from url",
|
||||
fields{Extension: "txt", Url: []string{"./test-fixtures/root/another.txt?checksum=" + cs["/root/another.txt"]}},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(cs["/root/another.txt"]) + ".txt",
|
||||
toSha1(cs["/root/another.txt"]) + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull relative symlink - checksum from parameter - no checksum type",
|
||||
fields{Extension: "txt", Url: []string{"./test-fixtures/root/another.txt?"}, Checksum: cs["/root/another.txt"]},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(cs["/root/another.txt"]) + ".txt",
|
||||
toSha1(cs["/root/another.txt"]) + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull relative symlink - checksum from parameter - checksum type",
|
||||
fields{Extension: "txt", Url: []string{"./test-fixtures/root/another.txt?"}, ChecksumType: "sha1", Checksum: cs["/root/another.txt"]},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(cs["/root/another.txt"]) + ".txt",
|
||||
toSha1(cs["/root/another.txt"]) + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull absolute symlink - checksum from url",
|
||||
fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/another.txt") + "?checksum=" + cs["/root/another.txt"]}},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(cs["/root/another.txt"]) + ".txt",
|
||||
toSha1(cs["/root/another.txt"]) + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull absolute symlink - checksum from parameter - no checksum type",
|
||||
fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/another.txt") + "?"}, Checksum: cs["/root/another.txt"]},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(cs["/root/another.txt"]) + ".txt",
|
||||
toSha1(cs["/root/another.txt"]) + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"successfull absolute symlink - checksum from parameter - checksum type",
|
||||
fields{Extension: "txt", Url: []string{abs(t, "./test-fixtures/root/another.txt") + "?"}, ChecksumType: "sha1", Checksum: cs["/root/another.txt"]},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(cs["/root/another.txt"]) + ".txt",
|
||||
toSha1(cs["/root/another.txt"]) + ".txt.lock",
|
||||
},
|
||||
},
|
||||
{"wrong first 2 urls - absolute urls - checksum from parameter - no checksum type",
|
||||
fields{
|
||||
Url: []string{
|
||||
abs(t, "./test-fixtures/root/another.txt"),
|
||||
abs(t, "./test-fixtures/root/not_found"),
|
||||
abs(t, "./test-fixtures/root/basic.txt"),
|
||||
},
|
||||
Checksum: cs["/root/basic.txt"],
|
||||
},
|
||||
multistep.ActionContinue,
|
||||
[]string{
|
||||
toSha1(cs["/root/basic.txt"]),
|
||||
toSha1(cs["/root/basic.txt"]) + ".lock",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dir := createTempDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
s := &StepDownload{
|
||||
TargetPath: tt.fields.TargetPath,
|
||||
Checksum: tt.fields.Checksum,
|
||||
ChecksumType: tt.fields.ChecksumType,
|
||||
ResultKey: tt.fields.ResultKey,
|
||||
Url: tt.fields.Url,
|
||||
Extension: tt.fields.Extension,
|
||||
Description: tt.name,
|
||||
}
|
||||
defer os.Setenv("PACKER_CACHE_DIR", os.Getenv("PACKER_CACHE_DIR"))
|
||||
os.Setenv("PACKER_CACHE_DIR", dir)
|
||||
|
||||
if got := s.Run(context.Background(), testState(t)); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Fatalf("StepDownload.Run() = %v, want %v", got, tt.want)
|
||||
}
|
||||
files := listFiles(t, dir)
|
||||
if diff := cmp.Diff(tt.wantFiles, files); diff != "" {
|
||||
t.Fatalf("file list differs in %s: %s", dir, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createTempDir(t *testing.T) string {
|
||||
dir, err := tmp.Dir("pkr")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func listFiles(t *testing.T, dir string) []string {
|
||||
fs, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var files []string
|
||||
for _, file := range fs {
|
||||
if file.Name() == "." {
|
||||
continue
|
||||
}
|
||||
files = append(files, file.Name())
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
7c6e5dd1bacb3b48fdffba2ed096097eb172497d another.txt
|
|
@ -0,0 +1 @@
|
|||
f572d396fae9206628714fb2ce00f72e94f2258f basic.txt
|
39
go.mod
39
go.mod
|
@ -6,7 +6,6 @@ require (
|
|||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Azure/go-autorest v10.12.0+incompatible
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 // indirect
|
||||
github.com/Bowery/prompt v0.0.0-20180817134258-8a1d5376df1c // indirect
|
||||
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290
|
||||
github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895 // indirect
|
||||
github.com/Jeffail/gabs v1.1.1 // indirect
|
||||
|
@ -31,12 +30,11 @@ require (
|
|||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 // indirect
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||
github.com/cenkalti/backoff v2.1.0+incompatible // indirect
|
||||
github.com/cheggaaa/pb v1.0.26
|
||||
github.com/cheggaaa/pb v1.0.27
|
||||
github.com/circonus-labs/circonus-gometrics v2.2.5+incompatible // indirect
|
||||
github.com/circonus-labs/circonusllhist v0.1.3 // indirect
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
|
||||
github.com/creack/goselect v0.0.0-20180210034346-528c74964609 // indirect
|
||||
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 // indirect
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f // indirect
|
||||
github.com/denverdino/aliyungo v0.0.0-20190220033614-36e2ae938978
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
|
@ -46,22 +44,19 @@ require (
|
|||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.3.3 // indirect
|
||||
github.com/duosecurity/duo_api_golang v0.0.0-20181210160733-61e0defebf22 // indirect
|
||||
github.com/dustin/go-humanize v0.0.0-20170228161531-259d2a102b87 // indirect
|
||||
github.com/dylanmei/iso8601 v0.1.0 // indirect
|
||||
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/go-ini/ini v1.25.4
|
||||
github.com/go-ldap/ldap v2.5.1+incompatible // indirect
|
||||
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||
github.com/go-test/deep v1.0.1 // indirect
|
||||
github.com/gocql/gocql v0.0.0-20181124151448-70385f88b28b // indirect
|
||||
github.com/gofrs/flock v0.7.0
|
||||
github.com/gogo/protobuf v1.2.0 // indirect
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||
github.com/google/go-cmp v0.0.0-20180328201512-5411ab924f9f
|
||||
github.com/google/go-github v17.0.0+incompatible // indirect
|
||||
github.com/google/go-querystring v0.0.0-20151028211038-2a60fc2ba6c1 // indirect
|
||||
github.com/google/go-cmp v0.2.0
|
||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9
|
||||
github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d
|
||||
github.com/gophercloud/gophercloud v0.0.0-20180903124057-ea7289ebdf06
|
||||
|
@ -72,7 +67,8 @@ require (
|
|||
github.com/hashicorp/consul v0.0.0-20180807174550-3e6313bebbf0
|
||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce
|
||||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20160217214820-875fb671b3dd
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0
|
||||
github.com/hashicorp/go-getter v1.2.0
|
||||
github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f // indirect
|
||||
github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa // indirect
|
||||
github.com/hashicorp/go-memdb v0.0.0-20181108192425-032f93b25bec // indirect
|
||||
|
@ -84,7 +80,7 @@ require (
|
|||
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 // indirect
|
||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect
|
||||
github.com/hashicorp/go-uuid v0.0.0-20160329185618-73d19cdc2bf0
|
||||
github.com/hashicorp/go-version v0.0.0-20160119211326-7e3c02b30806
|
||||
github.com/hashicorp/go-version v1.1.0
|
||||
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47 // indirect
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce // indirect
|
||||
github.com/hashicorp/memberlist v0.1.0 // indirect
|
||||
|
@ -97,7 +93,6 @@ require (
|
|||
github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee // indirect
|
||||
github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/kardianos/govendor v1.0.9 // indirect
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
|
||||
github.com/keybase/go-crypto v0.0.0-20181127160227-255a5089e85a // indirect
|
||||
github.com/klauspost/compress v0.0.0-20160131094358-f86d2e6d8a77 // indirect
|
||||
|
@ -105,30 +100,24 @@ require (
|
|||
github.com/klauspost/crc32 v0.0.0-20160114101742-999f3125931f // indirect
|
||||
github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec
|
||||
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 // indirect
|
||||
github.com/lib/pq v1.0.0 // indirect
|
||||
github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c // indirect
|
||||
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c // indirect
|
||||
github.com/masterzen/simplexml v0.0.0-20140219194429-95ba30457eb1 // indirect
|
||||
github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939
|
||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.0-20170510074858-97311d9f7767 // indirect
|
||||
github.com/mattn/go-tty v0.0.0-20181127064339-e4f871175a2f
|
||||
github.com/miekg/dns v1.1.1 // indirect
|
||||
github.com/mitchellh/cli v0.0.0-20170908181043-65fcae5817c8
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7
|
||||
github.com/mitchellh/go-homedir v0.0.0-20151025052427-d682a8f0cf13
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.0.0
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
|
||||
github.com/mitchellh/iochan v0.0.0-20150529224432-87b45ffd0e95
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180111000720-b4575eea38cc
|
||||
github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557
|
||||
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784
|
||||
github.com/mitchellh/reflectwalk v1.0.0
|
||||
github.com/mna/pigeon v1.0.0 // indirect
|
||||
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef // indirect
|
||||
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215 // indirect
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
|
||||
|
@ -165,22 +154,18 @@ require (
|
|||
github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9 // indirect
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
||||
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1
|
||||
github.com/ulikunitz/xz v0.0.0-20180703112113-636d36a76670
|
||||
github.com/ulikunitz/xz v0.5.5
|
||||
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311
|
||||
github.com/xanzy/go-cloudstack v2.4.1+incompatible
|
||||
golang.org/x/crypto v0.0.0-20180322175230-88942b9c40a4
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
|
||||
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5 // indirect
|
||||
google.golang.org/api v0.0.0-20180818000503-e21acd801f91
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06
|
||||
google.golang.org/api v0.1.0
|
||||
google.golang.org/appengine v1.4.0 // indirect
|
||||
google.golang.org/grpc v1.17.0 // indirect
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.27 // indirect
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
|
||||
gopkg.in/h2non/gock.v1 v1.0.12 // indirect
|
||||
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516 // indirect
|
||||
|
|
170
go.sum
170
go.sum
|
@ -1,5 +1,13 @@
|
|||
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.36.0 h1:+aCSj7tOo2LODWVEuZDZeGCckdt6MlSF+X/rB3wUiS8=
|
||||
cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1 h1:RMTyvS5bjvSWiUcfqfr/E2pxHEMrALvU+E12n6biymg=
|
||||
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1/go.mod h1:61apmbkVJH4kg+38ftT+/l0XxdUCVnHggqcOTqZRSEE=
|
||||
github.com/Azure/azure-sdk-for-go v17.3.1+incompatible h1:9Nzge8xxnYm5lVRkvTpG1odiDN0fYDorQwVEaVfg1+g=
|
||||
|
@ -10,8 +18,7 @@ github.com/Azure/go-autorest v10.12.0+incompatible h1:6YphwUK+oXbzvCc1fd5VrnxCek
|
|||
github.com/Azure/go-autorest v10.12.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 h1:pSm8mp0T2OH2CPmPDPtwHPr3VAQaOwVF/JbllOPP4xA=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/Bowery/prompt v0.0.0-20180817134258-8a1d5376df1c h1:fAMg70P5ydy1uiIj6CdA69h6nmQKbv18VlVOXhKNrcM=
|
||||
github.com/Bowery/prompt v0.0.0-20180817134258-8a1d5376df1c/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290 h1:K9I21XUHNbYD3GNMmJBN0UKJCpdP+glftwNZ7Bo8kqY=
|
||||
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
|
||||
github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895 h1:dmc/C8bpE5VkQn65PNbbyACDC8xw8Hpp/NEurdPmQDQ=
|
||||
|
@ -34,6 +41,7 @@ github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KM
|
|||
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f h1:jI4DIE5Vf4oRaHfthB0oRhU+yuYuoOTurDzwAlskP00=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd h1:S3Fr6QnkpW9VRjiEY4psQHhhbbahASuNVj52YIce7lI=
|
||||
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
|
||||
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 h1:BFFG6KP8ASFBg2ptWsJn8p8RDufBjBDKIxLU7BTYGOM=
|
||||
|
@ -48,10 +56,13 @@ github.com/armon/go-radix v0.0.0-20160115234725-4239b77079c7 h1:MBXhrxjNkjdqJysf
|
|||
github.com/armon/go-radix v0.0.0-20160115234725-4239b77079c7/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
|
||||
github.com/aws/aws-sdk-go v1.16.24 h1:I/A3Hwbgs3IEAP6v1bFpHKXiT7wZDoToX9cb00nxZnM=
|
||||
github.com/aws/aws-sdk-go v1.16.24/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
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=
|
||||
github.com/bgentry/speakeasy v0.0.0-20150902231413-36e9cfdd6909 h1:mUVWHQ4tjVv86uJhxSbYqwdz4o+Imcl6HoZtoaqC3zM=
|
||||
github.com/bgentry/speakeasy v0.0.0-20150902231413-36e9cfdd6909/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3 h1:3b+p838vN4sc37brz9W2HDphtSwZFcXZwFLyzm5Vk28=
|
||||
|
@ -60,10 +71,11 @@ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYE
|
|||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/cenkalti/backoff v2.1.0+incompatible h1:FIRvWBZrzS4YC7NT5cOuZjexzFvIr+Dbi6aD1cZaNBk=
|
||||
github.com/cenkalti/backoff v2.1.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cheggaaa/pb v1.0.26 h1:cxVZXxXCTNW7yYwnrTAhJ42LcWrLjp676j+y1AmmLKA=
|
||||
github.com/cheggaaa/pb v1.0.26/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/cheggaaa/pb v1.0.27 h1:wIkZHkNfC7R6GI5w7l/PdAdzXzlrbcI3p8OAlnkTsnc=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/circonus-labs/circonus-gometrics v2.2.5+incompatible h1:KsuY3ogbxgVv3FNhbLUoT+SE9znoWEUIuChSIT4HukI=
|
||||
github.com/circonus-labs/circonus-gometrics v2.2.5+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA=
|
||||
|
@ -71,22 +83,17 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
|
|||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/goselect v0.0.0-20180210034346-528c74964609 h1:FSxXMd2wCHj6GqgBdo4UtVA9R2aieIDvSniepqyOppU=
|
||||
github.com/creack/goselect v0.0.0-20180210034346-528c74964609/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA=
|
||||
github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||
github.com/denverdino/aliyungo v0.0.0-20180417075537-ebad04655e03 h1:NYqwUkb/ypX8OZmBDpPPbZ4npAPjlGFXZ4aSOyRzRL8=
|
||||
github.com/denverdino/aliyungo v0.0.0-20180417075537-ebad04655e03/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
|
||||
github.com/denverdino/aliyungo v0.0.0-20190220033614-36e2ae938978 h1:oyfbRmu7YnytN4bXhvJPY1HPgUL52j5AxtgGDU0bMVs=
|
||||
github.com/denverdino/aliyungo v0.0.0-20190220033614-36e2ae938978/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/digitalocean/godo v0.0.0-20170407151542-4c04abe183f4 h1:34XBbvedApvUMZY6E6Q95ksnpl+FtX+KF3ywZzZY6X0=
|
||||
github.com/digitalocean/godo v0.0.0-20170407151542-4c04abe183f4/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=
|
||||
github.com/digitalocean/godo v1.7.3 h1:0tFPilFBDsVSiAKF8hyzj/MAmRYxNc5MGhAiYZYVrpo=
|
||||
github.com/digitalocean/godo v1.7.3/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=
|
||||
github.com/dnaeon/go-vcr v1.0.0 h1:1QZ+ahihvRvppcJnFvuoHAdnZTf1PqKjO4Ftr1cfQTo=
|
||||
|
@ -99,8 +106,8 @@ github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk
|
|||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/duosecurity/duo_api_golang v0.0.0-20181210160733-61e0defebf22 h1:RqZJa9Ohzpyr5OADCKz/8t0vtdD2vczMdjqCbvTYz7o=
|
||||
github.com/duosecurity/duo_api_golang v0.0.0-20181210160733-61e0defebf22/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo=
|
||||
github.com/dustin/go-humanize v0.0.0-20170228161531-259d2a102b87 h1:uPzP/9GIqYKvZAmz4IayKMMZiWRWNtGynUREBtTXPXA=
|
||||
github.com/dustin/go-humanize v0.0.0-20170228161531-259d2a102b87/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI=
|
||||
github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ=
|
||||
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08 h1:0bp6/GrNOrTDtSXe9YYGCwf8jp5Fb/b+4a6MTRm4qzY=
|
||||
|
@ -111,8 +118,11 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
|||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-ldap/ldap v2.5.1+incompatible h1:Opaoft5zMW8IU/VRULB0eGMBQ9P5buRvCW6sFTRmMn8=
|
||||
|
@ -123,36 +133,50 @@ github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
|
|||
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gocql/gocql v0.0.0-20181124151448-70385f88b28b h1:dnUw9Ih14dCKzbtZxm+pwQRYIb+9ypiwtZgsCQN4zmg=
|
||||
github.com/gocql/gocql v0.0.0-20181124151448-70385f88b28b/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
|
||||
github.com/gofrs/flock v0.7.0 h1:pGFUjl501gafK9HBt1VGL1KCOd/YhIooID+xgyJCf3g=
|
||||
github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.0.0-20180328201512-5411ab924f9f h1:e7go61E2Xh2l5KtL3Lh6Tjy0/T+eem0bQsDwETI7SMo=
|
||||
github.com/google/go-cmp v0.0.0-20180328201512-5411ab924f9f/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v0.0.0-20151028211038-2a60fc2ba6c1 h1:ULA7QZ5KGgwOBeBBCA7Xyv96fmriEfhs0D2KDRslTd8=
|
||||
github.com/google/go-querystring v0.0.0-20151028211038-2a60fc2ba6c1/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 h1:JM174NTeGNJ2m/oLH3UOWOvWQQKd+BoL3hcSCUWFLt0=
|
||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||
github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d h1:rXQlD9GXkjA/PQZhmEaF/8Pj/sJfdZJK7GJG0gkS8I0=
|
||||
github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3 h1:siORttZ36U2R/WjiJuDz8znElWBiAlO9rVt+mqJt0Cc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gophercloud/gophercloud v0.0.0-20180903124057-ea7289ebdf06 h1:m7Rt/8En7PLrM7PQpykdZBPKUdgZWN6MwiA/ChVIoxs=
|
||||
github.com/gophercloud/gophercloud v0.0.0-20180903124057-ea7289ebdf06/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
|
||||
github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6 h1:Cw/B8Bu7Rryomxf7bjc8zNfIyLgjxsDd91n0eGRWpuo=
|
||||
github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 h1:JIM+OacoOJRU30xpjMf8sulYqjr0ViA3WDrTX6j/yDI=
|
||||
github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hashicorp/consul v0.0.0-20180807174550-3e6313bebbf0 h1:vh/G7ew4mi/FcKNrRk4x1N1fWYzYB5/AaSHr9W+wrF4=
|
||||
|
@ -161,8 +185,10 @@ github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce h1:prjrVgOk2Yg6w
|
|||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8=
|
||||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4=
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20160217214820-875fb671b3dd h1:g63OFYSdtNzMg+OszHIUmlRweTB2syHVsZDBKoa1km8=
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20160217214820-875fb671b3dd/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-getter v1.2.0 h1:E05bVPilzyh2yXgT6srn7WEkfMZaH+LuX9tDJw/4kaE=
|
||||
github.com/hashicorp/go-getter v1.2.0/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f h1:Yv9YzBlAETjy6AOX9eLBZ3nshNVRREgerT/3nvxlGho=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
|
@ -182,12 +208,14 @@ github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6 h1:qCv4
|
|||
github.com/hashicorp/go-retryablehttp v0.0.0-20180718195005-e651d75abec6/go.mod h1:fXcdFsQoipQa7mwORhKad5jmDCeSy/RCGzWA08PO0lM=
|
||||
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E=
|
||||
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
|
||||
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
|
||||
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
|
||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
|
||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-uuid v0.0.0-20160329185618-73d19cdc2bf0 h1:YnYV/qHLtjZmhFnWaSsNqVAUhxCuPKRe5wDGpOKYL/U=
|
||||
github.com/hashicorp/go-uuid v0.0.0-20160329185618-73d19cdc2bf0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v0.0.0-20160119211326-7e3c02b30806 h1:0MKTKHll8VOZ0ciR+yTIEJ8JrT0UUiRDsgtujJT7a8U=
|
||||
github.com/hashicorp/go-version v0.0.0-20160119211326-7e3c02b30806/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47 h1:UnszMmmmm5vLwWzDjTFVIkfhvWF1NdrmChl8L2NUDCw=
|
||||
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
|
||||
|
@ -210,14 +238,14 @@ github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775 h1:MIteIo
|
|||
github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775/go.mod h1:R9rU87RxxmcD3DkspW9JqGBXiJyg5MA+WNCtJrBtnXs=
|
||||
github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee h1:AQ/QmCk6x8ECPpf2pkPtA4lyncEEBbs8VFnVXPYKhIs=
|
||||
github.com/jefferai/jsonx v0.0.0-20160721235117-9cc31c3135ee/go.mod h1:N0t2vlmpe8nyZB5ouIbJQPDSR+mH6oe7xHB9VZHSUzM=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564 h1:+HMa2xWQOm+9ebsl0+XsuLaPuFCxExv3sCXo5psVzYI=
|
||||
github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kardianos/govendor v1.0.9 h1:WOH3FcVI9eOgnIZYg96iwUwrL4eOVx+aQ66oyX2R8Yc=
|
||||
github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/keybase/go-crypto v0.0.0-20181127160227-255a5089e85a h1:X/UFlwD2/UV0RCy+8ITi4DmxJwk83YUH7bXwkJIHHMo=
|
||||
|
@ -236,6 +264,7 @@ github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8Pwwd
|
|||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
|
@ -252,14 +281,15 @@ github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939 h1:cRFHA33ER97Xy5j
|
|||
github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7 h1:owMyzMR4QR+jSdlfkX9jPU3rsby4++j99BfbtgVr6ZY=
|
||||
github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.0-20170510074858-97311d9f7767 h1:Nk2R0tWpD2RdkQ+53zE6kWnSGuhQyDlnOs2MPiqVubE=
|
||||
github.com/mattn/go-runewidth v0.0.0-20170510074858-97311d9f7767/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-tty v0.0.0-20181127064339-e4f871175a2f h1:4P7Ul+TAnk92vTeVkXs6VLjmf1EhrYtDRa03PCYY6VM=
|
||||
github.com/mattn/go-tty v0.0.0-20181127064339-e4f871175a2f/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
|
||||
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v0.0.0-20170908181043-65fcae5817c8 h1:ZPmOsqSxGDUqFp0ErYpJDIfaBRBGvQv0qDPr37kZBrk=
|
||||
|
@ -268,8 +298,8 @@ github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMK
|
|||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7 h1:PXPMDtfqV+rZJshQHOiwUFqlqErXaAcuWy+/ZmyRfNc=
|
||||
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7/go.mod h1:g7SZj7ABpStq3tM4zqHiVEG5un/DZ1+qJJKO7qx1EvU=
|
||||
github.com/mitchellh/go-homedir v0.0.0-20151025052427-d682a8f0cf13 h1:VWOqu/UItbEBupQwKkabbfnoEbgAE4IGjZBH2MFx8jY=
|
||||
github.com/mitchellh/go-homedir v0.0.0-20151025052427-d682a8f0cf13/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
|
@ -285,14 +315,14 @@ github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784 h1:+DAetXqxv/
|
|||
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mna/pigeon v1.0.0 h1:n46IoStjdzjaXuyBH53j9HZ8CVqGWpC7P5/v8dP4qEY=
|
||||
github.com/mna/pigeon v1.0.0/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04=
|
||||
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef h1:E/seV1Rtsnr2juBw1Dfz4iDPT3/5s1H/BATx+ePmSyo=
|
||||
github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef/go.mod h1:LgKrp0Iss/BVwquptq4eIe6HPr0s3t1WHT5x0qOh14U=
|
||||
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215 h1:y6FZWUBBt1iPmJyGbGza3ncvVBMKzgd32oFChRZR7Do=
|
||||
github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215/go.mod h1:CxM/JGtpRrEPve5H04IhxJrGhxgwxMc6jSP2T4YD60w=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
|
@ -310,6 +340,7 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo
|
|||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/oracle/oci-go-sdk v1.8.0 h1:4SO45bKV0I3/Mn1os3ANDZmV0eSE5z5CLdSUIkxtyzs=
|
||||
github.com/oracle/oci-go-sdk v1.8.0/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ory/dockertest v3.3.2+incompatible h1:uO+NcwH6GuFof/Uz8yzjNi1g0sGT5SLAJbdBvD8bUYc=
|
||||
|
@ -334,16 +365,20 @@ github.com/posener/complete v0.0.0-20170908125245-88e59760adad h1:jTlrcNQXxCpT8N
|
|||
github.com/posener/complete v0.0.0-20170908125245-88e59760adad/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible h1:ZoVHH6voxW9Onzo6z2yLtocVoN6mBocyDoqoyAMHokE=
|
||||
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible/go.mod h1:T3/WrziK7fYH3C8ilAFAHe99R452/IzIG3YYkqaOFeQ=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17 h1:4qPms2txLWMLXKzqlnYSulKRS4cS9aYgPtAEpUelQok=
|
||||
github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17/go.mod h1:SAEjPB4voP88qmWJXI7mA5m15uNlEnuHLx4Eu2mPGpQ=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/rwtodd/Go.Sed v0.0.0-20170507045331-d6d5d585814e h1:lN+IKs+Jb9uwDOMO4VJZzH9vOjjist0THR5s9akp+Ss=
|
||||
github.com/rwtodd/Go.Sed v0.0.0-20170507045331-d6d5d585814e/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
|
||||
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
|
||||
|
@ -354,14 +389,40 @@ github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70 h1:DaqC32ZwO
|
|||
github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70/go.mod h1:XjlXWPd6VONhsRSEuzGkV8mzRpH7ou1cdLV7IKJk96s=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v0.0.0-20180315010703-90150a8ed11b h1:m1cItYkpJSJ3ZKPEV/XKbZTcCaJHY1qF+R9GisLZ4kw=
|
||||
github.com/sirupsen/logrus v0.0.0-20180315010703-90150a8ed11b/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v0.0.0-20181220135002-f1744d40d346 h1:a014AaXz7AISMePv8xKRffUZZkr5z2XmSDf41gRV3+A=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v0.0.0-20181220135002-f1744d40d346/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
|
||||
github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9 h1:/Bsw4C+DEdqPjt8vAqaC9LAqpAQnaCQQqmolqq3S1T4=
|
||||
|
@ -370,46 +431,75 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z
|
|||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1 h1:U6ufy3mLDgg9RYupntOvAF7xCmNNquyKaYaaVHo1Nnk=
|
||||
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||
github.com/ulikunitz/xz v0.0.0-20180703112113-636d36a76670 h1:HQWT4ta3wW5GZ790GaqLCS+w1dvuA3rMfEQxLi+UOYU=
|
||||
github.com/ulikunitz/xz v0.0.0-20180703112113-636d36a76670/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
|
||||
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311 h1:s5pyxd5S6wRs2WpEE0xRfWUF46Wbz44h203KnbX0ecI=
|
||||
github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
|
||||
github.com/xanzy/go-cloudstack v2.4.1+incompatible h1:Oc4xa2+I94h1g/QJ+nHoq597nJz2KXzxuQx/weOx0AU=
|
||||
github.com/xanzy/go-cloudstack v2.4.1+incompatible/go.mod h1:s3eL3z5pNXF5FVybcT+LIVdId8pYn709yv6v5mrkrQE=
|
||||
golang.org/x/crypto v0.0.0-20180322175230-88942b9c40a4 h1:AJCW0rhPjFKEAoValWpqnRKxX8YV0Xvqfw+dOexCTPc=
|
||||
golang.org/x/crypto v0.0.0-20180322175230-88942b9c40a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5 h1:ev5exjGDsOo0NPTB0qdCcE53BfWl1IICJlhgXgfT9fM=
|
||||
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
google.golang.org/api v0.0.0-20180818000503-e21acd801f91 h1:MgYYgjaWMS2qQiDwCznfbqNmEOdSULlvjCvSCvIe/Wo=
|
||||
google.golang.org/api v0.0.0-20180818000503-e21acd801f91/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922 h1:mBVYJnbrXLA/ZCBTCe7PtEgAUP+1bg92qTaFoPHdz+8=
|
||||
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
|
||||
|
@ -442,4 +532,8 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
|||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
|
|
@ -145,13 +145,12 @@ func Test(t TestT, c TestCase) {
|
|||
// Run it! We use a temporary directory for caching and discard
|
||||
// any UI output. We discard since it shows up in logs anyways.
|
||||
log.Printf("[DEBUG] Running 'test' build")
|
||||
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||
ui := &packer.BasicUi{
|
||||
Reader: os.Stdin,
|
||||
Writer: ioutil.Discard,
|
||||
ErrorWriter: ioutil.Discard,
|
||||
}
|
||||
artifacts, err := build.Run(ui, cache)
|
||||
artifacts, err := build.Run(ui)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Sprintf("Run error:\n\n%s", err))
|
||||
goto TEARDOWN
|
||||
|
|
13
main.go
13
main.go
|
@ -11,7 +11,6 @@ import (
|
|||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
@ -160,19 +159,12 @@ func wrappedMain() int {
|
|||
)
|
||||
}
|
||||
|
||||
cacheDir := os.Getenv("PACKER_CACHE_DIR")
|
||||
if cacheDir == "" {
|
||||
cacheDir = "packer_cache"
|
||||
}
|
||||
|
||||
cacheDir, err = filepath.Abs(cacheDir)
|
||||
cacheDir, err := packer.CachePath()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
log.Printf("Setting cache directory: %s", cacheDir)
|
||||
cache := &packer.FileCache{CacheDir: cacheDir}
|
||||
|
||||
// Determine if we're in machine-readable mode by mucking around with
|
||||
// the arguments...
|
||||
|
@ -220,8 +212,7 @@ func wrappedMain() int {
|
|||
},
|
||||
Version: version.Version,
|
||||
},
|
||||
Cache: cache,
|
||||
Ui: ui,
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
cli := &cli.CLI{
|
||||
|
|
|
@ -53,7 +53,7 @@ type Build interface {
|
|||
|
||||
// Run runs the actual builder, returning an artifact implementation
|
||||
// of what is built. If anything goes wrong, an error is returned.
|
||||
Run(Ui, Cache) ([]Artifact, error)
|
||||
Run(Ui) ([]Artifact, error)
|
||||
|
||||
// Cancel will cancel a running build. This will block until the build
|
||||
// is actually completely canceled.
|
||||
|
@ -180,7 +180,7 @@ func (b *coreBuild) Prepare() (warn []string, err error) {
|
|||
}
|
||||
|
||||
// Runs the actual build. Prepare must be called prior to running this.
|
||||
func (b *coreBuild) Run(originalUi Ui, cache Cache) ([]Artifact, error) {
|
||||
func (b *coreBuild) Run(originalUi Ui) ([]Artifact, error) {
|
||||
if !b.prepareCalled {
|
||||
panic("Prepare must be called first")
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ func (b *coreBuild) Run(originalUi Ui, cache Cache) ([]Artifact, error) {
|
|||
|
||||
log.Printf("Running builder: %s", b.builderType)
|
||||
ts := CheckpointReporter.AddSpan(b.builderType, "builder", b.builderConfig)
|
||||
builderArtifact, err := b.builder.Run(builderUi, hook, cache)
|
||||
builderArtifact, err := b.builder.Run(builderUi, hook)
|
||||
ts.End(err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -172,12 +172,11 @@ func TestBuildPrepare_variables_default(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBuild_Run(t *testing.T) {
|
||||
cache := &TestCache{}
|
||||
ui := testUi()
|
||||
|
||||
build := testBuild()
|
||||
build.Prepare()
|
||||
artifacts, err := build.Run(ui, cache)
|
||||
artifacts, err := build.Run(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -218,7 +217,6 @@ func TestBuild_Run(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBuild_Run_Artifacts(t *testing.T) {
|
||||
cache := &TestCache{}
|
||||
ui := testUi()
|
||||
|
||||
// Test case: Test that with no post-processors, we only get the
|
||||
|
@ -227,7 +225,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
|||
build.postProcessors = [][]coreBuildPostProcessor{}
|
||||
|
||||
build.Prepare()
|
||||
artifacts, err := build.Run(ui, cache)
|
||||
artifacts, err := build.Run(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -252,7 +250,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
|||
}
|
||||
|
||||
build.Prepare()
|
||||
artifacts, err = build.Run(ui, cache)
|
||||
artifacts, err = build.Run(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -280,7 +278,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
|||
}
|
||||
|
||||
build.Prepare()
|
||||
artifacts, err = build.Run(ui, cache)
|
||||
artifacts, err = build.Run(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -310,7 +308,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
|||
}
|
||||
|
||||
build.Prepare()
|
||||
artifacts, err = build.Run(ui, cache)
|
||||
artifacts, err = build.Run(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -337,7 +335,7 @@ func TestBuild_Run_Artifacts(t *testing.T) {
|
|||
}
|
||||
|
||||
build.Prepare()
|
||||
artifacts, err = build.Run(ui, cache)
|
||||
artifacts, err = build.Run(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -365,7 +363,7 @@ func TestBuild_RunBeforePrepare(t *testing.T) {
|
|||
}
|
||||
}()
|
||||
|
||||
testBuild().Run(testUi(), &TestCache{})
|
||||
testBuild().Run(testUi())
|
||||
}
|
||||
|
||||
func TestBuild_Cancel(t *testing.T) {
|
||||
|
|
|
@ -28,7 +28,7 @@ type Builder interface {
|
|||
Prepare(...interface{}) ([]string, error)
|
||||
|
||||
// Run is where the actual build should take place. It takes a Build and a Ui.
|
||||
Run(ui Ui, hook Hook, cache Cache) (Artifact, error)
|
||||
Run(ui Ui, hook Hook) (Artifact, error)
|
||||
|
||||
// Cancel cancels a possibly running Builder. This should block until
|
||||
// the builder actually cancels and cleans up after itself.
|
||||
|
|
|
@ -16,7 +16,6 @@ type MockBuilder struct {
|
|||
PrepareCalled bool
|
||||
PrepareConfig []interface{}
|
||||
RunCalled bool
|
||||
RunCache Cache
|
||||
RunHook Hook
|
||||
RunUi Ui
|
||||
CancelCalled bool
|
||||
|
@ -28,11 +27,10 @@ func (tb *MockBuilder) Prepare(config ...interface{}) ([]string, error) {
|
|||
return tb.PrepareWarnings, nil
|
||||
}
|
||||
|
||||
func (tb *MockBuilder) Run(ui Ui, h Hook, c Cache) (Artifact, error) {
|
||||
func (tb *MockBuilder) Run(ui Ui, h Hook) (Artifact, error) {
|
||||
tb.RunCalled = true
|
||||
tb.RunHook = h
|
||||
tb.RunUi = ui
|
||||
tb.RunCache = c
|
||||
|
||||
if tb.RunErrResult {
|
||||
return nil, errors.New("foo")
|
||||
|
|
128
packer/cache.go
128
packer/cache.go
|
@ -1,120 +1,30 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Cache implements a caching interface where files can be stored for
|
||||
// re-use between multiple runs.
|
||||
type Cache interface {
|
||||
// Lock takes a key and returns the path where the file can be written to.
|
||||
// Packer guarantees that no other process will write to this file while
|
||||
// the lock is held.
|
||||
//
|
||||
// If the key has an extension (e.g., file.ext), the resulting path
|
||||
// will have that extension as well.
|
||||
//
|
||||
// The cache will block and wait for the lock.
|
||||
Lock(string) string
|
||||
var DefaultCacheDir = "packer_cache"
|
||||
|
||||
// Unlock will unlock a certain cache key. Be very careful that this
|
||||
// is only called once per lock obtained.
|
||||
Unlock(string)
|
||||
|
||||
// RLock returns the path to a key in the cache and locks it for reading.
|
||||
// The second return parameter is whether the key existed or not.
|
||||
// This will block if any locks are held for writing. No lock will be
|
||||
// held if the key doesn't exist.
|
||||
RLock(string) (string, bool)
|
||||
|
||||
// RUnlock will unlock a key for reading.
|
||||
RUnlock(string)
|
||||
}
|
||||
|
||||
// FileCache implements a Cache by caching the data directly to a cache
|
||||
// directory.
|
||||
type FileCache struct {
|
||||
CacheDir string
|
||||
l sync.Mutex
|
||||
rw map[string]*sync.RWMutex
|
||||
}
|
||||
|
||||
func (f *FileCache) Lock(key string) string {
|
||||
hashKey := f.hashKey(key)
|
||||
rw := f.rwLock(hashKey)
|
||||
rw.Lock()
|
||||
|
||||
return f.cachePath(key, hashKey)
|
||||
}
|
||||
|
||||
func (f *FileCache) Unlock(key string) {
|
||||
hashKey := f.hashKey(key)
|
||||
rw := f.rwLock(hashKey)
|
||||
rw.Unlock()
|
||||
}
|
||||
|
||||
func (f *FileCache) RLock(key string) (string, bool) {
|
||||
hashKey := f.hashKey(key)
|
||||
rw := f.rwLock(hashKey)
|
||||
rw.RLock()
|
||||
|
||||
return f.cachePath(key, hashKey), true
|
||||
}
|
||||
|
||||
func (f *FileCache) RUnlock(key string) {
|
||||
hashKey := f.hashKey(key)
|
||||
rw := f.rwLock(hashKey)
|
||||
rw.RUnlock()
|
||||
}
|
||||
|
||||
func (f *FileCache) cachePath(key string, hashKey string) string {
|
||||
if endIndex := strings.Index(key, "?"); endIndex > -1 {
|
||||
key = key[:endIndex]
|
||||
// CachePath returns an absolute path to a cache file or directory
|
||||
//
|
||||
// When the directory is not absolute, CachePath will try to get
|
||||
// current working directory to be able to return a full path.
|
||||
//
|
||||
// CachePath can error in case it cannot find the cwd.
|
||||
//
|
||||
// ex:
|
||||
// PACKER_CACHE_DIR="" CacheDir() => "./packer_cache/
|
||||
// PACKER_CACHE_DIR="" CacheDir("foo") => "./packer_cache/foo
|
||||
// PACKER_CACHE_DIR="bar" CacheDir("foo") => "./bar/foo
|
||||
// PACKER_CACHE_DIR="/home/there" CacheDir("foo", "bar") => "/home/there/foo/bar
|
||||
func CachePath(paths ...string) (string, error) {
|
||||
cacheDir := DefaultCacheDir
|
||||
if cd := os.Getenv("PACKER_CACHE_DIR"); cd != "" {
|
||||
cacheDir = cd
|
||||
}
|
||||
|
||||
suffix := ""
|
||||
dotIndex := strings.LastIndex(key, ".")
|
||||
if dotIndex > -1 {
|
||||
if slashIndex := strings.LastIndex(key, "/"); slashIndex <= dotIndex {
|
||||
suffix = key[dotIndex:]
|
||||
}
|
||||
}
|
||||
|
||||
// Make the cache directory. We ignore errors here, but
|
||||
// log them in case something happens.
|
||||
if err := os.MkdirAll(f.CacheDir, 0755); err != nil {
|
||||
log.Printf(
|
||||
"[ERR] Error making cacheDir: %s %s", f.CacheDir, err)
|
||||
}
|
||||
|
||||
return filepath.Join(f.CacheDir, hashKey+suffix)
|
||||
}
|
||||
|
||||
func (f *FileCache) hashKey(key string) string {
|
||||
sha := sha256.New()
|
||||
sha.Write([]byte(key))
|
||||
return hex.EncodeToString(sha.Sum(nil))
|
||||
}
|
||||
|
||||
func (f *FileCache) rwLock(hashKey string) *sync.RWMutex {
|
||||
f.l.Lock()
|
||||
defer f.l.Unlock()
|
||||
|
||||
if f.rw == nil {
|
||||
f.rw = make(map[string]*sync.RWMutex)
|
||||
}
|
||||
|
||||
if result, ok := f.rw[hashKey]; ok {
|
||||
return result
|
||||
}
|
||||
|
||||
var result sync.RWMutex
|
||||
f.rw[hashKey] = &result
|
||||
return &result
|
||||
paths = append([]string{cacheDir}, paths...)
|
||||
return filepath.Abs(filepath.Join(paths...))
|
||||
}
|
||||
|
|
|
@ -1,84 +1,52 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer/tmp"
|
||||
)
|
||||
|
||||
type TestCache struct{}
|
||||
|
||||
func (TestCache) Lock(string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (TestCache) Unlock(string) {}
|
||||
|
||||
func (TestCache) RLock(string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (TestCache) RUnlock(string) {}
|
||||
|
||||
func TestFileCache_Implements(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &FileCache{}
|
||||
if _, ok := raw.(Cache); !ok {
|
||||
t.Fatal("FileCache must be a Cache")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileCache(t *testing.T) {
|
||||
cacheDir, err := tmp.Dir("packer")
|
||||
func TestCachePath(t *testing.T) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temporary dir: %s", err)
|
||||
t.Fatalf("Getwd: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(cacheDir)
|
||||
tmp := os.TempDir()
|
||||
|
||||
cache := &FileCache{CacheDir: cacheDir}
|
||||
// reset env
|
||||
cd := os.Getenv("PACKER_CACHE_DIR")
|
||||
os.Setenv("PACKER_CACHE_DIR", "")
|
||||
defer func() {
|
||||
os.Setenv("PACKER_CACHE_DIR", cd)
|
||||
}()
|
||||
|
||||
// Test path with no extension (GH-716)
|
||||
path := cache.Lock("/foo.bar/baz")
|
||||
defer cache.Unlock("/foo.bar/baz")
|
||||
if strings.Contains(path, ".bar") {
|
||||
t.Fatalf("bad: %s", path)
|
||||
type args struct {
|
||||
paths []string
|
||||
}
|
||||
|
||||
// Test paths with a ?
|
||||
path = cache.Lock("foo.ext?foo=bar.foo")
|
||||
defer cache.Unlock("foo.ext?foo=bar.foo")
|
||||
if !strings.HasSuffix(path, ".ext") {
|
||||
t.Fatalf("bad extension with question mark: %s", path)
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
env map[string]string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"base", args{}, nil, filepath.Join(wd, "packer_cache"), false},
|
||||
{"base and path", args{[]string{"a", "b"}}, nil, filepath.Join(wd, "packer_cache", "a", "b"), false},
|
||||
{"env and path", args{[]string{"a", "b"}}, map[string]string{"PACKER_CACHE_DIR": tmp}, filepath.Join(tmp, "a", "b"), false},
|
||||
}
|
||||
|
||||
// Test normal paths
|
||||
path = cache.Lock("foo.iso")
|
||||
if !strings.HasSuffix(path, ".iso") {
|
||||
t.Fatalf("path doesn't end with suffix '%s': '%s'", ".iso", path)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(path, []byte("data"), 0666)
|
||||
if err != nil {
|
||||
t.Fatalf("error writing: %s", err)
|
||||
}
|
||||
|
||||
cache.Unlock("foo.iso")
|
||||
|
||||
path, ok := cache.RLock("foo.iso")
|
||||
if !ok {
|
||||
t.Fatal("cache says key doesn't exist")
|
||||
}
|
||||
defer cache.RUnlock("foo.iso")
|
||||
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("error reading file: %s", err)
|
||||
}
|
||||
|
||||
if string(data) != "data" {
|
||||
t.Fatalf("unknown data: %s", data)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for k, v := range tt.env {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
got, err := CachePath(tt.args.paths...)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CachePath() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("CachePath() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ func TestCoreBuild_basic(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
artifact, err := build.Run(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ func TestCoreBuild_basicInterpolated(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
artifact, err := build.Run(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ func TestCoreBuild_prov(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
artifact, err := build.Run(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -264,7 +264,7 @@ func TestCoreBuild_provSkip(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
artifact, err := build.Run(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -298,7 +298,7 @@ func TestCoreBuild_provSkipInclude(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
artifact, err := build.Run(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -332,7 +332,7 @@ func TestCoreBuild_provOverride(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(nil, nil)
|
||||
artifact, err := build.Run(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -381,7 +381,7 @@ func TestCoreBuild_postProcess(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
artifact, err := build.Run(ui, nil)
|
||||
artifact, err := build.Run(ui)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
|
@ -20,13 +20,13 @@ func (b *cmdBuilder) Prepare(config ...interface{}) ([]string, error) {
|
|||
return b.builder.Prepare(config...)
|
||||
}
|
||||
|
||||
func (b *cmdBuilder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *cmdBuilder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
b.checkExit(r, nil)
|
||||
}()
|
||||
|
||||
return b.builder.Run(ui, hook, cache)
|
||||
return b.builder.Run(ui, hook)
|
||||
}
|
||||
|
||||
func (b *cmdBuilder) Cancel() {
|
||||
|
|
|
@ -1,170 +1,89 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/cheggaaa/pb"
|
||||
pb "github.com/cheggaaa/pb"
|
||||
)
|
||||
|
||||
// ProgressBar allows to graphically display
|
||||
// a self refreshing progress bar.
|
||||
type ProgressBar interface {
|
||||
Start(total int64)
|
||||
Add(current int64)
|
||||
NewProxyReader(r io.Reader) (proxy io.Reader)
|
||||
Finish()
|
||||
}
|
||||
|
||||
// StackableProgressBar is a progress bar that
|
||||
// allows to track multiple downloads at once.
|
||||
// Every call to Start increments a counter that
|
||||
// will display the number of current loadings.
|
||||
// Every call to Start will add total to an internal
|
||||
// total that is the total displayed.
|
||||
// First call to Start will start a goroutine
|
||||
// that is waiting for every download to be finished.
|
||||
// Last call to Finish triggers a cleanup.
|
||||
// When all active downloads are finished
|
||||
// StackableProgressBar will clean itself to a default
|
||||
// state.
|
||||
type StackableProgressBar struct {
|
||||
mtx sync.Mutex // locks in Start, Finish, Add & NewProxyReader
|
||||
Bar BasicProgressBar
|
||||
items int32
|
||||
total int64
|
||||
|
||||
started bool
|
||||
ConfigProgressbarFN func(*pb.ProgressBar)
|
||||
}
|
||||
|
||||
var _ ProgressBar = new(StackableProgressBar)
|
||||
|
||||
func defaultProgressbarConfigFn(bar *pb.ProgressBar) {
|
||||
func ProgressBarConfig(bar *pb.ProgressBar, prefix string) {
|
||||
bar.SetUnits(pb.U_BYTES)
|
||||
bar.Prefix(prefix)
|
||||
}
|
||||
|
||||
func (spb *StackableProgressBar) start() {
|
||||
bar := pb.New(0)
|
||||
if spb.ConfigProgressbarFN == nil {
|
||||
spb.ConfigProgressbarFN = defaultProgressbarConfigFn
|
||||
var defaultUiProgressBar = &uiProgressBar{}
|
||||
|
||||
// uiProgressBar is a self managed progress bar singleton.
|
||||
// decorate your struct with a *uiProgressBar to
|
||||
// give it TrackProgress capabilities.
|
||||
// In TrackProgress if uiProgressBar is nil
|
||||
// defaultUiProgressBar will be used as
|
||||
// the progress bar.
|
||||
type uiProgressBar struct {
|
||||
lock sync.Mutex
|
||||
|
||||
pool *pb.Pool
|
||||
|
||||
pbs int
|
||||
}
|
||||
|
||||
func (p *uiProgressBar) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
|
||||
if p == nil {
|
||||
return defaultUiProgressBar.TrackProgress(src, currentSize, totalSize, stream)
|
||||
}
|
||||
spb.ConfigProgressbarFN(bar)
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
bar.Start()
|
||||
spb.Bar.ProgressBar = bar
|
||||
spb.started = true
|
||||
}
|
||||
newPb := pb.New64(totalSize)
|
||||
newPb.Set64(currentSize)
|
||||
ProgressBarConfig(newPb, filepath.Base(src))
|
||||
|
||||
func (spb *StackableProgressBar) Start(total int64) {
|
||||
spb.mtx.Lock()
|
||||
|
||||
spb.total += total
|
||||
spb.items++
|
||||
|
||||
if !spb.started {
|
||||
spb.start()
|
||||
if p.pool == nil {
|
||||
pool := pb.NewPool()
|
||||
err := pool.Start()
|
||||
if err != nil {
|
||||
// here, we probably cannot lock
|
||||
// stdout, so let's just return
|
||||
// stream to avoid any error.
|
||||
return stream
|
||||
}
|
||||
p.pool = pool
|
||||
}
|
||||
spb.Bar.SetTotal64(spb.total)
|
||||
spb.prefix()
|
||||
spb.mtx.Unlock()
|
||||
}
|
||||
p.pool.Add(newPb)
|
||||
reader := newPb.NewProxyReader(stream)
|
||||
|
||||
func (spb *StackableProgressBar) Add(total int64) {
|
||||
spb.mtx.Lock()
|
||||
defer spb.mtx.Unlock()
|
||||
if spb.Bar.ProgressBar != nil {
|
||||
spb.Bar.Add(total)
|
||||
p.pbs++
|
||||
return &readCloser{
|
||||
Reader: reader,
|
||||
close: func() error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
newPb.Finish()
|
||||
p.pbs--
|
||||
if p.pbs <= 0 {
|
||||
p.pool.Stop()
|
||||
p.pool = nil
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (spb *StackableProgressBar) NewProxyReader(r io.Reader) io.Reader {
|
||||
spb.mtx.Lock()
|
||||
defer spb.mtx.Unlock()
|
||||
return spb.Bar.NewProxyReader(r)
|
||||
}
|
||||
|
||||
func (spb *StackableProgressBar) prefix() {
|
||||
spb.Bar.ProgressBar.Prefix(fmt.Sprintf("%d items: ", spb.items))
|
||||
}
|
||||
|
||||
func (spb *StackableProgressBar) Finish() {
|
||||
spb.mtx.Lock()
|
||||
defer spb.mtx.Unlock()
|
||||
|
||||
if spb.items > 0 {
|
||||
spb.items--
|
||||
}
|
||||
if spb.items == 0 && spb.Bar.ProgressBar != nil {
|
||||
// slef cleanup
|
||||
spb.Bar.ProgressBar.Finish()
|
||||
spb.Bar.ProgressBar = nil
|
||||
spb.started = false
|
||||
spb.total = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// BasicProgressBar is packer's basic progress bar.
|
||||
// Current implementation will always try to keep
|
||||
// itself at the bottom of a terminal.
|
||||
type BasicProgressBar struct {
|
||||
*pb.ProgressBar
|
||||
}
|
||||
|
||||
var _ ProgressBar = new(BasicProgressBar)
|
||||
|
||||
func (bpb *BasicProgressBar) Start(total int64) {
|
||||
bpb.SetTotal64(total)
|
||||
bpb.ProgressBar.Start()
|
||||
}
|
||||
|
||||
func (bpb *BasicProgressBar) Add(current int64) {
|
||||
bpb.ProgressBar.Add64(current)
|
||||
}
|
||||
func (bpb *BasicProgressBar) NewProxyReader(r io.Reader) io.Reader {
|
||||
return &ProxyReader{
|
||||
Reader: r,
|
||||
ProgressBar: bpb,
|
||||
}
|
||||
}
|
||||
func (bpb *BasicProgressBar) NewProxyReadCloser(r io.ReadCloser) io.ReadCloser {
|
||||
return &ProxyReader{
|
||||
Reader: r,
|
||||
ProgressBar: bpb,
|
||||
}
|
||||
}
|
||||
|
||||
// NoopProgressBar is a silent progress bar.
|
||||
type NoopProgressBar struct {
|
||||
}
|
||||
|
||||
var _ ProgressBar = new(NoopProgressBar)
|
||||
|
||||
func (npb *NoopProgressBar) Start(int64) {}
|
||||
func (npb *NoopProgressBar) Add(int64) {}
|
||||
func (npb *NoopProgressBar) Finish() {}
|
||||
func (npb *NoopProgressBar) NewProxyReader(r io.Reader) io.Reader { return r }
|
||||
func (npb *NoopProgressBar) NewProxyReadCloser(r io.ReadCloser) io.ReadCloser { return r }
|
||||
|
||||
// ProxyReader implements io.ReadCloser but sends
|
||||
// count of read bytes to a progress bar
|
||||
type ProxyReader struct {
|
||||
type readCloser struct {
|
||||
io.Reader
|
||||
ProgressBar
|
||||
close func() error
|
||||
}
|
||||
|
||||
func (r *ProxyReader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.Reader.Read(p)
|
||||
r.ProgressBar.Add(int64(n))
|
||||
return
|
||||
}
|
||||
func (c *readCloser) Close() error { return c.close() }
|
||||
|
||||
// Close the reader if it implements io.Closer
|
||||
func (r *ProxyReader) Close() (err error) {
|
||||
if closer, ok := r.Reader.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return
|
||||
// NoopProgressTracker is a progress tracker
|
||||
// that displays nothing.
|
||||
type NoopProgressTracker struct{}
|
||||
|
||||
// TrackProgress returns stream
|
||||
func (*NoopProgressTracker) TrackProgress(_ string, _, _ int64, stream io.ReadCloser) io.ReadCloser {
|
||||
return stream
|
||||
}
|
||||
|
|
|
@ -1,71 +1,58 @@
|
|||
package packer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cheggaaa/pb"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func speedyProgressBar(bar *pb.ProgressBar) {
|
||||
bar.SetUnits(pb.U_BYTES)
|
||||
bar.SetRefreshRate(1 * time.Millisecond)
|
||||
bar.NotPrint = true
|
||||
bar.Format("[\x00=\x00>\x00-\x00]")
|
||||
// The following tests rarelly just happen. So we run them 100 times.
|
||||
|
||||
func TestProgressTracking_open_close(t *testing.T) {
|
||||
var bar *uiProgressBar
|
||||
|
||||
tracker := bar.TrackProgress("1,", 1, 42, ioutil.NopCloser(nil))
|
||||
tracker.Close()
|
||||
|
||||
tracker = bar.TrackProgress("2,", 1, 42, ioutil.NopCloser(nil))
|
||||
tracker.Close()
|
||||
}
|
||||
|
||||
func TestStackableProgressBar_race(t *testing.T) {
|
||||
bar := &StackableProgressBar{
|
||||
ConfigProgressbarFN: speedyProgressBar,
|
||||
}
|
||||
func TestProgressTracking_multi_open_close(t *testing.T) {
|
||||
var bar *uiProgressBar
|
||||
g := errgroup.Group{}
|
||||
|
||||
start42Fn := func() { bar.Start(42) }
|
||||
finishFn := func() { bar.Finish() }
|
||||
add21 := func() { bar.Add(21) }
|
||||
// prefix := func() { bar.prefix() }
|
||||
|
||||
type fields struct {
|
||||
pre func()
|
||||
calls []func()
|
||||
post func()
|
||||
for i := 0; i < 100; i++ {
|
||||
g.Go(func() error {
|
||||
tracker := bar.TrackProgress("file,", 1, 42, ioutil.NopCloser(nil))
|
||||
return tracker.Close()
|
||||
})
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
iterations int
|
||||
}{
|
||||
{"all public", fields{nil, []func(){start42Fn, finishFn, add21, add21}, finishFn}, 300},
|
||||
{"add", fields{start42Fn, []func(){add21}, finishFn}, 300},
|
||||
{"add start", fields{start42Fn, []func(){start42Fn, add21}, finishFn}, 300},
|
||||
if err := g.Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for i := 0; i < tt.iterations; i++ {
|
||||
if tt.fields.pre != nil {
|
||||
tt.fields.pre()
|
||||
}
|
||||
var startWg, endWg sync.WaitGroup
|
||||
startWg.Add(1)
|
||||
endWg.Add(len(tt.fields.calls))
|
||||
for _, call := range tt.fields.calls {
|
||||
call := call
|
||||
go func() {
|
||||
defer endWg.Done()
|
||||
startWg.Wait()
|
||||
call()
|
||||
}()
|
||||
}
|
||||
startWg.Done() // everyone is initialized, let's unlock everyone at the same time.
|
||||
// ....
|
||||
endWg.Wait() // wait for all calls to return.
|
||||
if tt.fields.post != nil {
|
||||
tt.fields.post()
|
||||
}
|
||||
func TestProgressTracking_races(t *testing.T) {
|
||||
var bar *uiProgressBar
|
||||
g := errgroup.Group{}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
g.Go(func() error {
|
||||
txt := []byte("foobarbaz dolores")
|
||||
b := bytes.NewReader(txt)
|
||||
tracker := bar.TrackProgress("file,", 1, 42, ioutil.NopCloser(b))
|
||||
|
||||
for i := 0; i < 42; i++ {
|
||||
tracker.Read([]byte("i"))
|
||||
}
|
||||
return tracker.Close()
|
||||
})
|
||||
}
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,10 +43,9 @@ func (b *build) Prepare() ([]string, error) {
|
|||
return resp.Warnings, err
|
||||
}
|
||||
|
||||
func (b *build) Run(ui packer.Ui, cache packer.Cache) ([]packer.Artifact, error) {
|
||||
func (b *build) Run(ui packer.Ui) ([]packer.Artifact, error) {
|
||||
nextId := b.mux.NextId()
|
||||
server := newServerWithMux(b.mux, nextId)
|
||||
server.RegisterCache(cache)
|
||||
server.RegisterUi(ui)
|
||||
go server.Serve()
|
||||
|
||||
|
@ -113,7 +112,7 @@ func (b *BuildServer) Run(streamId uint32, reply *[]uint32) error {
|
|||
}
|
||||
defer client.Close()
|
||||
|
||||
artifacts, err := b.build.Run(client.Ui(), client.Cache())
|
||||
artifacts, err := b.build.Run(client.Ui())
|
||||
if err != nil {
|
||||
return NewBasicError(err)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ type testBuild struct {
|
|||
prepareCalled bool
|
||||
prepareWarnings []string
|
||||
runCalled bool
|
||||
runCache packer.Cache
|
||||
runUi packer.Ui
|
||||
setDebugCalled bool
|
||||
setForceCalled bool
|
||||
|
@ -35,9 +34,8 @@ func (b *testBuild) Prepare() ([]string, error) {
|
|||
return b.prepareWarnings, nil
|
||||
}
|
||||
|
||||
func (b *testBuild) Run(ui packer.Ui, cache packer.Cache) ([]packer.Artifact, error) {
|
||||
func (b *testBuild) Run(ui packer.Ui) ([]packer.Artifact, error) {
|
||||
b.runCalled = true
|
||||
b.runCache = cache
|
||||
b.runUi = ui
|
||||
|
||||
if b.errRunResult {
|
||||
|
@ -84,9 +82,8 @@ func TestBuild(t *testing.T) {
|
|||
}
|
||||
|
||||
// Test Run
|
||||
cache := new(testCache)
|
||||
ui := new(testUi)
|
||||
artifacts, err := bClient.Run(ui, cache)
|
||||
artifacts, err := bClient.Run(ui)
|
||||
if !b.runCalled {
|
||||
t.Fatal("run should be called")
|
||||
}
|
||||
|
@ -105,7 +102,7 @@ func TestBuild(t *testing.T) {
|
|||
|
||||
// Test run with an error
|
||||
b.errRunResult = true
|
||||
_, err = bClient.Run(ui, cache)
|
||||
_, err = bClient.Run(ui)
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
|
|
@ -44,10 +44,9 @@ func (b *builder) Prepare(config ...interface{}) ([]string, error) {
|
|||
return resp.Warnings, err
|
||||
}
|
||||
|
||||
func (b *builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
func (b *builder) Run(ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
|
||||
nextId := b.mux.NextId()
|
||||
server := newServerWithMux(b.mux, nextId)
|
||||
server.RegisterCache(cache)
|
||||
server.RegisterHook(hook)
|
||||
server.RegisterUi(ui)
|
||||
go server.Serve()
|
||||
|
@ -91,7 +90,7 @@ func (b *BuilderServer) Run(streamId uint32, reply *uint32) error {
|
|||
}
|
||||
defer client.Close()
|
||||
|
||||
artifact, err := b.builder.Run(client.Ui(), client.Hook(), client.Cache())
|
||||
artifact, err := b.builder.Run(client.Ui(), client.Hook())
|
||||
if err != nil {
|
||||
return NewBasicError(err)
|
||||
}
|
||||
|
|
|
@ -67,10 +67,9 @@ func TestBuilderRun(t *testing.T) {
|
|||
bClient := client.Builder()
|
||||
|
||||
// Test Run
|
||||
cache := new(testCache)
|
||||
hook := &packer.MockHook{}
|
||||
ui := &testUi{}
|
||||
artifact, err := bClient.Run(ui, hook, cache)
|
||||
artifact, err := bClient.Run(ui, hook)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -94,10 +93,9 @@ func TestBuilderRun_nilResult(t *testing.T) {
|
|||
server.RegisterBuilder(b)
|
||||
bClient := client.Builder()
|
||||
|
||||
cache := new(testCache)
|
||||
hook := &packer.MockHook{}
|
||||
ui := &testUi{}
|
||||
artifact, err := bClient.Run(ui, hook, cache)
|
||||
artifact, err := bClient.Run(ui, hook)
|
||||
if artifact != nil {
|
||||
t.Fatalf("bad: %#v", artifact)
|
||||
}
|
||||
|
@ -116,10 +114,9 @@ func TestBuilderRun_ErrResult(t *testing.T) {
|
|||
|
||||
b.RunErrResult = true
|
||||
|
||||
cache := new(testCache)
|
||||
hook := &packer.MockHook{}
|
||||
ui := &testUi{}
|
||||
artifact, err := bClient.Run(ui, hook, cache)
|
||||
artifact, err := bClient.Run(ui, hook)
|
||||
if artifact != nil {
|
||||
t.Fatalf("bad: %#v", artifact)
|
||||
}
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/rpc"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// An implementation of packer.Cache where the cache is actually executed
|
||||
// over an RPC connection.
|
||||
type cache struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
// CacheServer wraps a packer.Cache implementation and makes it exportable
|
||||
// as part of a Golang RPC server.
|
||||
type CacheServer struct {
|
||||
cache packer.Cache
|
||||
}
|
||||
|
||||
type CacheRLockResponse struct {
|
||||
Path string
|
||||
Exists bool
|
||||
}
|
||||
|
||||
func (c *cache) Lock(key string) (result string) {
|
||||
if err := c.client.Call("Cache.Lock", key, &result); err != nil {
|
||||
log.Printf("[ERR] Cache.Lock error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cache) RLock(key string) (string, bool) {
|
||||
var result CacheRLockResponse
|
||||
if err := c.client.Call("Cache.RLock", key, &result); err != nil {
|
||||
log.Printf("[ERR] Cache.RLock error: %s", err)
|
||||
return "", false
|
||||
}
|
||||
|
||||
return result.Path, result.Exists
|
||||
}
|
||||
|
||||
func (c *cache) Unlock(key string) {
|
||||
if err := c.client.Call("Cache.Unlock", key, new(interface{})); err != nil {
|
||||
log.Printf("[ERR] Cache.Unlock error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cache) RUnlock(key string) {
|
||||
if err := c.client.Call("Cache.RUnlock", key, new(interface{})); err != nil {
|
||||
log.Printf("[ERR] Cache.RUnlock error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CacheServer) Lock(key string, result *string) error {
|
||||
*result = c.cache.Lock(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CacheServer) Unlock(key string, result *interface{}) error {
|
||||
c.cache.Unlock(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CacheServer) RLock(key string, result *CacheRLockResponse) error {
|
||||
path, exists := c.cache.RLock(key)
|
||||
*result = CacheRLockResponse{path, exists}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CacheServer) RUnlock(key string, result *interface{}) error {
|
||||
c.cache.RUnlock(key)
|
||||
return nil
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type testCache struct {
|
||||
lockCalled bool
|
||||
lockKey string
|
||||
unlockCalled bool
|
||||
unlockKey string
|
||||
rlockCalled bool
|
||||
rlockKey string
|
||||
runlockCalled bool
|
||||
runlockKey string
|
||||
}
|
||||
|
||||
func (t *testCache) Lock(key string) string {
|
||||
t.lockCalled = true
|
||||
t.lockKey = key
|
||||
return "foo"
|
||||
}
|
||||
|
||||
func (t *testCache) RLock(key string) (string, bool) {
|
||||
t.rlockCalled = true
|
||||
t.rlockKey = key
|
||||
return "foo", true
|
||||
}
|
||||
|
||||
func (t *testCache) Unlock(key string) {
|
||||
t.unlockCalled = true
|
||||
t.unlockKey = key
|
||||
}
|
||||
|
||||
func (t *testCache) RUnlock(key string) {
|
||||
t.runlockCalled = true
|
||||
t.runlockKey = key
|
||||
}
|
||||
|
||||
func TestCache_Implements(t *testing.T) {
|
||||
var _ packer.Cache = new(cache)
|
||||
}
|
||||
|
||||
func TestCacheRPC(t *testing.T) {
|
||||
// Create the interface to test
|
||||
c := new(testCache)
|
||||
|
||||
// Start the server
|
||||
client, server := testClientServer(t)
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
server.RegisterCache(c)
|
||||
|
||||
cacheClient := client.Cache()
|
||||
|
||||
// Test Lock
|
||||
cacheClient.Lock("foo")
|
||||
if !c.lockCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if c.lockKey != "foo" {
|
||||
t.Fatalf("bad: %s", c.lockKey)
|
||||
}
|
||||
|
||||
// Test Unlock
|
||||
cacheClient.Unlock("foo")
|
||||
if !c.unlockCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if c.unlockKey != "foo" {
|
||||
t.Fatalf("bad: %s", c.unlockKey)
|
||||
}
|
||||
|
||||
// Test RLock
|
||||
cacheClient.RLock("foo")
|
||||
if !c.rlockCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if c.rlockKey != "foo" {
|
||||
t.Fatalf("bad: %s", c.rlockKey)
|
||||
}
|
||||
|
||||
// Test RUnlock
|
||||
cacheClient.RUnlock("foo")
|
||||
if !c.runlockCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if c.runlockKey != "foo" {
|
||||
t.Fatalf("bad: %s", c.runlockKey)
|
||||
}
|
||||
}
|
|
@ -88,12 +88,6 @@ func (c *Client) Builder() packer.Builder {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Client) Cache() packer.Cache {
|
||||
return &cache{
|
||||
client: c.client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Communicator() packer.Communicator {
|
||||
return &communicator{
|
||||
client: c.client,
|
||||
|
|
|
@ -78,12 +78,6 @@ func (s *Server) RegisterBuilder(b packer.Builder) {
|
|||
})
|
||||
}
|
||||
|
||||
func (s *Server) RegisterCache(c packer.Cache) {
|
||||
s.server.RegisterName(DefaultCacheEndpoint, &CacheServer{
|
||||
cache: c,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) RegisterCommunicator(c packer.Communicator) {
|
||||
s.server.RegisterName(DefaultCommunicatorEndpoint, &CommunicatorServer{
|
||||
c: c,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/rpc"
|
||||
|
||||
|
@ -64,31 +63,6 @@ func (u *Ui) Say(message string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (u *Ui) ProgressBar() packer.ProgressBar {
|
||||
if err := u.client.Call("Ui.ProgressBar", new(interface{}), new(interface{})); err != nil {
|
||||
log.Printf("Error in Ui.ProgressBar RPC call: %s", err)
|
||||
}
|
||||
return u // Ui is also a progress bar !!
|
||||
}
|
||||
|
||||
var _ packer.ProgressBar = new(Ui)
|
||||
|
||||
func (pb *Ui) Start(total int64) {
|
||||
pb.client.Call("Ui.Start", total, new(interface{}))
|
||||
}
|
||||
|
||||
func (pb *Ui) Add(current int64) {
|
||||
pb.client.Call("Ui.Add", current, new(interface{}))
|
||||
}
|
||||
|
||||
func (pb *Ui) Finish() {
|
||||
pb.client.Call("Ui.Finish", nil, new(interface{}))
|
||||
}
|
||||
|
||||
func (pb *Ui) NewProxyReader(r io.Reader) io.Reader {
|
||||
return &packer.ProxyReader{Reader: r, ProgressBar: pb}
|
||||
}
|
||||
|
||||
func (u *UiServer) Ask(query string, reply *string) (err error) {
|
||||
*reply, err = u.ui.Ask(query)
|
||||
return
|
||||
|
@ -120,26 +94,3 @@ func (u *UiServer) Say(message *string, reply *interface{}) error {
|
|||
*reply = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UiServer) ProgressBar(_ *string, reply *interface{}) error {
|
||||
// No-op for now, this function might be
|
||||
// used in the future if we want to use
|
||||
// different progress bars with identifiers.
|
||||
u.ui.ProgressBar()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pb *UiServer) Finish(_ string, _ *interface{}) error {
|
||||
pb.ui.ProgressBar().Finish()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pb *UiServer) Start(total int64, _ *interface{}) error {
|
||||
pb.ui.ProgressBar().Start(total)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pb *UiServer) Add(current int64, _ *interface{}) error {
|
||||
pb.ui.ProgressBar().Add(current)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/rpc"
|
||||
|
||||
"github.com/hashicorp/packer/common/random"
|
||||
)
|
||||
|
||||
// TrackProgress starts a pair of ProgressTrackingClient and ProgressProgressTrackingServer
|
||||
// that will send the size of each read bytes of stream.
|
||||
// In order to track an operation on the terminal side.
|
||||
func (u *Ui) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
|
||||
pl := &TrackProgressParameters{
|
||||
Src: src,
|
||||
CurrentSize: currentSize,
|
||||
TotalSize: totalSize,
|
||||
}
|
||||
var trackingID string
|
||||
if err := u.client.Call("Ui.NewTrackProgress", pl, &trackingID); err != nil {
|
||||
log.Printf("Error in Ui.NewTrackProgress RPC call: %s", err)
|
||||
return stream
|
||||
}
|
||||
cli := &ProgressTrackingClient{
|
||||
id: trackingID,
|
||||
client: u.client,
|
||||
stream: stream,
|
||||
}
|
||||
return cli
|
||||
}
|
||||
|
||||
type ProgressTrackingClient struct {
|
||||
id string
|
||||
client *rpc.Client
|
||||
stream io.ReadCloser
|
||||
}
|
||||
|
||||
// Read will send len(b) over the wire instead of it's content
|
||||
func (u *ProgressTrackingClient) Read(b []byte) (read int, err error) {
|
||||
defer func() {
|
||||
if err := u.client.Call("Ui"+u.id+".Add", read, new(interface{})); err != nil {
|
||||
log.Printf("Error in ProgressTrackingClient.Read RPC call: %s", err)
|
||||
}
|
||||
}()
|
||||
return u.stream.Read(b)
|
||||
}
|
||||
|
||||
func (u *ProgressTrackingClient) Close() error {
|
||||
log.Printf("closing")
|
||||
if err := u.client.Call("Ui"+u.id+".Close", nil, new(interface{})); err != nil {
|
||||
log.Printf("Error in ProgressTrackingClient.Close RPC call: %s", err)
|
||||
}
|
||||
return u.stream.Close()
|
||||
}
|
||||
|
||||
type TrackProgressParameters struct {
|
||||
Src string
|
||||
TotalSize int64
|
||||
CurrentSize int64
|
||||
}
|
||||
|
||||
func (ui *UiServer) NewTrackProgress(pl *TrackProgressParameters, reply *string) error {
|
||||
// keep identifier as is for now
|
||||
srvr := &ProgressTrackingServer{
|
||||
id: *reply,
|
||||
}
|
||||
|
||||
*reply = pl.Src + random.AlphaNum(6)
|
||||
srvr.stream = ui.ui.TrackProgress(pl.Src, pl.CurrentSize, pl.TotalSize, nopReadCloser{})
|
||||
err := ui.register("Ui"+*reply, srvr)
|
||||
if err != nil {
|
||||
log.Printf("failed to register ProgressTrackingServer at %s: %s", *reply, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ProgressTrackingServer struct {
|
||||
id string
|
||||
stream io.ReadCloser
|
||||
}
|
||||
|
||||
func (t *ProgressTrackingServer) Add(size int, _ *interface{}) error {
|
||||
stubBytes := make([]byte, size, size)
|
||||
t.stream.Read(stubBytes)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ProgressTrackingServer) Close(_, _ *interface{}) error {
|
||||
t.stream.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
type nopReadCloser struct {
|
||||
}
|
||||
|
||||
func (nopReadCloser) Close() error { return nil }
|
||||
func (nopReadCloser) Read(b []byte) (int, error) { return len(b), nil }
|
|
@ -1,11 +1,11 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type testUi struct {
|
||||
|
@ -21,11 +21,9 @@ type testUi struct {
|
|||
sayCalled bool
|
||||
sayMessage string
|
||||
|
||||
progressBarCalled bool
|
||||
progressBarStartCalled bool
|
||||
progressBarAddCalled bool
|
||||
progressBarFinishCalled bool
|
||||
progressBarNewProxyReaderCalled bool
|
||||
trackProgressCalled bool
|
||||
progressBarAddCalled bool
|
||||
progressBarCloseCalled bool
|
||||
}
|
||||
|
||||
func (u *testUi) Ask(query string) (string, error) {
|
||||
|
@ -55,27 +53,28 @@ func (u *testUi) Say(message string) {
|
|||
u.sayMessage = message
|
||||
}
|
||||
|
||||
func (u *testUi) ProgressBar() packer.ProgressBar {
|
||||
u.progressBarCalled = true
|
||||
return u
|
||||
func (u *testUi) TrackProgress(_ string, _, _ int64, stream io.ReadCloser) (body io.ReadCloser) {
|
||||
u.trackProgressCalled = true
|
||||
|
||||
return &readCloser{
|
||||
read: func(p []byte) (int, error) {
|
||||
u.progressBarAddCalled = true
|
||||
return stream.Read(p)
|
||||
},
|
||||
close: func() error {
|
||||
u.progressBarCloseCalled = true
|
||||
return stream.Close()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (u *testUi) Start(int64) {
|
||||
u.progressBarStartCalled = true
|
||||
type readCloser struct {
|
||||
read func([]byte) (int, error)
|
||||
close func() error
|
||||
}
|
||||
|
||||
func (u *testUi) Add(int64) {
|
||||
u.progressBarAddCalled = true
|
||||
}
|
||||
|
||||
func (u *testUi) Finish() {
|
||||
u.progressBarFinishCalled = true
|
||||
}
|
||||
|
||||
func (u *testUi) NewProxyReader(r io.Reader) io.Reader {
|
||||
u.progressBarNewProxyReaderCalled = true
|
||||
return r
|
||||
}
|
||||
func (c *readCloser) Close() error { return c.close() }
|
||||
func (c *readCloser) Read(p []byte) (int, error) { return c.read(p) }
|
||||
|
||||
func TestUiRPC(t *testing.T) {
|
||||
// Create the UI to test
|
||||
|
@ -119,24 +118,23 @@ func TestUiRPC(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", ui.errorMessage)
|
||||
}
|
||||
|
||||
bar := uiClient.ProgressBar()
|
||||
if ui.progressBarCalled != true {
|
||||
t.Errorf("ProgressBar not called.")
|
||||
ctt := []byte("foo bar baz !!!")
|
||||
rc := ioutil.NopCloser(bytes.NewReader(ctt))
|
||||
|
||||
stream := uiClient.TrackProgress("stuff.txt", 0, int64(len(ctt)), rc)
|
||||
if ui.trackProgressCalled != true {
|
||||
t.Errorf("ProgressBastream not called.")
|
||||
}
|
||||
|
||||
bar.Start(100)
|
||||
if ui.progressBarStartCalled != true {
|
||||
t.Errorf("progressBar.Start not called.")
|
||||
}
|
||||
|
||||
bar.Add(1)
|
||||
b := []byte{0}
|
||||
stream.Read(b) // output ignored
|
||||
if ui.progressBarAddCalled != true {
|
||||
t.Errorf("progressBar.Add not called.")
|
||||
t.Errorf("Add not called.")
|
||||
}
|
||||
|
||||
bar.Finish()
|
||||
if ui.progressBarFinishCalled != true {
|
||||
t.Errorf("progressBar.Finish not called.")
|
||||
stream.Close()
|
||||
if ui.progressBarCloseCalled != true {
|
||||
t.Errorf("close not called.")
|
||||
}
|
||||
|
||||
uiClient.Machine("foo", "bar", "baz")
|
||||
|
|
106
packer/ui.go
106
packer/ui.go
|
@ -14,6 +14,8 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
getter "github.com/hashicorp/go-getter"
|
||||
)
|
||||
|
||||
type UiColor uint
|
||||
|
@ -36,10 +38,12 @@ type Ui interface {
|
|||
Message(string)
|
||||
Error(string)
|
||||
Machine(string, ...string)
|
||||
ProgressBar() ProgressBar
|
||||
getter.ProgressTracker
|
||||
}
|
||||
|
||||
type NoopUi struct{}
|
||||
type NoopUi struct {
|
||||
NoopProgressTracker
|
||||
}
|
||||
|
||||
var _ Ui = new(NoopUi)
|
||||
|
||||
|
@ -48,56 +52,17 @@ func (*NoopUi) Say(string) { return }
|
|||
func (*NoopUi) Message(string) { return }
|
||||
func (*NoopUi) Error(string) { return }
|
||||
func (*NoopUi) Machine(string, ...string) { return }
|
||||
func (*NoopUi) ProgressBar() ProgressBar { return new(NoopProgressBar) }
|
||||
|
||||
// ColoredUi is a UI that is colored using terminal colors.
|
||||
type ColoredUi struct {
|
||||
Color UiColor
|
||||
ErrorColor UiColor
|
||||
Ui Ui
|
||||
*uiProgressBar
|
||||
}
|
||||
|
||||
var _ Ui = new(ColoredUi)
|
||||
|
||||
// TargetedUI is a UI that wraps another UI implementation and modifies
|
||||
// the output to indicate a specific target. Specifically, all Say output
|
||||
// is prefixed with the target name. Message output is not prefixed but
|
||||
// is offset by the length of the target so that output is lined up properly
|
||||
// with Say output. Machine-readable output has the proper target set.
|
||||
type TargetedUI struct {
|
||||
Target string
|
||||
Ui Ui
|
||||
}
|
||||
|
||||
var _ Ui = new(TargetedUI)
|
||||
|
||||
// The BasicUI is a UI that reads and writes from a standard Go reader
|
||||
// and writer. It is safe to be called from multiple goroutines. Machine
|
||||
// readable output is simply logged for this UI.
|
||||
type BasicUi struct {
|
||||
Reader io.Reader
|
||||
Writer io.Writer
|
||||
ErrorWriter io.Writer
|
||||
l sync.Mutex
|
||||
interrupted bool
|
||||
TTY TTY
|
||||
StackableProgressBar
|
||||
}
|
||||
|
||||
var _ Ui = new(BasicUi)
|
||||
|
||||
func (bu *BasicUi) ProgressBar() ProgressBar {
|
||||
return &bu.StackableProgressBar
|
||||
}
|
||||
|
||||
// MachineReadableUi is a UI that only outputs machine-readable output
|
||||
// to the given Writer.
|
||||
type MachineReadableUi struct {
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
var _ Ui = new(MachineReadableUi)
|
||||
|
||||
func (u *ColoredUi) Ask(query string) (string, error) {
|
||||
return u.Ui.Ask(u.colorize(query, u.Color, true))
|
||||
}
|
||||
|
@ -124,10 +89,6 @@ func (u *ColoredUi) Machine(t string, args ...string) {
|
|||
u.Ui.Machine(t, args...)
|
||||
}
|
||||
|
||||
func (u *ColoredUi) ProgressBar() ProgressBar {
|
||||
return u.Ui.ProgressBar() //TODO(adrien): color me
|
||||
}
|
||||
|
||||
func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
|
||||
if !u.supportsColors() {
|
||||
return message
|
||||
|
@ -160,6 +121,19 @@ func (u *ColoredUi) supportsColors() bool {
|
|||
return cygwin
|
||||
}
|
||||
|
||||
// TargetedUI is a UI that wraps another UI implementation and modifies
|
||||
// the output to indicate a specific target. Specifically, all Say output
|
||||
// is prefixed with the target name. Message output is not prefixed but
|
||||
// is offset by the length of the target so that output is lined up properly
|
||||
// with Say output. Machine-readable output has the proper target set.
|
||||
type TargetedUI struct {
|
||||
Target string
|
||||
Ui Ui
|
||||
*uiProgressBar
|
||||
}
|
||||
|
||||
var _ Ui = new(TargetedUI)
|
||||
|
||||
func (u *TargetedUI) Ask(query string) (string, error) {
|
||||
return u.Ui.Ask(u.prefixLines(true, query))
|
||||
}
|
||||
|
@ -181,10 +155,6 @@ func (u *TargetedUI) Machine(t string, args ...string) {
|
|||
u.Ui.Machine(fmt.Sprintf("%s,%s", u.Target, t), args...)
|
||||
}
|
||||
|
||||
func (u *TargetedUI) ProgressBar() ProgressBar {
|
||||
return u.Ui.ProgressBar()
|
||||
}
|
||||
|
||||
func (u *TargetedUI) prefixLines(arrow bool, message string) string {
|
||||
arrowText := "==>"
|
||||
if !arrow {
|
||||
|
@ -200,6 +170,21 @@ func (u *TargetedUI) prefixLines(arrow bool, message string) string {
|
|||
return strings.TrimRightFunc(result.String(), unicode.IsSpace)
|
||||
}
|
||||
|
||||
// The BasicUI is a UI that reads and writes from a standard Go reader
|
||||
// and writer. It is safe to be called from multiple goroutines. Machine
|
||||
// readable output is simply logged for this UI.
|
||||
type BasicUi struct {
|
||||
Reader io.Reader
|
||||
Writer io.Writer
|
||||
ErrorWriter io.Writer
|
||||
l sync.Mutex
|
||||
interrupted bool
|
||||
TTY TTY
|
||||
*uiProgressBar
|
||||
}
|
||||
|
||||
var _ Ui = new(BasicUi)
|
||||
|
||||
func (rw *BasicUi) Ask(query string) (string, error) {
|
||||
rw.l.Lock()
|
||||
defer rw.l.Unlock()
|
||||
|
@ -289,6 +274,15 @@ func (rw *BasicUi) Machine(t string, args ...string) {
|
|||
log.Printf("machine readable: %s %#v", t, args)
|
||||
}
|
||||
|
||||
// MachineReadableUi is a UI that only outputs machine-readable output
|
||||
// to the given Writer.
|
||||
type MachineReadableUi struct {
|
||||
Writer io.Writer
|
||||
NoopProgressTracker
|
||||
}
|
||||
|
||||
var _ Ui = new(MachineReadableUi)
|
||||
|
||||
func (u *MachineReadableUi) Ask(query string) (string, error) {
|
||||
return "", errors.New("machine-readable UI can't ask")
|
||||
}
|
||||
|
@ -335,14 +329,11 @@ func (u *MachineReadableUi) Machine(category string, args ...string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (u *MachineReadableUi) ProgressBar() ProgressBar {
|
||||
return new(NoopProgressBar)
|
||||
}
|
||||
|
||||
// TimestampedUi is a UI that wraps another UI implementation and
|
||||
// prefixes each message with an RFC3339 timestamp
|
||||
type TimestampedUi struct {
|
||||
Ui Ui
|
||||
*uiProgressBar
|
||||
}
|
||||
|
||||
var _ Ui = new(TimestampedUi)
|
||||
|
@ -367,8 +358,6 @@ func (u *TimestampedUi) Machine(message string, args ...string) {
|
|||
u.Ui.Machine(message, args...)
|
||||
}
|
||||
|
||||
func (u *TimestampedUi) ProgressBar() ProgressBar { return u.Ui.ProgressBar() }
|
||||
|
||||
func (u *TimestampedUi) timestampLine(string string) string {
|
||||
return fmt.Sprintf("%v: %v", time.Now().Format(time.RFC3339), string)
|
||||
}
|
||||
|
@ -378,6 +367,7 @@ func (u *TimestampedUi) timestampLine(string string) string {
|
|||
type SafeUi struct {
|
||||
Sem chan int
|
||||
Ui Ui
|
||||
*uiProgressBar
|
||||
}
|
||||
|
||||
var _ Ui = new(SafeUi)
|
||||
|
@ -413,7 +403,3 @@ func (u *SafeUi) Machine(t string, args ...string) {
|
|||
u.Ui.Machine(t, args...)
|
||||
<-u.Sem
|
||||
}
|
||||
|
||||
func (u *SafeUi) ProgressBar() ProgressBar {
|
||||
return new(NoopProgressBar)
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ func (tty *testTTY) ReadString() (string, error) {
|
|||
|
||||
func TestColoredUi(t *testing.T) {
|
||||
bufferUi := testUi()
|
||||
ui := &ColoredUi{UiColorYellow, UiColorRed, bufferUi}
|
||||
ui := &ColoredUi{UiColorYellow, UiColorRed, bufferUi, defaultUiProgressBar}
|
||||
|
||||
if !ui.supportsColors() {
|
||||
t.Skip("skipping for ui without color support")
|
||||
|
@ -80,7 +80,7 @@ func TestColoredUi(t *testing.T) {
|
|||
|
||||
func TestColoredUi_noColorEnv(t *testing.T) {
|
||||
bufferUi := testUi()
|
||||
ui := &ColoredUi{UiColorYellow, UiColorRed, bufferUi}
|
||||
ui := &ColoredUi{UiColorYellow, UiColorRed, bufferUi, defaultUiProgressBar}
|
||||
|
||||
// Set the env var to get rid of the color
|
||||
oldenv := os.Getenv("PACKER_NO_COLOR")
|
||||
|
|
|
@ -46,7 +46,6 @@ func TestChecksumSHA1(t *testing.T) {
|
|||
func setup(t *testing.T) (packer.Ui, packer.Artifact, error) {
|
||||
// Create fake UI and Cache
|
||||
ui := packer.TestUi(t)
|
||||
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||
|
||||
// Create config for file builder
|
||||
const fileConfig = `{"builders":[{"type":"file","target":"package.txt","content":"Hello world!"}]}`
|
||||
|
@ -68,7 +67,7 @@ func setup(t *testing.T) (packer.Ui, packer.Artifact, error) {
|
|||
}
|
||||
|
||||
// Run the file builder
|
||||
artifact, err := builder.Run(ui, nil, cache)
|
||||
artifact, err := builder.Run(ui, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed to build artifact: %s", err)
|
||||
}
|
||||
|
|
|
@ -186,7 +186,6 @@ func TestCompressInterpolation(t *testing.T) {
|
|||
func setup(t *testing.T) (packer.Ui, packer.Artifact, error) {
|
||||
// Create fake UI and Cache
|
||||
ui := packer.TestUi(t)
|
||||
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||
|
||||
// Create config for file builder
|
||||
const fileConfig = `{"builders":[{"type":"file","target":"package.txt","content":"Hello world!"}]}`
|
||||
|
@ -208,7 +207,7 @@ func setup(t *testing.T) (packer.Ui, packer.Artifact, error) {
|
|||
}
|
||||
|
||||
// Run the file builder
|
||||
artifact, err := builder.Run(ui, nil, cache)
|
||||
artifact, err := builder.Run(ui, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed to build artifact: %s", err)
|
||||
}
|
||||
|
|
|
@ -328,7 +328,6 @@ func testProvisionerProvisionDockerWithPlaybookFiles(t *testing.T, templateStrin
|
|||
}
|
||||
|
||||
ui := packer.TestUi(t)
|
||||
cache := &packer.FileCache{CacheDir: os.TempDir()}
|
||||
|
||||
tpl, err := template.Parse(strings.NewReader(templateString))
|
||||
if err != nil {
|
||||
|
@ -375,7 +374,7 @@ func testProvisionerProvisionDockerWithPlaybookFiles(t *testing.T, templateStrin
|
|||
}
|
||||
hook := &packer.DispatchHook{Mapping: hooks}
|
||||
|
||||
artifact, err := builder.Run(ui, hook, cache)
|
||||
artifact, err := builder.Run(ui, hook)
|
||||
if err != nil {
|
||||
t.Fatalf("Error running build %s", err)
|
||||
}
|
||||
|
|
|
@ -126,11 +126,6 @@ func (p *Provisioner) ProvisionDownload(ui packer.Ui, comm packer.Communicator)
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
// Get a default progress bar
|
||||
pb := packer.NoopProgressBar{}
|
||||
pb.Start(0) // TODO: find size ? Remove ?
|
||||
defer pb.Finish()
|
||||
|
||||
// Create MultiWriter for the current progress
|
||||
pf := io.MultiWriter(f)
|
||||
|
||||
|
@ -175,13 +170,8 @@ func (p *Provisioner) ProvisionUpload(ui packer.Ui, comm packer.Communicator) er
|
|||
dst = dst + filepath.Base(src)
|
||||
}
|
||||
|
||||
// Get a default progress bar
|
||||
bar := ui.ProgressBar()
|
||||
bar.Start(info.Size())
|
||||
defer bar.Finish()
|
||||
|
||||
// Create ProxyReader for the current progress
|
||||
pf := bar.NewProxyReader(f)
|
||||
pf := ui.TrackProgress(filepath.Base(src), 0, info.Size(), f)
|
||||
defer pf.Close()
|
||||
|
||||
// Upload the file
|
||||
if err = comm.Upload(dst, pf, &fi); err != nil {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# This is the official list of cloud authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
|
||||
# Names should be added to this file as:
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
Filippo Valsorda <hi@filippo.io>
|
||||
Google Inc.
|
||||
Ingo Oeser <nightlyone@googlemail.com>
|
||||
Palm Stone Games, Inc.
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
Tyler Treat <ttreat31@gmail.com>
|
|
@ -0,0 +1,40 @@
|
|||
# People who have agreed to one of the CLAs and can contribute patches.
|
||||
# The AUTHORS file lists the copyright holders; this file
|
||||
# lists people. For example, Google employees are listed here
|
||||
# but not in AUTHORS, because Google holds the copyright.
|
||||
#
|
||||
# https://developers.google.com/open-source/cla/individual
|
||||
# https://developers.google.com/open-source/cla/corporate
|
||||
#
|
||||
# Names should be added to this file as:
|
||||
# Name <email address>
|
||||
|
||||
# Keep the list alphabetically sorted.
|
||||
|
||||
Alexis Hunt <lexer@google.com>
|
||||
Andreas Litt <andreas.litt@gmail.com>
|
||||
Andrew Gerrand <adg@golang.org>
|
||||
Brad Fitzpatrick <bradfitz@golang.org>
|
||||
Burcu Dogan <jbd@google.com>
|
||||
Dave Day <djd@golang.org>
|
||||
David Sansome <me@davidsansome.com>
|
||||
David Symonds <dsymonds@golang.org>
|
||||
Filippo Valsorda <hi@filippo.io>
|
||||
Glenn Lewis <gmlewis@google.com>
|
||||
Ingo Oeser <nightlyone@googlemail.com>
|
||||
James Hall <james.hall@shopify.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Jonathan Amsterdam <jba@google.com>
|
||||
Kunpei Sakai <namusyaka@gmail.com>
|
||||
Luna Duclos <luna.duclos@palmstonegames.com>
|
||||
Magnus Hiie <magnus.hiie@gmail.com>
|
||||
Mario Castro <mariocaster@gmail.com>
|
||||
Michael McGreevy <mcgreevy@golang.org>
|
||||
Omar Jarjur <ojarjur@google.com>
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
Sarah Adams <shadams@google.com>
|
||||
Thanatat Tamtan <acoshift@gmail.com>
|
||||
Toby Burress <kurin@google.com>
|
||||
Tuo Shan <shantuo@google.com>
|
||||
Tyler Treat <ttreat31@gmail.com>
|
|
@ -187,7 +187,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2014 Google Inc.
|
||||
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.
|
|
@ -0,0 +1,513 @@
|
|||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Package metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254"
|
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST"
|
||||
|
||||
userAgent = "gcloud-golang/0.1"
|
||||
)
|
||||
|
||||
type cachedValue struct {
|
||||
k string
|
||||
trim bool
|
||||
mu sync.Mutex
|
||||
v string
|
||||
}
|
||||
|
||||
var (
|
||||
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var (
|
||||
defaultClient = &Client{hc: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
ResponseHeaderTimeout: 2 * time.Second,
|
||||
},
|
||||
}}
|
||||
subscribeClient = &Client{hc: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
},
|
||||
}}
|
||||
)
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string
|
||||
|
||||
func (suffix NotDefinedError) Error() string {
|
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
func (c *cachedValue) get(cl *Client) (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = cl.getTrimmed(c.k)
|
||||
} else {
|
||||
v, err = cl.Get(c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
onGCEOnce sync.Once
|
||||
onGCE bool
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||
func OnGCE() bool {
|
||||
onGCEOnce.Do(initOnGCE)
|
||||
return onGCE
|
||||
}
|
||||
|
||||
func initOnGCE() {
|
||||
onGCE = testOnGCE()
|
||||
}
|
||||
|
||||
func testOnGCE() bool {
|
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
resc := make(chan bool, 2)
|
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go func() {
|
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := defaultClient.hc.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
addrs, err := net.LookupHost("metadata.google.internal")
|
||||
if err != nil || len(addrs) == 0 {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
resc <- strsContains(addrs, metadataIP)
|
||||
}()
|
||||
|
||||
tryHarder := systemInfoSuggestsGCE()
|
||||
if tryHarder {
|
||||
res := <-resc
|
||||
if res {
|
||||
// The first strategy succeeded, so let's use it.
|
||||
return true
|
||||
}
|
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case res = <-resc:
|
||||
return res
|
||||
case <-timer.C:
|
||||
// Too slow. Who knows what this system is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc
|
||||
}
|
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
func systemInfoSuggestsGCE() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
// We don't have any non-Linux clues available, at least yet.
|
||||
return false
|
||||
}
|
||||
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
|
||||
name := strings.TrimSpace(string(slurp))
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
|
||||
// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no
|
||||
// ResponseHeaderTimeout).
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return subscribeClient.Subscribe(suffix, fn)
|
||||
}
|
||||
|
||||
// Get calls Client.Get on the default client.
|
||||
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return defaultClient.ProjectID() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) { return defaultClient.InternalIP() }
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) { return defaultClient.Hostname() }
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) { return defaultClient.InstanceID() }
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) { return defaultClient.InstanceName() }
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) { return defaultClient.Zone() }
|
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributes on the default client.
|
||||
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
|
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributes on the default client.
|
||||
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
|
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValue(attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValue(attr)
|
||||
}
|
||||
|
||||
// Scopes calls Client.Scopes on the default client.
|
||||
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata. All HTTP requests
|
||||
// will use the given http.Client instead of the default client.
|
||||
func NewClient(c *http.Client) *Client {
|
||||
return &Client{hc: c}
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(suffix string) (value, etag string, err error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, _ := http.NewRequest("GET", u, nil)
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func (c *Client) Get(suffix string) (string, error) {
|
||||
val, _, err := c.getETag(suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (c *Client) getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = c.Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) lines(suffix string) ([]string, error) {
|
||||
j, err := c.Get(suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) Hostname() (string, error) {
|
||||
return c.getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := c.Get("instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceName() (string, error) {
|
||||
host, err := c.Hostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Split(host, ".")[0], nil
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) Zone() (string, error) {
|
||||
zone, err := c.getTrimmed("instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
|
||||
return c.Get("instance/attributes/" + attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
|
||||
return c.Get("project/attributes/" + attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||
// is deleted. Subscribe returns the error value returned from the last call to
|
||||
// fn, which may be nil when ok == false.
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
const failedSubscribeSleep = time.Second * 5
|
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := c.getETag(suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(val, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok := true
|
||||
if strings.ContainsRune(suffix, '?') {
|
||||
suffix += "&wait_for_change=true&last_etag="
|
||||
} else {
|
||||
suffix += "?wait_for_change=true&last_etag="
|
||||
}
|
||||
for {
|
||||
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
|
||||
if err != nil {
|
||||
if _, deleted := err.(NotDefinedError); !deleted {
|
||||
time.Sleep(failedSubscribeSleep)
|
||||
continue // Retry on other errors.
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
lastETag = etag
|
||||
|
||||
if err := fn(val, ok); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
// Code is the HTTP response status code.
|
||||
Code int
|
||||
// Message is the server response message.
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Package iam supports the resource-specific operations of Google Cloud
|
||||
// IAM (Identity and Access Management) for the Google Cloud Libraries.
|
||||
// See https://cloud.google.com/iam for more about IAM.
|
||||
//
|
||||
// Users of the Google Cloud Libraries will typically not use this package
|
||||
// directly. Instead they will begin with some resource that supports IAM, like
|
||||
// a pubsub topic, and call its IAM method to get a Handle for that resource.
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
pb "google.golang.org/genproto/googleapis/iam/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// client abstracts the IAMPolicy API to allow multiple implementations.
|
||||
type client interface {
|
||||
Get(ctx context.Context, resource string) (*pb.Policy, error)
|
||||
Set(ctx context.Context, resource string, p *pb.Policy) error
|
||||
Test(ctx context.Context, resource string, perms []string) ([]string, error)
|
||||
}
|
||||
|
||||
// grpcClient implements client for the standard gRPC-based IAMPolicy service.
|
||||
type grpcClient struct {
|
||||
c pb.IAMPolicyClient
|
||||
}
|
||||
|
||||
var withRetry = gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.DeadlineExceeded,
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60 * time.Second,
|
||||
Multiplier: 1.3,
|
||||
})
|
||||
})
|
||||
|
||||
func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) {
|
||||
var proto *pb.Policy
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
var err error
|
||||
proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{Resource: resource})
|
||||
return err
|
||||
}, withRetry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proto, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error {
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
_, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{
|
||||
Resource: resource,
|
||||
Policy: p,
|
||||
})
|
||||
return err
|
||||
}, withRetry)
|
||||
}
|
||||
|
||||
func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
|
||||
var res *pb.TestIamPermissionsResponse
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
var err error
|
||||
res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{
|
||||
Resource: resource,
|
||||
Permissions: perms,
|
||||
})
|
||||
return err
|
||||
}, withRetry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Permissions, nil
|
||||
}
|
||||
|
||||
// A Handle provides IAM operations for a resource.
|
||||
type Handle struct {
|
||||
c client
|
||||
resource string
|
||||
}
|
||||
|
||||
// InternalNewHandle is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewHandle returns a Handle for resource.
|
||||
// The conn parameter refers to a server that must support the IAMPolicy service.
|
||||
func InternalNewHandle(conn *grpc.ClientConn, resource string) *Handle {
|
||||
return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource)
|
||||
}
|
||||
|
||||
// InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewHandleClient returns a Handle for resource using the given
|
||||
// grpc service that implements IAM as a mixin
|
||||
func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle {
|
||||
return InternalNewHandleClient(&grpcClient{c: c}, resource)
|
||||
}
|
||||
|
||||
// InternalNewHandleClient is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewHandleClient returns a Handle for resource using the given
|
||||
// client implementation.
|
||||
func InternalNewHandleClient(c client, resource string) *Handle {
|
||||
return &Handle{
|
||||
c: c,
|
||||
resource: resource,
|
||||
}
|
||||
}
|
||||
|
||||
// Policy retrieves the IAM policy for the resource.
|
||||
func (h *Handle) Policy(ctx context.Context) (*Policy, error) {
|
||||
proto, err := h.c.Get(ctx, h.resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Policy{InternalProto: proto}, nil
|
||||
}
|
||||
|
||||
// SetPolicy replaces the resource's current policy with the supplied Policy.
|
||||
//
|
||||
// If policy was created from a prior call to Get, then the modification will
|
||||
// only succeed if the policy has not changed since the Get.
|
||||
func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error {
|
||||
return h.c.Set(ctx, h.resource, policy.InternalProto)
|
||||
}
|
||||
|
||||
// TestPermissions returns the subset of permissions that the caller has on the resource.
|
||||
func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
|
||||
return h.c.Test(ctx, h.resource, permissions)
|
||||
}
|
||||
|
||||
// A RoleName is a name representing a collection of permissions.
|
||||
type RoleName string
|
||||
|
||||
// Common role names.
|
||||
const (
|
||||
Owner RoleName = "roles/owner"
|
||||
Editor RoleName = "roles/editor"
|
||||
Viewer RoleName = "roles/viewer"
|
||||
)
|
||||
|
||||
const (
|
||||
// AllUsers is a special member that denotes all users, even unauthenticated ones.
|
||||
AllUsers = "allUsers"
|
||||
|
||||
// AllAuthenticatedUsers is a special member that denotes all authenticated users.
|
||||
AllAuthenticatedUsers = "allAuthenticatedUsers"
|
||||
)
|
||||
|
||||
// A Policy is a list of Bindings representing roles
|
||||
// granted to members.
|
||||
//
|
||||
// The zero Policy is a valid policy with no bindings.
|
||||
type Policy struct {
|
||||
// TODO(jba): when type aliases are available, put Policy into an internal package
|
||||
// and provide an exported alias here.
|
||||
|
||||
// This field is exported for use by the Google Cloud Libraries only.
|
||||
// It may become unexported in a future release.
|
||||
InternalProto *pb.Policy
|
||||
}
|
||||
|
||||
// Members returns the list of members with the supplied role.
|
||||
// The return value should not be modified. Use Add and Remove
|
||||
// to modify the members of a role.
|
||||
func (p *Policy) Members(r RoleName) []string {
|
||||
b := p.binding(r)
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
return b.Members
|
||||
}
|
||||
|
||||
// HasRole reports whether member has role r.
|
||||
func (p *Policy) HasRole(member string, r RoleName) bool {
|
||||
return memberIndex(member, p.binding(r)) >= 0
|
||||
}
|
||||
|
||||
// Add adds member member to role r if it is not already present.
|
||||
// A new binding is created if there is no binding for the role.
|
||||
func (p *Policy) Add(member string, r RoleName) {
|
||||
b := p.binding(r)
|
||||
if b == nil {
|
||||
if p.InternalProto == nil {
|
||||
p.InternalProto = &pb.Policy{}
|
||||
}
|
||||
p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{
|
||||
Role: string(r),
|
||||
Members: []string{member},
|
||||
})
|
||||
return
|
||||
}
|
||||
if memberIndex(member, b) < 0 {
|
||||
b.Members = append(b.Members, member)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes member from role r if it is present.
|
||||
func (p *Policy) Remove(member string, r RoleName) {
|
||||
bi := p.bindingIndex(r)
|
||||
if bi < 0 {
|
||||
return
|
||||
}
|
||||
bindings := p.InternalProto.Bindings
|
||||
b := bindings[bi]
|
||||
mi := memberIndex(member, b)
|
||||
if mi < 0 {
|
||||
return
|
||||
}
|
||||
// Order doesn't matter for bindings or members, so to remove, move the last item
|
||||
// into the removed spot and shrink the slice.
|
||||
if len(b.Members) == 1 {
|
||||
// Remove binding.
|
||||
last := len(bindings) - 1
|
||||
bindings[bi] = bindings[last]
|
||||
bindings[last] = nil
|
||||
p.InternalProto.Bindings = bindings[:last]
|
||||
return
|
||||
}
|
||||
// Remove member.
|
||||
// TODO(jba): worry about multiple copies of m?
|
||||
last := len(b.Members) - 1
|
||||
b.Members[mi] = b.Members[last]
|
||||
b.Members[last] = ""
|
||||
b.Members = b.Members[:last]
|
||||
}
|
||||
|
||||
// Roles returns the names of all the roles that appear in the Policy.
|
||||
func (p *Policy) Roles() []RoleName {
|
||||
if p.InternalProto == nil {
|
||||
return nil
|
||||
}
|
||||
var rns []RoleName
|
||||
for _, b := range p.InternalProto.Bindings {
|
||||
rns = append(rns, RoleName(b.Role))
|
||||
}
|
||||
return rns
|
||||
}
|
||||
|
||||
// binding returns the Binding for the suppied role, or nil if there isn't one.
|
||||
func (p *Policy) binding(r RoleName) *pb.Binding {
|
||||
i := p.bindingIndex(r)
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
return p.InternalProto.Bindings[i]
|
||||
}
|
||||
|
||||
func (p *Policy) bindingIndex(r RoleName) int {
|
||||
if p.InternalProto == nil {
|
||||
return -1
|
||||
}
|
||||
for i, b := range p.InternalProto.Bindings {
|
||||
if b.Role == string(r) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// memberIndex returns the index of m in b's Members, or -1 if not found.
|
||||
func memberIndex(m string, b *pb.Binding) int {
|
||||
if b == nil {
|
||||
return -1
|
||||
}
|
||||
for i, mm := range b.Members {
|
||||
if mm == m {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// insertMetadata inserts metadata into the given context
|
||||
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
|
||||
out, _ := metadata.FromOutgoingContext(ctx)
|
||||
out = out.Copy()
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return metadata.NewOutgoingContext(ctx, out)
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Annotate prepends msg to the error message in err, attempting
|
||||
// to preserve other information in err, like an error code.
|
||||
//
|
||||
// Annotate panics if err is nil.
|
||||
//
|
||||
// Annotate knows about these error types:
|
||||
// - "google.golang.org/grpc/status".Status
|
||||
// - "google.golang.org/api/googleapi".Error
|
||||
// If the error is not one of these types, Annotate behaves
|
||||
// like
|
||||
// fmt.Errorf("%s: %v", msg, err)
|
||||
func Annotate(err error, msg string) error {
|
||||
if err == nil {
|
||||
panic("Annotate called with nil")
|
||||
}
|
||||
if s, ok := status.FromError(err); ok {
|
||||
p := s.Proto()
|
||||
p.Message = msg + ": " + p.Message
|
||||
return status.ErrorProto(p)
|
||||
}
|
||||
if g, ok := err.(*googleapi.Error); ok {
|
||||
g.Message = msg + ": " + g.Message
|
||||
return g
|
||||
}
|
||||
return fmt.Errorf("%s: %v", msg, err)
|
||||
}
|
||||
|
||||
// Annotatef uses format and args to format a string, then calls Annotate.
|
||||
func Annotatef(err error, format string, args ...interface{}) error {
|
||||
return Annotate(err, fmt.Sprintf(format, args...))
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Package optional provides versions of primitive types that can
|
||||
// be nil. These are useful in methods that update some of an API object's
|
||||
// fields.
|
||||
package optional
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// Bool is either a bool or nil.
|
||||
Bool interface{}
|
||||
|
||||
// String is either a string or nil.
|
||||
String interface{}
|
||||
|
||||
// Int is either an int or nil.
|
||||
Int interface{}
|
||||
|
||||
// Uint is either a uint or nil.
|
||||
Uint interface{}
|
||||
|
||||
// Float64 is either a float64 or nil.
|
||||
Float64 interface{}
|
||||
|
||||
// Duration is either a time.Duration or nil.
|
||||
Duration interface{}
|
||||
)
|
||||
|
||||
// ToBool returns its argument as a bool.
|
||||
// It panics if its argument is nil or not a bool.
|
||||
func ToBool(v Bool) bool {
|
||||
x, ok := v.(bool)
|
||||
if !ok {
|
||||
doPanic("Bool", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToString returns its argument as a string.
|
||||
// It panics if its argument is nil or not a string.
|
||||
func ToString(v String) string {
|
||||
x, ok := v.(string)
|
||||
if !ok {
|
||||
doPanic("String", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToInt returns its argument as an int.
|
||||
// It panics if its argument is nil or not an int.
|
||||
func ToInt(v Int) int {
|
||||
x, ok := v.(int)
|
||||
if !ok {
|
||||
doPanic("Int", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToUint returns its argument as a uint.
|
||||
// It panics if its argument is nil or not a uint.
|
||||
func ToUint(v Uint) uint {
|
||||
x, ok := v.(uint)
|
||||
if !ok {
|
||||
doPanic("Uint", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToFloat64 returns its argument as a float64.
|
||||
// It panics if its argument is nil or not a float64.
|
||||
func ToFloat64(v Float64) float64 {
|
||||
x, ok := v.(float64)
|
||||
if !ok {
|
||||
doPanic("Float64", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToDuration returns its argument as a time.Duration.
|
||||
// It panics if its argument is nil or not a time.Duration.
|
||||
func ToDuration(v Duration) time.Duration {
|
||||
x, ok := v.(time.Duration)
|
||||
if !ok {
|
||||
doPanic("Duration", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func doPanic(capType string, v interface{}) {
|
||||
panic(fmt.Sprintf("optional.%s value should be %s, got %T", capType, strings.ToLower(capType), v))
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
)
|
||||
|
||||
// Retry calls the supplied function f repeatedly according to the provided
|
||||
// backoff parameters. It returns when one of the following occurs:
|
||||
// When f's first return value is true, Retry immediately returns with f's second
|
||||
// return value.
|
||||
// When the provided context is done, Retry returns with an error that
|
||||
// includes both ctx.Error() and the last error returned by f.
|
||||
func Retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error)) error {
|
||||
return retry(ctx, bo, f, gax.Sleep)
|
||||
}
|
||||
|
||||
func retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error),
|
||||
sleep func(context.Context, time.Duration) error) error {
|
||||
var lastErr error
|
||||
for {
|
||||
stop, err := f()
|
||||
if stop {
|
||||
return err
|
||||
}
|
||||
// Remember the last "real" error from f.
|
||||
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
|
||||
lastErr = err
|
||||
}
|
||||
p := bo.Pause()
|
||||
if cerr := sleep(ctx, p); cerr != nil {
|
||||
if lastErr != nil {
|
||||
return Annotatef(lastErr, "retry failed with %v; last error", cerr)
|
||||
}
|
||||
return cerr
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/genproto/googleapis/rpc/code"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// StartSpan adds a span to the trace with the given name.
|
||||
func StartSpan(ctx context.Context, name string) context.Context {
|
||||
ctx, _ = trace.StartSpan(ctx, name)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// EndSpan ends a span with the given error.
|
||||
func EndSpan(ctx context.Context, err error) {
|
||||
span := trace.FromContext(ctx)
|
||||
if err != nil {
|
||||
span.SetStatus(toStatus(err))
|
||||
}
|
||||
span.End()
|
||||
}
|
||||
|
||||
// ToStatus interrogates an error and converts it to an appropriate
|
||||
// OpenCensus status.
|
||||
func toStatus(err error) trace.Status {
|
||||
if err2, ok := err.(*googleapi.Error); ok {
|
||||
return trace.Status{Code: httpStatusCodeToOCCode(err2.Code), Message: err2.Message}
|
||||
} else if s, ok := status.FromError(err); ok {
|
||||
return trace.Status{Code: int32(s.Code()), Message: s.Message()}
|
||||
} else {
|
||||
return trace.Status{Code: int32(code.Code_UNKNOWN), Message: err.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (deklerk): switch to using OpenCensus function when it becomes available.
|
||||
// Reference: https://github.com/googleapis/googleapis/blob/26b634d2724ac5dd30ae0b0cbfb01f07f2e4050e/google/rpc/code.proto
|
||||
func httpStatusCodeToOCCode(httpStatusCode int) int32 {
|
||||
switch httpStatusCode {
|
||||
case 200:
|
||||
return int32(code.Code_OK)
|
||||
case 499:
|
||||
return int32(code.Code_CANCELLED)
|
||||
case 500:
|
||||
return int32(code.Code_UNKNOWN) // Could also be Code_INTERNAL, Code_DATA_LOSS
|
||||
case 400:
|
||||
return int32(code.Code_INVALID_ARGUMENT) // Could also be Code_OUT_OF_RANGE
|
||||
case 504:
|
||||
return int32(code.Code_DEADLINE_EXCEEDED)
|
||||
case 404:
|
||||
return int32(code.Code_NOT_FOUND)
|
||||
case 409:
|
||||
return int32(code.Code_ALREADY_EXISTS) // Could also be Code_ABORTED
|
||||
case 403:
|
||||
return int32(code.Code_PERMISSION_DENIED)
|
||||
case 401:
|
||||
return int32(code.Code_UNAUTHENTICATED)
|
||||
case 429:
|
||||
return int32(code.Code_RESOURCE_EXHAUSTED)
|
||||
case 501:
|
||||
return int32(code.Code_UNIMPLEMENTED)
|
||||
case 503:
|
||||
return int32(code.Code_UNAVAILABLE)
|
||||
default:
|
||||
return int32(code.Code_UNKNOWN)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
today=$(date +%Y%m%d)
|
||||
|
||||
sed -i -r -e 's/const Repo = "([0-9]{8})"/const Repo = "'$today'"/' $GOFILE
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
//go:generate ./update_version.sh
|
||||
|
||||
// Package version contains version information for Google Cloud Client
|
||||
// Libraries for Go, as reported in request headers.
|
||||
package version
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Repo is the current version of the client libraries in this
|
||||
// repo. It should be a date in YYYYMMDD format.
|
||||
const Repo = "20180226"
|
||||
|
||||
// Go returns the Go runtime version. The returned string
|
||||
// has no whitespace.
|
||||
func Go() string {
|
||||
return goVersion
|
||||
}
|
||||
|
||||
var goVersion = goVer(runtime.Version())
|
||||
|
||||
const develPrefix = "devel +"
|
||||
|
||||
func goVer(s string) string {
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
s += "-" + prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func notSemverRune(r rune) bool {
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
|
@ -0,0 +1,335 @@
|
|||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// ACLRole is the level of access to grant.
|
||||
type ACLRole string
|
||||
|
||||
const (
|
||||
RoleOwner ACLRole = "OWNER"
|
||||
RoleReader ACLRole = "READER"
|
||||
RoleWriter ACLRole = "WRITER"
|
||||
)
|
||||
|
||||
// ACLEntity refers to a user or group.
|
||||
// They are sometimes referred to as grantees.
|
||||
//
|
||||
// It could be in the form of:
|
||||
// "user-<userId>", "user-<email>", "group-<groupId>", "group-<email>",
|
||||
// "domain-<domain>" and "project-team-<projectId>".
|
||||
//
|
||||
// Or one of the predefined constants: AllUsers, AllAuthenticatedUsers.
|
||||
type ACLEntity string
|
||||
|
||||
const (
|
||||
AllUsers ACLEntity = "allUsers"
|
||||
AllAuthenticatedUsers ACLEntity = "allAuthenticatedUsers"
|
||||
)
|
||||
|
||||
// ACLRule represents a grant for a role to an entity (user, group or team) for a
|
||||
// Google Cloud Storage object or bucket.
|
||||
type ACLRule struct {
|
||||
Entity ACLEntity
|
||||
EntityID string
|
||||
Role ACLRole
|
||||
Domain string
|
||||
Email string
|
||||
ProjectTeam *ProjectTeam
|
||||
}
|
||||
|
||||
// ProjectTeam is the project team associated with the entity, if any.
|
||||
type ProjectTeam struct {
|
||||
ProjectNumber string
|
||||
Team string
|
||||
}
|
||||
|
||||
// ACLHandle provides operations on an access control list for a Google Cloud Storage bucket or object.
|
||||
type ACLHandle struct {
|
||||
c *Client
|
||||
bucket string
|
||||
object string
|
||||
isDefault bool
|
||||
userProject string // for requester-pays buckets
|
||||
}
|
||||
|
||||
// Delete permanently deletes the ACL entry for the given entity.
|
||||
func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Delete")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectDelete(ctx, entity)
|
||||
}
|
||||
if a.isDefault {
|
||||
return a.bucketDefaultDelete(ctx, entity)
|
||||
}
|
||||
return a.bucketDelete(ctx, entity)
|
||||
}
|
||||
|
||||
// Set sets the role for the given entity.
|
||||
func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Set")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectSet(ctx, entity, role, false)
|
||||
}
|
||||
if a.isDefault {
|
||||
return a.objectSet(ctx, entity, role, true)
|
||||
}
|
||||
return a.bucketSet(ctx, entity, role)
|
||||
}
|
||||
|
||||
// List retrieves ACL entries.
|
||||
func (a *ACLHandle) List(ctx context.Context) (rules []ACLRule, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.List")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectList(ctx)
|
||||
}
|
||||
if a.isDefault {
|
||||
return a.bucketDefaultList(ctx)
|
||||
}
|
||||
return a.bucketList(ctx)
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDefaultList(ctx context.Context) ([]ACLRule, error) {
|
||||
var acls *raw.ObjectAccessControls
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.DefaultObjectAccessControls.List(a.bucket)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toObjectACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDefaultDelete(ctx context.Context, entity ACLEntity) error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.DefaultObjectAccessControls.Delete(a.bucket, string(entity))
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketList(ctx context.Context) ([]ACLRule, error) {
|
||||
var acls *raw.BucketAccessControls
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.List(a.bucket)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toBucketACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketSet(ctx context.Context, entity ACLEntity, role ACLRole) error {
|
||||
acl := &raw.BucketAccessControl{
|
||||
Bucket: a.bucket,
|
||||
Entity: string(entity),
|
||||
Role: string(role),
|
||||
}
|
||||
err := runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.Update(a.bucket, string(entity), acl)
|
||||
a.configureCall(ctx, req)
|
||||
_, err := req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDelete(ctx context.Context, entity ACLEntity) error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.Delete(a.bucket, string(entity))
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectList(ctx context.Context) ([]ACLRule, error) {
|
||||
var acls *raw.ObjectAccessControls
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.ObjectAccessControls.List(a.bucket, a.object)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toObjectACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectSet(ctx context.Context, entity ACLEntity, role ACLRole, isBucketDefault bool) error {
|
||||
type setRequest interface {
|
||||
Do(opts ...googleapi.CallOption) (*raw.ObjectAccessControl, error)
|
||||
Header() http.Header
|
||||
}
|
||||
|
||||
acl := &raw.ObjectAccessControl{
|
||||
Bucket: a.bucket,
|
||||
Entity: string(entity),
|
||||
Role: string(role),
|
||||
}
|
||||
var req setRequest
|
||||
if isBucketDefault {
|
||||
req = a.c.raw.DefaultObjectAccessControls.Update(a.bucket, string(entity), acl)
|
||||
} else {
|
||||
req = a.c.raw.ObjectAccessControls.Update(a.bucket, a.object, string(entity), acl)
|
||||
}
|
||||
a.configureCall(ctx, req)
|
||||
return runWithRetry(ctx, func() error {
|
||||
_, err := req.Do()
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectDelete(ctx context.Context, entity ACLEntity) error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.ObjectAccessControls.Delete(a.bucket, a.object, string(entity))
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACLHandle) configureCall(ctx context.Context, call interface{ Header() http.Header }) {
|
||||
vc := reflect.ValueOf(call)
|
||||
vc.MethodByName("Context").Call([]reflect.Value{reflect.ValueOf(ctx)})
|
||||
if a.userProject != "" {
|
||||
vc.MethodByName("UserProject").Call([]reflect.Value{reflect.ValueOf(a.userProject)})
|
||||
}
|
||||
setClientHeader(call.Header())
|
||||
}
|
||||
|
||||
func toObjectACLRules(items []*raw.ObjectAccessControl) []ACLRule {
|
||||
var rs []ACLRule
|
||||
for _, item := range items {
|
||||
rs = append(rs, toObjectACLRule(item))
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func toBucketACLRules(items []*raw.BucketAccessControl) []ACLRule {
|
||||
var rs []ACLRule
|
||||
for _, item := range items {
|
||||
rs = append(rs, toBucketACLRule(item))
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func toObjectACLRule(a *raw.ObjectAccessControl) ACLRule {
|
||||
return ACLRule{
|
||||
Entity: ACLEntity(a.Entity),
|
||||
EntityID: a.EntityId,
|
||||
Role: ACLRole(a.Role),
|
||||
Domain: a.Domain,
|
||||
Email: a.Email,
|
||||
ProjectTeam: toObjectProjectTeam(a.ProjectTeam),
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketACLRule(a *raw.BucketAccessControl) ACLRule {
|
||||
return ACLRule{
|
||||
Entity: ACLEntity(a.Entity),
|
||||
EntityID: a.EntityId,
|
||||
Role: ACLRole(a.Role),
|
||||
Domain: a.Domain,
|
||||
Email: a.Email,
|
||||
ProjectTeam: toBucketProjectTeam(a.ProjectTeam),
|
||||
}
|
||||
}
|
||||
|
||||
func toRawObjectACL(rules []ACLRule) []*raw.ObjectAccessControl {
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
r := make([]*raw.ObjectAccessControl, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
r = append(r, rule.toRawObjectAccessControl("")) // bucket name unnecessary
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func toRawBucketACL(rules []ACLRule) []*raw.BucketAccessControl {
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
r := make([]*raw.BucketAccessControl, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
r = append(r, rule.toRawBucketAccessControl("")) // bucket name unnecessary
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r ACLRule) toRawBucketAccessControl(bucket string) *raw.BucketAccessControl {
|
||||
return &raw.BucketAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(r.Entity),
|
||||
Role: string(r.Role),
|
||||
// The other fields are not settable.
|
||||
}
|
||||
}
|
||||
|
||||
func (r ACLRule) toRawObjectAccessControl(bucket string) *raw.ObjectAccessControl {
|
||||
return &raw.ObjectAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(r.Entity),
|
||||
Role: string(r.Role),
|
||||
// The other fields are not settable.
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketProjectTeam(p *raw.BucketAccessControlProjectTeam) *ProjectTeam {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &ProjectTeam{
|
||||
ProjectNumber: p.ProjectNumber,
|
||||
Team: p.Team,
|
||||
}
|
||||
}
|
||||
|
||||
func toObjectProjectTeam(p *raw.ObjectAccessControlProjectTeam) *ProjectTeam {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &ProjectTeam{
|
||||
ProjectNumber: p.ProjectNumber,
|
||||
Team: p.Team,
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,228 @@
|
|||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// CopierFrom creates a Copier that can copy src to dst.
|
||||
// You can immediately call Run on the returned Copier, or
|
||||
// you can configure it first.
|
||||
//
|
||||
// For Requester Pays buckets, the user project of dst is billed, unless it is empty,
|
||||
// in which case the user project of src is billed.
|
||||
func (dst *ObjectHandle) CopierFrom(src *ObjectHandle) *Copier {
|
||||
return &Copier{dst: dst, src: src}
|
||||
}
|
||||
|
||||
// A Copier copies a source object to a destination.
|
||||
type Copier struct {
|
||||
// ObjectAttrs are optional attributes to set on the destination object.
|
||||
// Any attributes must be initialized before any calls on the Copier. Nil
|
||||
// or zero-valued attributes are ignored.
|
||||
ObjectAttrs
|
||||
|
||||
// RewriteToken can be set before calling Run to resume a copy
|
||||
// operation. After Run returns a non-nil error, RewriteToken will
|
||||
// have been updated to contain the value needed to resume the copy.
|
||||
RewriteToken string
|
||||
|
||||
// ProgressFunc can be used to monitor the progress of a multi-RPC copy
|
||||
// operation. If ProgressFunc is not nil and copying requires multiple
|
||||
// calls to the underlying service (see
|
||||
// https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite), then
|
||||
// ProgressFunc will be invoked after each call with the number of bytes of
|
||||
// content copied so far and the total size in bytes of the source object.
|
||||
//
|
||||
// ProgressFunc is intended to make upload progress available to the
|
||||
// application. For example, the implementation of ProgressFunc may update
|
||||
// a progress bar in the application's UI, or log the result of
|
||||
// float64(copiedBytes)/float64(totalBytes).
|
||||
//
|
||||
// ProgressFunc should return quickly without blocking.
|
||||
ProgressFunc func(copiedBytes, totalBytes uint64)
|
||||
|
||||
// The Cloud KMS key, in the form projects/P/locations/L/keyRings/R/cryptoKeys/K,
|
||||
// that will be used to encrypt the object. Overrides the object's KMSKeyName, if
|
||||
// any.
|
||||
//
|
||||
// Providing both a DestinationKMSKeyName and a customer-supplied encryption key
|
||||
// (via ObjectHandle.Key) on the destination object will result in an error when
|
||||
// Run is called.
|
||||
DestinationKMSKeyName string
|
||||
|
||||
dst, src *ObjectHandle
|
||||
}
|
||||
|
||||
// Run performs the copy.
|
||||
func (c *Copier) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Copier.Run")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := c.src.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.dst.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.DestinationKMSKeyName != "" && c.dst.encryptionKey != nil {
|
||||
return nil, errors.New("storage: cannot use DestinationKMSKeyName with a customer-supplied encryption key")
|
||||
}
|
||||
// Convert destination attributes to raw form, omitting the bucket.
|
||||
// If the bucket is included but name or content-type aren't, the service
|
||||
// returns a 400 with "Required" as the only message. Omitting the bucket
|
||||
// does not cause any problems.
|
||||
rawObject := c.ObjectAttrs.toRawObject("")
|
||||
for {
|
||||
res, err := c.callRewrite(ctx, rawObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.ProgressFunc != nil {
|
||||
c.ProgressFunc(uint64(res.TotalBytesRewritten), uint64(res.ObjectSize))
|
||||
}
|
||||
if res.Done { // Finished successfully.
|
||||
return newObject(res.Resource), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Copier) callRewrite(ctx context.Context, rawObj *raw.Object) (*raw.RewriteResponse, error) {
|
||||
call := c.dst.c.raw.Objects.Rewrite(c.src.bucket, c.src.object, c.dst.bucket, c.dst.object, rawObj)
|
||||
|
||||
call.Context(ctx).Projection("full")
|
||||
if c.RewriteToken != "" {
|
||||
call.RewriteToken(c.RewriteToken)
|
||||
}
|
||||
if c.DestinationKMSKeyName != "" {
|
||||
call.DestinationKmsKeyName(c.DestinationKMSKeyName)
|
||||
}
|
||||
if c.PredefinedACL != "" {
|
||||
call.DestinationPredefinedAcl(c.PredefinedACL)
|
||||
}
|
||||
if err := applyConds("Copy destination", c.dst.gen, c.dst.conds, call); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.dst.userProject != "" {
|
||||
call.UserProject(c.dst.userProject)
|
||||
} else if c.src.userProject != "" {
|
||||
call.UserProject(c.src.userProject)
|
||||
}
|
||||
if err := applySourceConds(c.src.gen, c.src.conds, call); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), c.src.encryptionKey, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res *raw.RewriteResponse
|
||||
var err error
|
||||
setClientHeader(call.Header())
|
||||
err = runWithRetry(ctx, func() error { res, err = call.Do(); return err })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.RewriteToken = res.RewriteToken
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ComposerFrom creates a Composer that can compose srcs into dst.
|
||||
// You can immediately call Run on the returned Composer, or you can
|
||||
// configure it first.
|
||||
//
|
||||
// The encryption key for the destination object will be used to decrypt all
|
||||
// source objects and encrypt the destination object. It is an error
|
||||
// to specify an encryption key for any of the source objects.
|
||||
func (dst *ObjectHandle) ComposerFrom(srcs ...*ObjectHandle) *Composer {
|
||||
return &Composer{dst: dst, srcs: srcs}
|
||||
}
|
||||
|
||||
// A Composer composes source objects into a destination object.
|
||||
//
|
||||
// For Requester Pays buckets, the user project of dst is billed.
|
||||
type Composer struct {
|
||||
// ObjectAttrs are optional attributes to set on the destination object.
|
||||
// Any attributes must be initialized before any calls on the Composer. Nil
|
||||
// or zero-valued attributes are ignored.
|
||||
ObjectAttrs
|
||||
|
||||
dst *ObjectHandle
|
||||
srcs []*ObjectHandle
|
||||
}
|
||||
|
||||
// Run performs the compose operation.
|
||||
func (c *Composer) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Composer.Run")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := c.dst.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(c.srcs) == 0 {
|
||||
return nil, errors.New("storage: at least one source object must be specified")
|
||||
}
|
||||
|
||||
req := &raw.ComposeRequest{}
|
||||
// Compose requires a non-empty Destination, so we always set it,
|
||||
// even if the caller-provided ObjectAttrs is the zero value.
|
||||
req.Destination = c.ObjectAttrs.toRawObject(c.dst.bucket)
|
||||
for _, src := range c.srcs {
|
||||
if err := src.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if src.bucket != c.dst.bucket {
|
||||
return nil, fmt.Errorf("storage: all source objects must be in bucket %q, found %q", c.dst.bucket, src.bucket)
|
||||
}
|
||||
if src.encryptionKey != nil {
|
||||
return nil, fmt.Errorf("storage: compose source %s.%s must not have encryption key", src.bucket, src.object)
|
||||
}
|
||||
srcObj := &raw.ComposeRequestSourceObjects{
|
||||
Name: src.object,
|
||||
}
|
||||
if err := applyConds("ComposeFrom source", src.gen, src.conds, composeSourceObj{srcObj}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.SourceObjects = append(req.SourceObjects, srcObj)
|
||||
}
|
||||
|
||||
call := c.dst.c.raw.Objects.Compose(c.dst.bucket, c.dst.object, req).Context(ctx)
|
||||
if err := applyConds("ComposeFrom destination", c.dst.gen, c.dst.conds, call); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.dst.userProject != "" {
|
||||
call.UserProject(c.dst.userProject)
|
||||
}
|
||||
if c.PredefinedACL != "" {
|
||||
call.DestinationPredefinedAcl(c.PredefinedACL)
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var obj *raw.Object
|
||||
setClientHeader(call.Header())
|
||||
err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err })
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newObject(obj), nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue