From 541952bb8f96936340f4021c76b1108d934ea363 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 16 Oct 2018 19:48:28 -0700 Subject: [PATCH] Revert some work that happened since v0.12-dev branched This work was done against APIs that were already changed in the branch before work began, and so it doesn't apply to the v0.12 development work. To allow v0.12 to merge down to master, we'll revert this work out for now and then re-introduce equivalent functionality in later commits that works against the new APIs. --- backend/init/init.go | 3 +- backend/init/init_test.go | 73 +- backend/legacy/legacy.go | 28 - backend/legacy/legacy_test.go | 34 - backend/local/backend_test.go | 22 +- backend/local/testing.go | 50 +- backend/remote/backend.go | 581 ---------- backend/remote/backend_apply.go | 243 ----- backend/remote/backend_apply_test.go | 882 ---------------- backend/remote/backend_common.go | 305 ------ backend/remote/backend_mock.go | 998 ------------------ backend/remote/backend_plan.go | 319 ------ backend/remote/backend_plan_test.go | 597 ----------- backend/remote/backend_state.go | 181 ---- backend/remote/backend_state_test.go | 58 - backend/remote/backend_test.go | 240 ----- backend/remote/cli.go | 13 - backend/remote/colorize.go | 47 - .../test-fixtures/apply-destroy/apply.log | 4 - .../test-fixtures/apply-destroy/main.tf | 1 - .../test-fixtures/apply-destroy/plan.log | 22 - .../test-fixtures/apply-no-changes/main.tf | 1 - .../test-fixtures/apply-no-changes/plan.log | 17 - .../apply-policy-hard-failed/main.tf | 1 - .../apply-policy-hard-failed/plan.log | 21 - .../apply-policy-hard-failed/policy.log | 12 - .../apply-policy-passed/apply.log | 4 - .../test-fixtures/apply-policy-passed/main.tf | 1 - .../apply-policy-passed/plan.log | 21 - .../apply-policy-passed/policy.log | 12 - .../apply-policy-soft-failed/apply.log | 4 - .../apply-policy-soft-failed/main.tf | 1 - .../apply-policy-soft-failed/plan.log | 21 - .../apply-policy-soft-failed/policy.log | 12 - .../test-fixtures/apply-with-error/main.tf | 5 - .../test-fixtures/apply-with-error/plan.log | 10 - backend/remote/test-fixtures/apply/apply.log | 4 - backend/remote/test-fixtures/apply/main.tf | 1 - backend/remote/test-fixtures/apply/plan.log | 21 - .../plan-policy-hard-failed/main.tf | 1 - .../plan-policy-hard-failed/plan.log | 21 - .../plan-policy-hard-failed/policy.log | 12 - .../test-fixtures/plan-policy-passed/main.tf | 1 - .../test-fixtures/plan-policy-passed/plan.log | 21 - .../plan-policy-passed/policy.log | 12 - .../plan-policy-soft-failed/main.tf | 1 - .../plan-policy-soft-failed/plan.log | 21 - .../plan-policy-soft-failed/policy.log | 12 - .../test-fixtures/plan-with-error/main.tf | 5 - .../test-fixtures/plan-with-error/plan.log | 10 - .../terraform/main.tf | 1 - .../terraform/plan.log | 21 - backend/remote/test-fixtures/plan/main.tf | 1 - backend/remote/test-fixtures/plan/plan.log | 21 - backend/remote/testing.go | 137 --- .../terraform/data_source_state_test.go | 2 +- builtin/providers/terraform/provider_test.go | 2 + command/command_test.go | 7 +- command/init.go | 9 +- command/meta.go | 4 + command/meta_backend.go | 19 +- command/meta_backend_migrate.go | 107 +- command/meta_backend_test.go | 189 +--- command/meta_config.go | 1 - command/state_meta.go | 2 +- .../Azure/go-autorest/autorest/adal/README.md | 34 - .../Azure/go-autorest/autorest/adal/config.go | 30 - .../go-autorest/autorest/adal/devicetoken.go | 14 - .../Azure/go-autorest/autorest/adal/msi.go | 20 - .../go-autorest/autorest/adal/msi_windows.go | 25 - .../go-autorest/autorest/adal/persist.go | 14 - .../Azure/go-autorest/autorest/adal/sender.go | 14 - .../Azure/go-autorest/autorest/adal/token.go | 324 +----- .../go-autorest/autorest/authorization.go | 95 +- .../Azure/go-autorest/autorest/autorest.go | 17 - .../Azure/go-autorest/autorest/azure/async.go | 288 +---- .../Azure/go-autorest/autorest/azure/azure.go | 22 +- .../autorest/azure/environments.go | 61 +- .../Azure/go-autorest/autorest/azure/rp.go | 203 ---- .../Azure/go-autorest/autorest/client.go | 41 +- .../Azure/go-autorest/autorest/date/date.go | 14 - .../Azure/go-autorest/autorest/date/time.go | 14 - .../go-autorest/autorest/date/timerfc1123.go | 14 - .../go-autorest/autorest/date/unixtime.go | 14 - .../go-autorest/autorest/date/utility.go | 14 - .../Azure/go-autorest/autorest/error.go | 14 - .../Azure/go-autorest/autorest/preparer.go | 60 +- .../Azure/go-autorest/autorest/responder.go | 14 - .../go-autorest/autorest/retriablerequest.go | 14 - .../autorest/retriablerequest_1.7.go | 36 +- .../autorest/retriablerequest_1.8.go | 42 +- .../Azure/go-autorest/autorest/sender.go | 28 +- .../Azure/go-autorest/autorest/to/convert.go | 14 - .../Azure/go-autorest/autorest/utility.go | 90 +- .../autorest/validation/validation.go | 21 +- .../Azure/go-autorest/autorest/version.go | 20 +- .../github.com/google/go-querystring/LICENSE | 27 - .../google/go-querystring/query/encode.go | 320 ------ vendor/github.com/hashicorp/go-slug/LICENSE | 373 ------- vendor/github.com/hashicorp/go-slug/README.md | 70 -- vendor/github.com/hashicorp/go-slug/slug.go | 215 ---- vendor/github.com/hashicorp/go-tfe/LICENSE | 354 ------- vendor/github.com/hashicorp/go-tfe/README.md | 131 --- vendor/github.com/hashicorp/go-tfe/apply.go | 131 --- .../hashicorp/go-tfe/configuration_version.go | 199 ---- .../github.com/hashicorp/go-tfe/logreader.go | 138 --- .../hashicorp/go-tfe/oauth_client.go | 199 ---- .../hashicorp/go-tfe/oauth_token.go | 150 --- .../hashicorp/go-tfe/organization.go | 310 ------ .../hashicorp/go-tfe/organization_token.go | 99 -- vendor/github.com/hashicorp/go-tfe/plan.go | 132 --- vendor/github.com/hashicorp/go-tfe/policy.go | 282 ----- .../hashicorp/go-tfe/policy_check.go | 219 ---- vendor/github.com/hashicorp/go-tfe/run.go | 309 ------ vendor/github.com/hashicorp/go-tfe/ssh_key.go | 198 ---- .../hashicorp/go-tfe/state_version.go | 216 ---- vendor/github.com/hashicorp/go-tfe/team.go | 165 --- .../hashicorp/go-tfe/team_access.go | 184 ---- .../hashicorp/go-tfe/team_member.go | 139 --- .../github.com/hashicorp/go-tfe/team_token.go | 99 -- vendor/github.com/hashicorp/go-tfe/tfe.go | 420 -------- .../hashicorp/go-tfe/type_helpers.go | 46 - vendor/github.com/hashicorp/go-tfe/user.go | 93 -- .../hashicorp/go-tfe/validations.go | 19 - .../github.com/hashicorp/go-tfe/variable.go | 243 ----- .../github.com/hashicorp/go-tfe/workspace.go | 447 -------- .../github.com/svanharmelen/jsonapi/LICENSE | 21 - .../github.com/svanharmelen/jsonapi/README.md | 457 -------- .../svanharmelen/jsonapi/constants.go | 55 - vendor/github.com/svanharmelen/jsonapi/doc.go | 70 -- .../github.com/svanharmelen/jsonapi/errors.go | 55 - .../github.com/svanharmelen/jsonapi/node.go | 121 --- .../svanharmelen/jsonapi/request.go | 680 ------------ .../svanharmelen/jsonapi/response.go | 539 ---------- .../svanharmelen/jsonapi/runtime.go | 103 -- .../xanzy/ssh-agent/pageant_windows.go | 28 +- 136 files changed, 300 insertions(+), 14891 deletions(-) delete mode 100644 backend/legacy/legacy.go delete mode 100644 backend/legacy/legacy_test.go delete mode 100644 backend/remote/backend.go delete mode 100644 backend/remote/backend_apply.go delete mode 100644 backend/remote/backend_apply_test.go delete mode 100644 backend/remote/backend_common.go delete mode 100644 backend/remote/backend_mock.go delete mode 100644 backend/remote/backend_plan.go delete mode 100644 backend/remote/backend_plan_test.go delete mode 100644 backend/remote/backend_state.go delete mode 100644 backend/remote/backend_state_test.go delete mode 100644 backend/remote/backend_test.go delete mode 100644 backend/remote/cli.go delete mode 100644 backend/remote/colorize.go delete mode 100644 backend/remote/test-fixtures/apply-destroy/apply.log delete mode 100644 backend/remote/test-fixtures/apply-destroy/main.tf delete mode 100644 backend/remote/test-fixtures/apply-destroy/plan.log delete mode 100644 backend/remote/test-fixtures/apply-no-changes/main.tf delete mode 100644 backend/remote/test-fixtures/apply-no-changes/plan.log delete mode 100644 backend/remote/test-fixtures/apply-policy-hard-failed/main.tf delete mode 100644 backend/remote/test-fixtures/apply-policy-hard-failed/plan.log delete mode 100644 backend/remote/test-fixtures/apply-policy-hard-failed/policy.log delete mode 100644 backend/remote/test-fixtures/apply-policy-passed/apply.log delete mode 100644 backend/remote/test-fixtures/apply-policy-passed/main.tf delete mode 100644 backend/remote/test-fixtures/apply-policy-passed/plan.log delete mode 100644 backend/remote/test-fixtures/apply-policy-passed/policy.log delete mode 100644 backend/remote/test-fixtures/apply-policy-soft-failed/apply.log delete mode 100644 backend/remote/test-fixtures/apply-policy-soft-failed/main.tf delete mode 100644 backend/remote/test-fixtures/apply-policy-soft-failed/plan.log delete mode 100644 backend/remote/test-fixtures/apply-policy-soft-failed/policy.log delete mode 100644 backend/remote/test-fixtures/apply-with-error/main.tf delete mode 100644 backend/remote/test-fixtures/apply-with-error/plan.log delete mode 100644 backend/remote/test-fixtures/apply/apply.log delete mode 100644 backend/remote/test-fixtures/apply/main.tf delete mode 100644 backend/remote/test-fixtures/apply/plan.log delete mode 100644 backend/remote/test-fixtures/plan-policy-hard-failed/main.tf delete mode 100644 backend/remote/test-fixtures/plan-policy-hard-failed/plan.log delete mode 100644 backend/remote/test-fixtures/plan-policy-hard-failed/policy.log delete mode 100644 backend/remote/test-fixtures/plan-policy-passed/main.tf delete mode 100644 backend/remote/test-fixtures/plan-policy-passed/plan.log delete mode 100644 backend/remote/test-fixtures/plan-policy-passed/policy.log delete mode 100644 backend/remote/test-fixtures/plan-policy-soft-failed/main.tf delete mode 100644 backend/remote/test-fixtures/plan-policy-soft-failed/plan.log delete mode 100644 backend/remote/test-fixtures/plan-policy-soft-failed/policy.log delete mode 100644 backend/remote/test-fixtures/plan-with-error/main.tf delete mode 100644 backend/remote/test-fixtures/plan-with-error/plan.log delete mode 100644 backend/remote/test-fixtures/plan-with-working-directory/terraform/main.tf delete mode 100644 backend/remote/test-fixtures/plan-with-working-directory/terraform/plan.log delete mode 100644 backend/remote/test-fixtures/plan/main.tf delete mode 100644 backend/remote/test-fixtures/plan/plan.log delete mode 100644 backend/remote/testing.go delete mode 100644 vendor/github.com/Azure/go-autorest/autorest/adal/msi.go delete mode 100644 vendor/github.com/Azure/go-autorest/autorest/adal/msi_windows.go delete mode 100644 vendor/github.com/Azure/go-autorest/autorest/azure/rp.go delete mode 100644 vendor/github.com/google/go-querystring/LICENSE delete mode 100644 vendor/github.com/google/go-querystring/query/encode.go delete mode 100644 vendor/github.com/hashicorp/go-slug/LICENSE delete mode 100644 vendor/github.com/hashicorp/go-slug/README.md delete mode 100644 vendor/github.com/hashicorp/go-slug/slug.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/LICENSE delete mode 100644 vendor/github.com/hashicorp/go-tfe/README.md delete mode 100644 vendor/github.com/hashicorp/go-tfe/apply.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/configuration_version.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/logreader.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/oauth_client.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/oauth_token.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/organization.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/organization_token.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/plan.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/policy.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/policy_check.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/run.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/ssh_key.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/state_version.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/team.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/team_access.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/team_member.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/team_token.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/tfe.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/type_helpers.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/user.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/validations.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/variable.go delete mode 100644 vendor/github.com/hashicorp/go-tfe/workspace.go delete mode 100644 vendor/github.com/svanharmelen/jsonapi/LICENSE delete mode 100644 vendor/github.com/svanharmelen/jsonapi/README.md delete mode 100644 vendor/github.com/svanharmelen/jsonapi/constants.go delete mode 100644 vendor/github.com/svanharmelen/jsonapi/doc.go delete mode 100644 vendor/github.com/svanharmelen/jsonapi/errors.go delete mode 100644 vendor/github.com/svanharmelen/jsonapi/node.go delete mode 100644 vendor/github.com/svanharmelen/jsonapi/request.go delete mode 100644 vendor/github.com/svanharmelen/jsonapi/response.go delete mode 100644 vendor/github.com/svanharmelen/jsonapi/runtime.go diff --git a/backend/init/init.go b/backend/init/init.go index 1709f93ba..81286406b 100644 --- a/backend/init/init.go +++ b/backend/init/init.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/svchost/disco" "github.com/hashicorp/terraform/tfdiags" backendatlas "github.com/hashicorp/terraform/backend/atlas" @@ -39,7 +40,7 @@ import ( var backends map[string]func() backend.Backend var backendsLock sync.Mutex -func init() { +func Init(services *disco.Disco) { // Our hardcoded backends. We don't need to acquire a lock here // since init() code is serial and can't spawn goroutines. backends = map[string]func() backend.Backend{ diff --git a/backend/init/init_test.go b/backend/init/init_test.go index 150b4c101..804033bdf 100644 --- a/backend/init/init_test.go +++ b/backend/init/init_test.go @@ -16,53 +16,31 @@ func TestInit_backend(t *testing.T) { Name string Type string }{ - { - "local", - "*local.Local", - }, { - "remote", - "*remote.Remote", - }, { - "atlas", - "*atlas.Backend", - }, { - "azurerm", - "*azure.Backend", - }, { - "consul", - "*consul.Backend", - }, { - "etcdv3", - "*etcd.Backend", - }, { - "gcs", - "*gcs.Backend", - }, { - "inmem", - "*inmem.Backend", - }, { - "manta", - "*manta.Backend", - }, { - "s3", - "*s3.Backend", - }, { - "swift", - "*swift.Backend", - }, { - "azure", - "init.deprecatedBackendShim", - }, + {"local", "*local.Local"}, + {"atlas", "*atlas.Backend"}, + {"azurerm", "*azure.Backend"}, + {"consul", "*consul.Backend"}, + {"etcdv3", "*etcd.Backend"}, + {"gcs", "*gcs.Backend"}, + {"inmem", "*inmem.Backend"}, + {"manta", "*manta.Backend"}, + {"s3", "*s3.Backend"}, + {"swift", "*swift.Backend"}, + {"azure", "init.deprecatedBackendShim"}, } // Make sure we get the requested backend for _, b := range backends { - f := Backend(b.Name) - bType := reflect.TypeOf(f()).String() - - if bType != b.Type { - t.Fatalf("expected backend %q to be %q, got: %q", b.Name, b.Type, bType) - } + t.Run(b.Name, func(t *testing.T) { + f := Backend(b.Name) + if f == nil { + t.Fatalf("backend %q is not present; should be", b.Name) + } + bType := reflect.TypeOf(f()).String() + if bType != b.Type { + t.Fatalf("expected backend %q to be %q, got: %q", b.Name, b.Type, bType) + } + }) } } @@ -74,13 +52,7 @@ func TestInit_forceLocalBackend(t *testing.T) { Name string Type string }{ - { - "local", - "nil", - }, { - "remote", - "*remote.Remote", - }, + {"local", "nil"}, } // Set the TF_FORCE_LOCAL_BACKEND flag so all enhanced backends will @@ -88,6 +60,7 @@ func TestInit_forceLocalBackend(t *testing.T) { if err := os.Setenv("TF_FORCE_LOCAL_BACKEND", "1"); err != nil { t.Fatalf("error setting environment variable TF_FORCE_LOCAL_BACKEND: %v", err) } + defer os.Unsetenv("TF_FORCE_LOCAL_BACKEND") // Make sure we always get the local backend. for _, b := range enhancedBackends { diff --git a/backend/legacy/legacy.go b/backend/legacy/legacy.go deleted file mode 100644 index 6ed3e41d3..000000000 --- a/backend/legacy/legacy.go +++ /dev/null @@ -1,28 +0,0 @@ -// Package legacy contains a backend implementation that can be used -// with the legacy remote state clients. -package legacy - -import ( - "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/state/remote" -) - -// Init updates the backend/init package map of initializers to support -// all the remote state types. -// -// If a type is already in the map, it will not be added. This will allow -// us to slowly convert the legacy types to first-class backends. -func Init(m map[string]backend.InitFn) { - for k := range remote.BuiltinClients { - if _, ok := m[k]; !ok { - // Copy the "k" value since the variable "k" is reused for - // each key (address doesn't change). - typ := k - - // Build the factory function to return a backend of typ - m[k] = func() backend.Backend { - return &Backend{Type: typ} - } - } - } -} diff --git a/backend/legacy/legacy_test.go b/backend/legacy/legacy_test.go deleted file mode 100644 index 8a13c2577..000000000 --- a/backend/legacy/legacy_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package legacy - -import ( - "testing" - - "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/state/remote" -) - -func TestInit(t *testing.T) { - m := make(map[string]backend.InitFn) - Init(m) - - for k, _ := range remote.BuiltinClients { - b, ok := m[k] - if !ok { - t.Fatalf("missing: %s", k) - } - - if typ := b().(*Backend).Type; typ != k { - t.Fatalf("bad type: %s", typ) - } - } -} - -func TestInit_ignoreExisting(t *testing.T) { - m := make(map[string]backend.InitFn) - m["local"] = nil - Init(m) - - if v, ok := m["local"]; !ok || v != nil { - t.Fatalf("bad: %#v", m) - } -} diff --git a/backend/local/backend_test.go b/backend/local/backend_test.go index f936acd82..de3f75c07 100644 --- a/backend/local/backend_test.go +++ b/backend/local/backend_test.go @@ -15,14 +15,14 @@ import ( ) func TestLocal_impl(t *testing.T) { - var _ backend.Enhanced = New() - var _ backend.Local = New() - var _ backend.CLI = New() + var _ backend.Enhanced = new(Local) + var _ backend.Local = new(Local) + var _ backend.CLI = new(Local) } func TestLocal_backend(t *testing.T) { defer testTmpDir(t)() - b := New() + b := &Local{} backend.TestBackendStates(t, b) backend.TestBackendStateLocks(t, b, b) } @@ -49,7 +49,7 @@ func checkState(t *testing.T, path, expected string) { } func TestLocal_StatePaths(t *testing.T) { - b := New() + b := &Local{} // Test the defaults path, out, back := b.StatePaths("") @@ -207,11 +207,13 @@ func (b *testDelegateBackend) DeleteWorkspace(name string) error { // verify that the MultiState methods are dispatched to the correct Backend. func TestLocal_multiStateBackend(t *testing.T) { // assign a separate backend where we can read the state - b := NewWithBackend(&testDelegateBackend{ - stateErr: true, - statesErr: true, - deleteErr: true, - }) + b := &Local{ + Backend: &testDelegateBackend{ + stateErr: true, + statesErr: true, + deleteErr: true, + }, + } if _, err := b.StateMgr("test"); err != errTestDelegateState { t.Fatal("expected errTestDelegateState, got:", err) diff --git a/backend/local/testing.go b/backend/local/testing.go index 9266c9ccb..38a2da221 100644 --- a/backend/local/testing.go +++ b/backend/local/testing.go @@ -89,7 +89,7 @@ func TestLocalProvider(t *testing.T, b *Local, name string, schema *terraform.Pr // TestNewLocalSingle is a factory for creating a TestLocalSingleState. // This function matches the signature required for backend/init. func TestNewLocalSingle() backend.Backend { - return &TestLocalSingleState{Local: &Local{}} + return &TestLocalSingleState{} } // TestLocalSingleState is a backend implementation that wraps Local @@ -99,7 +99,7 @@ func TestNewLocalSingle() backend.Backend { // This isn't an actual use case, this is exported just to provide a // easy way to test that behavior. type TestLocalSingleState struct { - *Local + Local } func (b *TestLocalSingleState) State(name string) (statemgr.Full, error) { @@ -118,50 +118,6 @@ func (b *TestLocalSingleState) DeleteState(string) error { return backend.ErrNamedStatesNotSupported } -// TestNewLocalNoDefault is a factory for creating a TestLocalNoDefaultState. -// This function matches the signature required for backend/init. -func TestNewLocalNoDefault() backend.Backend { - return &TestLocalNoDefaultState{Local: &Local{}} -} - -// TestLocalNoDefaultState is a backend implementation that wraps -// Local and modifies it to support named states, but not the -// default state. It returns ErrDefaultStateNotSupported when the -// DefaultStateName is used. -type TestLocalNoDefaultState struct { - *Local -} - -func (b *TestLocalNoDefaultState) State(name string) (state.State, error) { - if name == backend.DefaultStateName { - return nil, backend.ErrDefaultStateNotSupported - } - return b.Local.State(name) -} - -func (b *TestLocalNoDefaultState) States() ([]string, error) { - states, err := b.Local.States() - if err != nil { - return nil, err - } - - filtered := states[:0] - for _, name := range states { - if name != backend.DefaultStateName { - filtered = append(filtered, name) - } - } - - return filtered, nil -} - -func (b *TestLocalNoDefaultState) DeleteState(name string) error { - if name == backend.DefaultStateName { - return backend.ErrDefaultStateNotSupported - } - return b.Local.DeleteState(name) -} - func testTempDir(t *testing.T) string { d, err := ioutil.TempDir("", "tf") if err != nil { @@ -174,4 +130,4 @@ func testTempDir(t *testing.T) string { func testStateFile(t *testing.T, path string, s *states.State) { stateFile := statemgr.NewFilesystem(path) stateFile.WriteState(s) -} +} \ No newline at end of file diff --git a/backend/remote/backend.go b/backend/remote/backend.go deleted file mode 100644 index a94bba02e..000000000 --- a/backend/remote/backend.go +++ /dev/null @@ -1,581 +0,0 @@ -package remote - -import ( - "context" - "fmt" - "log" - "net/http" - "net/url" - "os" - "sort" - "strings" - "sync" - - tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/state" - "github.com/hashicorp/terraform/state/remote" - "github.com/hashicorp/terraform/svchost" - "github.com/hashicorp/terraform/svchost/disco" - "github.com/hashicorp/terraform/terraform" - "github.com/hashicorp/terraform/version" - "github.com/mitchellh/cli" - "github.com/mitchellh/colorstring" -) - -const ( - defaultHostname = "app.terraform.io" - defaultModuleDepth = -1 - defaultParallelism = 10 - serviceID = "tfe.v2" -) - -// Remote is an implementation of EnhancedBackend that performs all -// operations in a remote backend. -type Remote struct { - // CLI and Colorize control the CLI output. If CLI is nil then no CLI - // output will be done. If CLIColor is nil then no coloring will be done. - CLI cli.Ui - CLIColor *colorstring.Colorize - - // ContextOpts are the base context options to set when initializing a - // new Terraform context. Many of these will be overridden or merged by - // Operation. See Operation for more details. - ContextOpts *terraform.ContextOpts - - // client is the remote backend API client - client *tfe.Client - - // hostname of the remote backend server - hostname string - - // organization is the organization that contains the target workspaces - organization string - - // workspace is used to map the default workspace to a remote workspace - workspace string - - // prefix is used to filter down a set of workspaces that use a single - // configuration - prefix string - - // schema defines the configuration for the backend - schema *schema.Backend - - // services is used for service discovery - services *disco.Disco - - // opLock locks operations - opLock sync.Mutex -} - -// New creates a new initialized remote backend. -func New(services *disco.Disco) *Remote { - b := &Remote{ - services: services, - } - - b.schema = &schema.Backend{ - Schema: map[string]*schema.Schema{ - "hostname": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: schemaDescriptions["hostname"], - Default: defaultHostname, - }, - - "organization": &schema.Schema{ - Type: schema.TypeString, - Required: true, - Description: schemaDescriptions["organization"], - }, - - "token": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: schemaDescriptions["token"], - }, - - "workspaces": &schema.Schema{ - Type: schema.TypeSet, - Required: true, - Description: schemaDescriptions["workspaces"], - MinItems: 1, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: schemaDescriptions["name"], - }, - - "prefix": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: schemaDescriptions["prefix"], - }, - }, - }, - }, - }, - - ConfigureFunc: b.configure, - } - - return b -} - -func (b *Remote) configure(ctx context.Context) error { - d := schema.FromContextBackendConfig(ctx) - - // Get the hostname and organization. - b.hostname = d.Get("hostname").(string) - b.organization = d.Get("organization").(string) - - // Get and assert the workspaces configuration block. - workspace := d.Get("workspaces").(*schema.Set).List()[0].(map[string]interface{}) - - // Get the default workspace name and prefix. - b.workspace = workspace["name"].(string) - b.prefix = workspace["prefix"].(string) - - // Make sure that we have either a workspace name or a prefix. - if b.workspace == "" && b.prefix == "" { - return fmt.Errorf("either workspace 'name' or 'prefix' is required") - } - - // Make sure that only one of workspace name or a prefix is configured. - if b.workspace != "" && b.prefix != "" { - return fmt.Errorf("only one of workspace 'name' or 'prefix' is allowed") - } - - // Discover the service URL for this host to confirm that it provides - // a remote backend API and to discover the required base path. - service, err := b.discover(b.hostname) - if err != nil { - return err - } - - // Retrieve the token for this host as configured in the credentials - // section of the CLI Config File. - token, err := b.token(b.hostname) - if err != nil { - return err - } - if token == "" { - token = d.Get("token").(string) - } - - cfg := &tfe.Config{ - Address: service.String(), - BasePath: service.Path, - Token: token, - Headers: make(http.Header), - } - - // Set the version header to the current version. - cfg.Headers.Set(version.Header, version.Version) - - // Create the remote backend API client. - b.client, err = tfe.NewClient(cfg) - if err != nil { - return err - } - - return nil -} - -// discover the remote backend API service URL and token. -func (b *Remote) discover(hostname string) (*url.URL, error) { - host, err := svchost.ForComparison(hostname) - if err != nil { - return nil, err - } - service := b.services.DiscoverServiceURL(host, serviceID) - if service == nil { - return nil, fmt.Errorf("host %s does not provide a remote backend API", host) - } - return service, nil -} - -// token returns the token for this host as configured in the credentials -// section of the CLI Config File. If no token was configured, an empty -// string will be returned instead. -func (b *Remote) token(hostname string) (string, error) { - host, err := svchost.ForComparison(hostname) - if err != nil { - return "", err - } - creds, err := b.services.CredentialsForHost(host) - if err != nil { - log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", host, err) - return "", nil - } - if creds != nil { - return creds.Token(), nil - } - return "", nil -} - -// Input is called to ask the user for input for completing the configuration. -func (b *Remote) Input(ui terraform.UIInput, c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { - return b.schema.Input(ui, c) -} - -// Validate is called once at the beginning with the raw configuration and -// can return a list of warnings and/or errors. -func (b *Remote) Validate(c *terraform.ResourceConfig) ([]string, []error) { - return b.schema.Validate(c) -} - -// Configure configures the backend itself with the configuration given. -func (b *Remote) Configure(c *terraform.ResourceConfig) error { - return b.schema.Configure(c) -} - -// State returns the latest state of the given remote workspace. The workspace -// will be created if it doesn't exist. -func (b *Remote) State(workspace string) (state.State, error) { - if b.workspace == "" && workspace == backend.DefaultStateName { - return nil, backend.ErrDefaultStateNotSupported - } - if b.prefix == "" && workspace != backend.DefaultStateName { - return nil, backend.ErrNamedStatesNotSupported - } - - workspaces, err := b.states() - if err != nil { - return nil, fmt.Errorf("Error retrieving workspaces: %v", err) - } - - exists := false - for _, name := range workspaces { - if workspace == name { - exists = true - break - } - } - - // Configure the remote workspace name. - switch { - case workspace == backend.DefaultStateName: - workspace = b.workspace - case b.prefix != "" && !strings.HasPrefix(workspace, b.prefix): - workspace = b.prefix + workspace - } - - if !exists { - options := tfe.WorkspaceCreateOptions{ - Name: tfe.String(workspace), - } - - // We only set the Terraform Version for the new workspace if this is - // a release candidate or a final release. - if version.Prerelease == "" || strings.HasPrefix(version.Prerelease, "rc") { - options.TerraformVersion = tfe.String(version.String()) - } - - _, err = b.client.Workspaces.Create(context.Background(), b.organization, options) - if err != nil { - return nil, fmt.Errorf("Error creating workspace %s: %v", workspace, err) - } - } - - client := &remoteClient{ - client: b.client, - organization: b.organization, - workspace: workspace, - - // This is optionally set during Terraform Enterprise runs. - runID: os.Getenv("TFE_RUN_ID"), - } - - return &remote.State{Client: client}, nil -} - -// DeleteState removes the remote workspace if it exists. -func (b *Remote) DeleteState(workspace string) error { - if b.workspace == "" && workspace == backend.DefaultStateName { - return backend.ErrDefaultStateNotSupported - } - if b.prefix == "" && workspace != backend.DefaultStateName { - return backend.ErrNamedStatesNotSupported - } - - // Configure the remote workspace name. - switch { - case workspace == backend.DefaultStateName: - workspace = b.workspace - case b.prefix != "" && !strings.HasPrefix(workspace, b.prefix): - workspace = b.prefix + workspace - } - - // Check if the configured organization exists. - _, err := b.client.Organizations.Read(context.Background(), b.organization) - if err != nil { - if err == tfe.ErrResourceNotFound { - return fmt.Errorf("organization %s does not exist", b.organization) - } - return err - } - - client := &remoteClient{ - client: b.client, - organization: b.organization, - workspace: workspace, - } - - return client.Delete() -} - -// States returns a filtered list of remote workspace names. -func (b *Remote) States() ([]string, error) { - if b.prefix == "" { - return nil, backend.ErrNamedStatesNotSupported - } - return b.states() -} - -func (b *Remote) states() ([]string, error) { - // Check if the configured organization exists. - _, err := b.client.Organizations.Read(context.Background(), b.organization) - if err != nil { - if err == tfe.ErrResourceNotFound { - return nil, fmt.Errorf("organization %s does not exist", b.organization) - } - return nil, err - } - - options := tfe.WorkspaceListOptions{} - switch { - case b.workspace != "": - options.Search = tfe.String(b.workspace) - case b.prefix != "": - options.Search = tfe.String(b.prefix) - } - - // Create a slice to contain all the names. - var names []string - - for { - wl, err := b.client.Workspaces.List(context.Background(), b.organization, options) - if err != nil { - return nil, err - } - - for _, w := range wl.Items { - if b.workspace != "" && w.Name == b.workspace { - names = append(names, backend.DefaultStateName) - continue - } - if b.prefix != "" && strings.HasPrefix(w.Name, b.prefix) { - names = append(names, strings.TrimPrefix(w.Name, b.prefix)) - } - } - - // Exit the loop when we've seen all pages. - if wl.CurrentPage >= wl.TotalPages { - break - } - - // Update the page number to get the next page. - options.PageNumber = wl.NextPage - } - - // Sort the result so we have consistent output. - sort.StringSlice(names).Sort() - - return names, nil -} - -// Operation implements backend.Enhanced -func (b *Remote) Operation(ctx context.Context, op *backend.Operation) (*backend.RunningOperation, error) { - // Configure the remote workspace name. - switch { - case op.Workspace == backend.DefaultStateName: - op.Workspace = b.workspace - case b.prefix != "" && !strings.HasPrefix(op.Workspace, b.prefix): - op.Workspace = b.prefix + op.Workspace - } - - // Determine the function to call for our operation - var f func(context.Context, context.Context, *backend.Operation) (*tfe.Run, error) - switch op.Type { - case backend.OperationTypePlan: - f = b.opPlan - case backend.OperationTypeApply: - f = b.opApply - default: - return nil, fmt.Errorf( - "\n\nThe \"remote\" backend does not support the %q operation.\n"+ - "Please use the remote backend web UI for running this operation:\n"+ - "https://%s/app/%s/%s", op.Type, b.hostname, b.organization, op.Workspace) - } - - // Lock - b.opLock.Lock() - - // Build our running operation - // the runninCtx is only used to block until the operation returns. - runningCtx, done := context.WithCancel(context.Background()) - runningOp := &backend.RunningOperation{ - Context: runningCtx, - PlanEmpty: true, - } - - // stopCtx wraps the context passed in, and is used to signal a graceful Stop. - stopCtx, stop := context.WithCancel(ctx) - runningOp.Stop = stop - - // cancelCtx is used to cancel the operation immediately, usually - // indicating that the process is exiting. - cancelCtx, cancel := context.WithCancel(context.Background()) - runningOp.Cancel = cancel - - // Do it. - go func() { - defer done() - defer stop() - defer cancel() - - defer b.opLock.Unlock() - - r, opErr := f(stopCtx, cancelCtx, op) - if opErr != nil && opErr != context.Canceled { - runningOp.Err = opErr - return - } - - if r != nil { - // Retrieve the run to get its current status. - r, err := b.client.Runs.Read(cancelCtx, r.ID) - if err != nil { - runningOp.Err = generalError("error retrieving run", err) - return - } - - // Record if there are any changes. - runningOp.PlanEmpty = !r.HasChanges - - if opErr == context.Canceled { - runningOp.Err = b.cancel(cancelCtx, op, r) - } - - if runningOp.Err == nil && r.Status == tfe.RunErrored { - runningOp.ExitCode = 1 - } - } - }() - - // Return the running operation. - return runningOp, nil -} - -func (b *Remote) cancel(cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error { - if r.Status == tfe.RunPending && r.Actions.IsCancelable { - // Only ask if the remote operation should be canceled - // if the auto approve flag is not set. - if !op.AutoApprove { - v, err := op.UIIn.Input(&terraform.InputOpts{ - Id: "cancel", - Query: "\nDo you want to cancel the pending remote operation?", - Description: "Only 'yes' will be accepted to cancel.", - }) - if err != nil { - return generalError("error asking to cancel", err) - } - if v != "yes" { - if b.CLI != nil { - b.CLI.Output(b.Colorize().Color(strings.TrimSpace(operationNotCanceled))) - } - return nil - } - } else { - if b.CLI != nil { - // Insert a blank line to separate the ouputs. - b.CLI.Output("") - } - } - - // Try to cancel the remote operation. - err := b.client.Runs.Cancel(cancelCtx, r.ID, tfe.RunCancelOptions{}) - if err != nil { - return generalError("error cancelling run", err) - } - if b.CLI != nil { - b.CLI.Output(b.Colorize().Color(strings.TrimSpace(operationCanceled))) - } - } - - return nil -} - -// Colorize returns the Colorize structure that can be used for colorizing -// output. This is guaranteed to always return a non-nil value and so useful -// as a helper to wrap any potentially colored strings. -// func (b *Remote) Colorize() *colorstring.Colorize { -// if b.CLIColor != nil { -// return b.CLIColor -// } - -// return &colorstring.Colorize{ -// Colors: colorstring.DefaultColors, -// Disable: true, -// } -// } - -func generalError(msg string, err error) error { - if urlErr, ok := err.(*url.Error); ok { - err = urlErr.Err - } - switch err { - case context.Canceled: - return err - case tfe.ErrResourceNotFound: - return fmt.Errorf(strings.TrimSpace(fmt.Sprintf(notFoundErr, msg, err))) - default: - return fmt.Errorf(strings.TrimSpace(fmt.Sprintf(generalErr, msg, err))) - } -} - -const generalErr = ` -%s: %v - -The configured "remote" backend encountered an unexpected error. Sometimes -this is caused by network connection problems, in which case you could retry -the command. If the issue persists please open a support ticket to get help -resolving the problem. -` - -const notFoundErr = ` -%s: %v - -The configured "remote" backend returns '404 Not Found' errors for resources -that do not exist, as well as for resources that a user doesn't have access -to. When the resource does exists, please check the rights for the used token. -` - -const operationCanceled = ` -[reset][red]The remote operation was successfully cancelled.[reset] -` - -const operationNotCanceled = ` -[reset][red]The remote operation was not cancelled.[reset] -` - -var schemaDescriptions = map[string]string{ - "hostname": "The remote backend hostname to connect to (defaults to app.terraform.io).", - "organization": "The name of the organization containing the targeted workspace(s).", - "token": "The token used to authenticate with the remote backend. If credentials for the\n" + - "host are configured in the CLI Config File, then those will be used instead.", - "workspaces": "Workspaces contains arguments used to filter down to a set of workspaces\n" + - "to work on.", - "name": "A workspace name used to map the default workspace to a named remote workspace.\n" + - "When configured only the default workspace can be used. This option conflicts\n" + - "with \"prefix\"", - "prefix": "A prefix used to filter workspaces using a single configuration. New workspaces\n" + - "will automatically be prefixed with this prefix. If omitted only the default\n" + - "workspace can be used. This option conflicts with \"name\"", -} diff --git a/backend/remote/backend_apply.go b/backend/remote/backend_apply.go deleted file mode 100644 index d3dc1ac54..000000000 --- a/backend/remote/backend_apply.go +++ /dev/null @@ -1,243 +0,0 @@ -package remote - -import ( - "bufio" - "context" - "fmt" - "log" - "strings" - - tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/terraform" -) - -func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operation) (*tfe.Run, error) { - log.Printf("[INFO] backend/remote: starting Apply operation") - - // Retrieve the workspace used to run this operation in. - w, err := b.client.Workspaces.Read(stopCtx, b.organization, op.Workspace) - if err != nil { - return nil, generalError("error retrieving workspace", err) - } - - if !w.Permissions.CanUpdate { - return nil, fmt.Errorf(strings.TrimSpace(applyErrNoUpdateRights)) - } - - if w.VCSRepo != nil { - return nil, fmt.Errorf(strings.TrimSpace(applyErrVCSNotSupported)) - } - - if op.Parallelism != defaultParallelism { - return nil, fmt.Errorf(strings.TrimSpace(applyErrParallelismNotSupported)) - } - - if op.Plan != nil { - return nil, fmt.Errorf(strings.TrimSpace(applyErrPlanNotSupported)) - } - - if !op.PlanRefresh { - return nil, fmt.Errorf(strings.TrimSpace(applyErrNoRefreshNotSupported)) - } - - if op.Targets != nil { - return nil, fmt.Errorf(strings.TrimSpace(applyErrTargetsNotSupported)) - } - - if op.Variables != nil { - return nil, fmt.Errorf(strings.TrimSpace( - fmt.Sprintf(applyErrVariablesNotSupported, b.hostname, b.organization, op.Workspace))) - } - - if (op.Module == nil || op.Module.Config().Dir == "") && !op.Destroy { - return nil, fmt.Errorf(strings.TrimSpace(applyErrNoConfig)) - } - - // Run the plan phase. - r, err := b.plan(stopCtx, cancelCtx, op, w) - if err != nil { - return r, err - } - - // This check is also performed in the plan method to determine if - // the policies should be checked, but we need to check the values - // here again to determine if we are done and should return. - if !r.HasChanges || r.Status == tfe.RunErrored { - return r, nil - } - - // Retrieve the run to get its current status. - r, err = b.client.Runs.Read(stopCtx, r.ID) - if err != nil { - return r, generalError("error retrieving run", err) - } - - // Return if the run cannot be confirmed. - if !w.AutoApply && !r.Actions.IsConfirmable { - return r, nil - } - - // Since we already checked the permissions before creating the run - // this should never happen. But it doesn't hurt to keep this in as - // a safeguard for any unexpected situations. - if !w.AutoApply && !r.Permissions.CanApply { - // Make sure we discard the run if possible. - if r.Actions.IsDiscardable { - err = b.client.Runs.Discard(stopCtx, r.ID, tfe.RunDiscardOptions{}) - if err != nil { - if op.Destroy { - return r, generalError("error disarding destroy", err) - } - return r, generalError("error disarding apply", err) - } - } - return r, fmt.Errorf(strings.TrimSpace( - fmt.Sprintf(applyErrNoApplyRights, b.hostname, b.organization, op.Workspace))) - } - - mustConfirm := (op.UIIn != nil && op.UIOut != nil) && - ((op.Destroy && (!op.DestroyForce && !op.AutoApprove)) || (!op.Destroy && !op.AutoApprove)) - - if !w.AutoApply { - if mustConfirm { - opts := &terraform.InputOpts{Id: "approve"} - - if op.Destroy { - opts.Query = "\nDo you really want to destroy all resources in workspace \"" + op.Workspace + "\"?" - opts.Description = "Terraform will destroy all your managed infrastructure, as shown above.\n" + - "There is no undo. Only 'yes' will be accepted to confirm." - } else { - opts.Query = "\nDo you want to perform these actions in workspace \"" + op.Workspace + "\"?" - opts.Description = "Terraform will perform the actions described above.\n" + - "Only 'yes' will be accepted to approve." - } - - if err = b.confirm(stopCtx, op, opts, r, "yes"); err != nil { - return r, err - } - } - - err = b.client.Runs.Apply(stopCtx, r.ID, tfe.RunApplyOptions{}) - if err != nil { - return r, generalError("error approving the apply command", err) - } - } - - // If we don't need to ask for confirmation, insert a blank - // line to separate the ouputs. - if w.AutoApply || !mustConfirm { - if b.CLI != nil { - b.CLI.Output("") - } - } - - r, err = b.waitForRun(stopCtx, cancelCtx, op, "apply", r, w) - if err != nil { - return r, err - } - - logs, err := b.client.Applies.Logs(stopCtx, r.Apply.ID) - if err != nil { - return r, generalError("error retrieving logs", err) - } - scanner := bufio.NewScanner(logs) - - skip := 0 - for scanner.Scan() { - // Skip the first 3 lines to prevent duplicate output. - if skip < 3 { - skip++ - continue - } - if b.CLI != nil { - b.CLI.Output(b.Colorize().Color(scanner.Text())) - } - } - if err := scanner.Err(); err != nil { - return r, generalError("error reading logs", err) - } - - return r, nil -} - -const applyErrNoUpdateRights = ` -Insufficient rights to apply changes! - -[reset][yellow]The provided credentials have insufficient rights to apply changes. In order -to apply changes at least write permissions on the workspace are required.[reset] -` - -const applyErrVCSNotSupported = ` -Apply not allowed for workspaces with a VCS connection. - -A workspace that is connected to a VCS requires the VCS-driven workflow -to ensure that the VCS remains the single source of truth. -` - -const applyErrParallelismNotSupported = ` -Custom parallelism values are currently not supported! - -The "remote" backend does not support setting a custom parallelism -value at this time. -` - -const applyErrPlanNotSupported = ` -Applying a saved plan is currently not supported! - -The "remote" backend currently requires configuration to be present and -does not accept an existing saved plan as an argument at this time. -` - -const applyErrNoRefreshNotSupported = ` -Applying without refresh is currently not supported! - -Currently the "remote" backend will always do an in-memory refresh of -the Terraform state prior to generating the plan. -` - -const applyErrTargetsNotSupported = ` -Resource targeting is currently not supported! - -The "remote" backend does not support resource targeting at this time. -` - -const applyErrVariablesNotSupported = ` -Run variables are currently not supported! - -The "remote" backend does not support setting run variables at this time. -Currently the only to way to pass variables to the remote backend is by -creating a '*.auto.tfvars' variables file. This file will automatically -be loaded by the "remote" backend when the workspace is configured to use -Terraform v0.10.0 or later. - -Additionally you can also set variables on the workspace in the web UI: -https://%s/app/%s/%s/variables -` - -const applyErrNoConfig = ` -No configuration files found! - -Apply requires configuration to be present. Applying without a configuration -would mark everything for destruction, which is normally not what is desired. -If you would like to destroy everything, please run 'terraform destroy' which -does not require any configuration files. -` - -const applyErrNoApplyRights = ` -Insufficient rights to approve the pending changes! - -[reset][yellow]There are pending changes, but the provided credentials have insufficient rights -to approve them. The run will be discarded to prevent it from blocking the queue -waiting for external approval. To queue a run that can be approved by someone -else, please use the 'Queue Plan' button in the web UI: -https://%s/app/%s/%s/runs[reset] -` - -const applyDefaultHeader = ` -[reset][yellow]Running apply in the remote backend. Output will stream here. Pressing Ctrl-C -will cancel the remote apply if its still pending. If the apply started it -will stop streaming the logs, but will not stop the apply running remotely. -To view this run in a browser, visit: -https://%s/app/%s/%s/runs/%s[reset] -` diff --git a/backend/remote/backend_apply_test.go b/backend/remote/backend_apply_test.go deleted file mode 100644 index 8b1095acf..000000000 --- a/backend/remote/backend_apply_test.go +++ /dev/null @@ -1,882 +0,0 @@ -package remote - -import ( - "context" - "os" - "os/signal" - "strings" - "syscall" - "testing" - "time" - - tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/config/module" - "github.com/hashicorp/terraform/terraform" - "github.com/mitchellh/cli" -) - -func testOperationApply() *backend.Operation { - return &backend.Operation{ - Parallelism: defaultParallelism, - PlanRefresh: true, - Type: backend.OperationTypeApply, - } -} - -func TestRemote_applyBasic(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - input := testInput(t, map[string]string{ - "approve": "yes", - }) - - op := testOperationApply() - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } - - if len(input.answers) > 0 { - t.Fatalf("expected no unused answers, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") { - t.Fatalf("missing apply summery in output: %s", output) - } -} - -func TestRemote_applyWithoutPermissions(t *testing.T) { - b := testBackendNoDefault(t) - - // Create a named workspace without permissions. - w, err := b.client.Workspaces.Create( - context.Background(), - b.organization, - tfe.WorkspaceCreateOptions{ - Name: tfe.String(b.prefix + "prod"), - }, - ) - if err != nil { - t.Fatalf("error creating named workspace: %v", err) - } - w.Permissions.CanUpdate = false - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - op := testOperationApply() - op.Module = mod - op.Workspace = "prod" - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !strings.Contains(run.Err.Error(), "insufficient rights to apply changes") { - t.Fatalf("expected a permissions error, got: %v", run.Err) - } -} - -func TestRemote_applyWithVCS(t *testing.T) { - b := testBackendNoDefault(t) - - // Create a named workspace with a VCS. - _, err := b.client.Workspaces.Create( - context.Background(), - b.organization, - tfe.WorkspaceCreateOptions{ - Name: tfe.String(b.prefix + "prod"), - VCSRepo: &tfe.VCSRepoOptions{}, - }, - ) - if err != nil { - t.Fatalf("error creating named workspace: %v", err) - } - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - op := testOperationApply() - op.Module = mod - op.Workspace = "prod" - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "not allowed for workspaces with a VCS") { - t.Fatalf("expected a VCS error, got: %v", run.Err) - } -} - -func TestRemote_applyWithParallelism(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - op := testOperationApply() - op.Module = mod - op.Parallelism = 3 - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !strings.Contains(run.Err.Error(), "parallelism values are currently not supported") { - t.Fatalf("expected a parallelism error, got: %v", run.Err) - } -} - -func TestRemote_applyWithPlan(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - op := testOperationApply() - op.Module = mod - op.Plan = &terraform.Plan{} - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "saved plan is currently not supported") { - t.Fatalf("expected a saved plan error, got: %v", run.Err) - } -} - -func TestRemote_applyWithoutRefresh(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - op := testOperationApply() - op.Module = mod - op.PlanRefresh = false - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !strings.Contains(run.Err.Error(), "refresh is currently not supported") { - t.Fatalf("expected a refresh error, got: %v", run.Err) - } -} - -func TestRemote_applyWithTarget(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - op := testOperationApply() - op.Module = mod - op.Targets = []string{"null_resource.foo"} - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "targeting is currently not supported") { - t.Fatalf("expected a targeting error, got: %v", run.Err) - } -} - -func TestRemote_applyWithVariables(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - op := testOperationApply() - op.Module = mod - op.Variables = map[string]interface{}{"foo": "bar"} - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !strings.Contains(run.Err.Error(), "variables are currently not supported") { - t.Fatalf("expected a variables error, got: %v", run.Err) - } -} - -func TestRemote_applyNoConfig(t *testing.T) { - b := testBackendDefault(t) - - op := testOperationApply() - op.Module = nil - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "configuration files found") { - t.Fatalf("expected configuration files error, got: %v", run.Err) - } -} - -func TestRemote_applyNoChanges(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-no-changes") - defer modCleanup() - - op := testOperationApply() - op.Module = mod - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") { - t.Fatalf("expected no changes in plan summery: %s", output) - } -} - -func TestRemote_applyNoApprove(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - input := testInput(t, map[string]string{ - "approve": "no", - }) - - op := testOperationApply() - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "Apply discarded") { - t.Fatalf("expected an apply discarded error, got: %v", run.Err) - } - if len(input.answers) > 0 { - t.Fatalf("expected no unused answers, got: %v", input.answers) - } -} - -func TestRemote_applyAutoApprove(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - input := testInput(t, map[string]string{ - "approve": "no", - }) - - op := testOperationApply() - op.AutoApprove = true - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } - - if len(input.answers) != 1 { - t.Fatalf("expected an unused answer, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") { - t.Fatalf("missing apply summery in output: %s", output) - } -} - -func TestRemote_applyWithAutoApply(t *testing.T) { - b := testBackendNoDefault(t) - - // Create a named workspace that auto applies. - _, err := b.client.Workspaces.Create( - context.Background(), - b.organization, - tfe.WorkspaceCreateOptions{ - AutoApply: tfe.Bool(true), - Name: tfe.String(b.prefix + "prod"), - }, - ) - if err != nil { - t.Fatalf("error creating named workspace: %v", err) - } - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - input := testInput(t, map[string]string{ - "approve": "yes", - }) - - op := testOperationApply() - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = "prod" - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } - - if len(input.answers) != 1 { - t.Fatalf("expected an unused answer, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") { - t.Fatalf("missing apply summery in output: %s", output) - } -} - -func TestRemote_applyLockTimeout(t *testing.T) { - b := testBackendDefault(t) - ctx := context.Background() - - // Retrieve the workspace used to run this operation in. - w, err := b.client.Workspaces.Read(ctx, b.organization, b.workspace) - if err != nil { - t.Fatalf("error retrieving workspace: %v", err) - } - - // Create a new configuration version. - c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{}) - if err != nil { - t.Fatalf("error creating configuration version: %v", err) - } - - // Create a pending run to block this run. - _, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{ - ConfigurationVersion: c, - Workspace: w, - }) - if err != nil { - t.Fatalf("error creating pending run: %v", err) - } - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply") - defer modCleanup() - - input := testInput(t, map[string]string{ - "cancel": "yes", - "approve": "yes", - }) - - op := testOperationApply() - op.StateLockTimeout = 5 * time.Second - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - _, err = b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - sigint := make(chan os.Signal, 1) - signal.Notify(sigint, syscall.SIGINT) - select { - case <-sigint: - // Stop redirecting SIGINT signals. - signal.Stop(sigint) - case <-time.After(10 * time.Second): - t.Fatalf("expected lock timeout after 5 seconds, waited 10 seconds") - } - - if len(input.answers) != 2 { - t.Fatalf("expected unused answers, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "Lock timeout exceeded") { - t.Fatalf("missing lock timout error in output: %s", output) - } - if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("unexpected plan summery in output: %s", output) - } - if strings.Contains(output, "1 added, 0 changed, 0 destroyed") { - t.Fatalf("unexpected apply summery in output: %s", output) - } -} - -func TestRemote_applyDestroy(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-destroy") - defer modCleanup() - - input := testInput(t, map[string]string{ - "approve": "yes", - }) - - op := testOperationApply() - op.Destroy = true - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } - - if len(input.answers) > 0 { - t.Fatalf("expected no unused answers, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "0 to add, 0 to change, 1 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "0 added, 0 changed, 1 destroyed") { - t.Fatalf("missing apply summery in output: %s", output) - } -} - -func TestRemote_applyDestroyNoConfig(t *testing.T) { - b := testBackendDefault(t) - - input := testInput(t, map[string]string{ - "approve": "yes", - }) - - op := testOperationApply() - op.Destroy = true - op.Module = nil - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("unexpected apply error: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } - - if len(input.answers) > 0 { - t.Fatalf("expected no unused answers, got: %v", input.answers) - } -} - -func TestRemote_applyPolicyPass(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-passed") - defer modCleanup() - - input := testInput(t, map[string]string{ - "approve": "yes", - }) - - op := testOperationApply() - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } - - if len(input.answers) > 0 { - t.Fatalf("expected no unused answers, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "Sentinel Result: true") { - t.Fatalf("missing polic check result in output: %s", output) - } - if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") { - t.Fatalf("missing apply summery in output: %s", output) - } -} - -func TestRemote_applyPolicyHardFail(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-hard-failed") - defer modCleanup() - - input := testInput(t, map[string]string{ - "approve": "yes", - }) - - op := testOperationApply() - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "hard failed") { - t.Fatalf("expected a policy check error, got: %v", run.Err) - } - if len(input.answers) != 1 { - t.Fatalf("expected an unused answers, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "Sentinel Result: false") { - t.Fatalf("missing policy check result in output: %s", output) - } - if strings.Contains(output, "1 added, 0 changed, 0 destroyed") { - t.Fatalf("unexpected apply summery in output: %s", output) - } -} - -func TestRemote_applyPolicySoftFail(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-soft-failed") - defer modCleanup() - - input := testInput(t, map[string]string{ - "override": "override", - "approve": "yes", - }) - - op := testOperationApply() - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } - - if len(input.answers) > 0 { - t.Fatalf("expected no unused answers, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "Sentinel Result: false") { - t.Fatalf("missing policy check result in output: %s", output) - } - if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") { - t.Fatalf("missing apply summery in output: %s", output) - } -} - -func TestRemote_applyPolicySoftFailAutoApprove(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-soft-failed") - defer modCleanup() - - input := testInput(t, map[string]string{ - "override": "override", - }) - - op := testOperationApply() - op.AutoApprove = true - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an apply error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "soft failed") { - t.Fatalf("expected a policy check error, got: %v", run.Err) - } - if len(input.answers) != 1 { - t.Fatalf("expected an unused answers, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "Sentinel Result: false") { - t.Fatalf("missing policy check result in output: %s", output) - } - if strings.Contains(output, "1 added, 0 changed, 0 destroyed") { - t.Fatalf("unexpected apply summery in output: %s", output) - } -} - -func TestRemote_applyPolicySoftFailAutoApply(t *testing.T) { - b := testBackendDefault(t) - - // Create a named workspace that auto applies. - _, err := b.client.Workspaces.Create( - context.Background(), - b.organization, - tfe.WorkspaceCreateOptions{ - AutoApply: tfe.Bool(true), - Name: tfe.String(b.prefix + "prod"), - }, - ) - if err != nil { - t.Fatalf("error creating named workspace: %v", err) - } - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-soft-failed") - defer modCleanup() - - input := testInput(t, map[string]string{ - "override": "override", - "approve": "yes", - }) - - op := testOperationApply() - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = "prod" - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } - - if len(input.answers) != 1 { - t.Fatalf("expected an unused answer, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "Sentinel Result: false") { - t.Fatalf("missing policy check result in output: %s", output) - } - if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") { - t.Fatalf("missing apply summery in output: %s", output) - } -} - -func TestRemote_applyWithRemoteError(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-with-error") - defer modCleanup() - - op := testOperationApply() - op.Module = mod - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.ExitCode != 1 { - t.Fatalf("expected exit code 1, got %d", run.ExitCode) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "null_resource.foo: 1 error") { - t.Fatalf("missing apply error in output: %s", output) - } -} diff --git a/backend/remote/backend_common.go b/backend/remote/backend_common.go deleted file mode 100644 index 176e5ea02..000000000 --- a/backend/remote/backend_common.go +++ /dev/null @@ -1,305 +0,0 @@ -package remote - -import ( - "bufio" - "context" - "errors" - "fmt" - "math" - "time" - - tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/terraform" -) - -// backoff will perform exponential backoff based on the iteration and -// limited by the provided min and max (in milliseconds) durations. -func backoff(min, max float64, iter int) time.Duration { - backoff := math.Pow(2, float64(iter)/5) * min - if backoff > max { - backoff = max - } - return time.Duration(backoff) * time.Millisecond -} - -func (b *Remote) waitForRun(stopCtx, cancelCtx context.Context, op *backend.Operation, opType string, r *tfe.Run, w *tfe.Workspace) (*tfe.Run, error) { - started := time.Now() - updated := started - for i := 0; ; i++ { - select { - case <-stopCtx.Done(): - return r, stopCtx.Err() - case <-cancelCtx.Done(): - return r, cancelCtx.Err() - case <-time.After(backoff(1000, 3000, i)): - // Timer up, show status - } - - // Retrieve the run to get its current status. - r, err := b.client.Runs.Read(stopCtx, r.ID) - if err != nil { - return r, generalError("error retrieving run", err) - } - - // Return if the run is no longer pending. - if r.Status != tfe.RunPending && r.Status != tfe.RunConfirmed { - if i == 0 && opType == "plan" && b.CLI != nil { - b.CLI.Output(b.Colorize().Color(fmt.Sprintf("Waiting for the %s to start...\n", opType))) - } - if i > 0 && b.CLI != nil { - // Insert a blank line to separate the ouputs. - b.CLI.Output("") - } - return r, nil - } - - // Check if 30 seconds have passed since the last update. - current := time.Now() - if b.CLI != nil && (i == 0 || current.Sub(updated).Seconds() > 30) { - updated = current - position := 0 - elapsed := "" - - // Calculate and set the elapsed time. - if i > 0 { - elapsed = fmt.Sprintf( - " (%s elapsed)", current.Sub(started).Truncate(30*time.Second)) - } - - // Retrieve the workspace used to run this operation in. - w, err = b.client.Workspaces.Read(stopCtx, b.organization, w.Name) - if err != nil { - return nil, generalError("error retrieving workspace", err) - } - - // If the workspace is locked the run will not be queued and we can - // update the status without making any expensive calls. - if w.Locked && w.CurrentRun != nil { - cr, err := b.client.Runs.Read(stopCtx, w.CurrentRun.ID) - if err != nil { - return r, generalError("error retrieving current run", err) - } - if cr.Status == tfe.RunPending { - b.CLI.Output(b.Colorize().Color( - "Waiting for the manually locked workspace to be unlocked..." + elapsed)) - continue - } - } - - // Skip checking the workspace queue when we are the current run. - if w.CurrentRun == nil || w.CurrentRun.ID != r.ID { - found := false - options := tfe.RunListOptions{} - runlist: - for { - rl, err := b.client.Runs.List(stopCtx, w.ID, options) - if err != nil { - return r, generalError("error retrieving run list", err) - } - - // Loop through all runs to calculate the workspace queue position. - for _, item := range rl.Items { - if !found { - if r.ID == item.ID { - found = true - } - continue - } - - // If the run is in a final state, ignore it and continue. - switch item.Status { - case tfe.RunApplied, tfe.RunCanceled, tfe.RunDiscarded, tfe.RunErrored: - continue - case tfe.RunPlanned: - if op.Type == backend.OperationTypePlan { - continue - } - } - - // Increase the workspace queue position. - position++ - - // Stop searching when we reached the current run. - if w.CurrentRun != nil && w.CurrentRun.ID == item.ID { - break runlist - } - } - - // Exit the loop when we've seen all pages. - if rl.CurrentPage >= rl.TotalPages { - break - } - - // Update the page number to get the next page. - options.PageNumber = rl.NextPage - } - - if position > 0 { - b.CLI.Output(b.Colorize().Color(fmt.Sprintf( - "Waiting for %d run(s) to finish before being queued...%s", - position, - elapsed, - ))) - continue - } - } - - options := tfe.RunQueueOptions{} - search: - for { - rq, err := b.client.Organizations.RunQueue(stopCtx, b.organization, options) - if err != nil { - return r, generalError("error retrieving queue", err) - } - - // Search through all queued items to find our run. - for _, item := range rq.Items { - if r.ID == item.ID { - position = item.PositionInQueue - break search - } - } - - // Exit the loop when we've seen all pages. - if rq.CurrentPage >= rq.TotalPages { - break - } - - // Update the page number to get the next page. - options.PageNumber = rq.NextPage - } - - if position > 0 { - c, err := b.client.Organizations.Capacity(stopCtx, b.organization) - if err != nil { - return r, generalError("error retrieving capacity", err) - } - b.CLI.Output(b.Colorize().Color(fmt.Sprintf( - "Waiting for %d queued run(s) to finish before starting...%s", - position-c.Running, - elapsed, - ))) - continue - } - - b.CLI.Output(b.Colorize().Color(fmt.Sprintf( - "Waiting for the %s to start...%s", opType, elapsed))) - } - } -} - -func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error { - if b.CLI != nil { - b.CLI.Output("\n------------------------------------------------------------------------\n") - } - for i, pc := range r.PolicyChecks { - logs, err := b.client.PolicyChecks.Logs(stopCtx, pc.ID) - if err != nil { - return generalError("error retrieving policy check logs", err) - } - scanner := bufio.NewScanner(logs) - - // Retrieve the policy check to get its current status. - pc, err := b.client.PolicyChecks.Read(stopCtx, pc.ID) - if err != nil { - return generalError("error retrieving policy check", err) - } - - var msgPrefix string - switch pc.Scope { - case tfe.PolicyScopeOrganization: - msgPrefix = "Organization policy check" - case tfe.PolicyScopeWorkspace: - msgPrefix = "Workspace policy check" - default: - msgPrefix = fmt.Sprintf("Unknown policy check (%s)", pc.Scope) - } - - if b.CLI != nil { - b.CLI.Output(b.Colorize().Color(msgPrefix + ":\n")) - } - - for scanner.Scan() { - if b.CLI != nil { - b.CLI.Output(b.Colorize().Color(scanner.Text())) - } - } - if err := scanner.Err(); err != nil { - return generalError("error reading logs", err) - } - - switch pc.Status { - case tfe.PolicyPasses: - if (op.Type == backend.OperationTypeApply || i < len(r.PolicyChecks)-1) && b.CLI != nil { - b.CLI.Output("\n------------------------------------------------------------------------") - } - continue - case tfe.PolicyErrored: - return fmt.Errorf(msgPrefix + " errored.") - case tfe.PolicyHardFailed: - return fmt.Errorf(msgPrefix + " hard failed.") - case tfe.PolicySoftFailed: - if op.Type == backend.OperationTypePlan || op.UIOut == nil || op.UIIn == nil || - op.AutoApprove || !pc.Actions.IsOverridable || !pc.Permissions.CanOverride { - return fmt.Errorf(msgPrefix + " soft failed.") - } - default: - return fmt.Errorf("Unknown or unexpected policy state: %s", pc.Status) - } - - opts := &terraform.InputOpts{ - Id: "override", - Query: "\nDo you want to override the soft failed policy check?", - Description: "Only 'override' will be accepted to override.", - } - - if err = b.confirm(stopCtx, op, opts, r, "override"); err != nil { - return err - } - - if _, err = b.client.PolicyChecks.Override(stopCtx, pc.ID); err != nil { - return generalError("error overriding policy check", err) - } - - if b.CLI != nil { - b.CLI.Output("------------------------------------------------------------------------") - } - } - - return nil -} - -func (b *Remote) confirm(stopCtx context.Context, op *backend.Operation, opts *terraform.InputOpts, r *tfe.Run, keyword string) error { - v, err := op.UIIn.Input(opts) - if err != nil { - return fmt.Errorf("Error asking %s: %v", opts.Id, err) - } - if v != keyword { - // Retrieve the run again to get its current status. - r, err = b.client.Runs.Read(stopCtx, r.ID) - if err != nil { - return generalError("error retrieving run", err) - } - - // Make sure we discard the run if possible. - if r.Actions.IsDiscardable { - err = b.client.Runs.Discard(stopCtx, r.ID, tfe.RunDiscardOptions{}) - if err != nil { - if op.Destroy { - return generalError("error disarding destroy", err) - } - return generalError("error disarding apply", err) - } - } - - // Even if the run was disarding successfully, we still - // return an error as the apply command was cancelled. - if op.Destroy { - return errors.New("Destroy discarded.") - } - return errors.New("Apply discarded.") - } - - return nil -} diff --git a/backend/remote/backend_mock.go b/backend/remote/backend_mock.go deleted file mode 100644 index eac6b6839..000000000 --- a/backend/remote/backend_mock.go +++ /dev/null @@ -1,998 +0,0 @@ -package remote - -import ( - "bytes" - "context" - "encoding/base64" - "errors" - "fmt" - "io" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "strings" - "time" - - tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/terraform" -) - -type mockClient struct { - Applies *mockApplies - ConfigurationVersions *mockConfigurationVersions - Organizations *mockOrganizations - Plans *mockPlans - PolicyChecks *mockPolicyChecks - Runs *mockRuns - StateVersions *mockStateVersions - Workspaces *mockWorkspaces -} - -func newMockClient() *mockClient { - c := &mockClient{} - c.Applies = newMockApplies(c) - c.ConfigurationVersions = newMockConfigurationVersions(c) - c.Organizations = newMockOrganizations(c) - c.Plans = newMockPlans(c) - c.PolicyChecks = newMockPolicyChecks(c) - c.Runs = newMockRuns(c) - c.StateVersions = newMockStateVersions(c) - c.Workspaces = newMockWorkspaces(c) - return c -} - -type mockApplies struct { - client *mockClient - applies map[string]*tfe.Apply - logs map[string]string -} - -func newMockApplies(client *mockClient) *mockApplies { - return &mockApplies{ - client: client, - applies: make(map[string]*tfe.Apply), - logs: make(map[string]string), - } -} - -// create is a helper function to create a mock apply that uses the configured -// working directory to find the logfile. -func (m *mockApplies) create(cvID, workspaceID string) (*tfe.Apply, error) { - c, ok := m.client.ConfigurationVersions.configVersions[cvID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - if c.Speculative { - // Speculative means its plan-only so we don't create a Apply. - return nil, nil - } - - id := generateID("apply-") - url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) - - a := &tfe.Apply{ - ID: id, - LogReadURL: url, - Status: tfe.ApplyPending, - } - - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if w.AutoApply { - a.Status = tfe.ApplyRunning - } - - m.logs[url] = filepath.Join( - m.client.ConfigurationVersions.uploadPaths[cvID], - w.WorkingDirectory, - "apply.log", - ) - m.applies[a.ID] = a - - return a, nil -} - -func (m *mockApplies) Read(ctx context.Context, applyID string) (*tfe.Apply, error) { - a, ok := m.applies[applyID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - // Together with the mockLogReader this allows testing queued runs. - if a.Status == tfe.ApplyRunning { - a.Status = tfe.ApplyFinished - } - return a, nil -} - -func (m *mockApplies) Logs(ctx context.Context, applyID string) (io.Reader, error) { - a, err := m.Read(ctx, applyID) - if err != nil { - return nil, err - } - - logfile, ok := m.logs[a.LogReadURL] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return bytes.NewBufferString("logfile does not exist"), nil - } - - logs, err := ioutil.ReadFile(logfile) - if err != nil { - return nil, err - } - - done := func() (bool, error) { - a, err := m.Read(ctx, applyID) - if err != nil { - return false, err - } - if a.Status != tfe.ApplyFinished { - return false, nil - } - return true, nil - } - - return &mockLogReader{ - done: done, - logs: bytes.NewBuffer(logs), - }, nil -} - -type mockConfigurationVersions struct { - client *mockClient - configVersions map[string]*tfe.ConfigurationVersion - uploadPaths map[string]string - uploadURLs map[string]*tfe.ConfigurationVersion -} - -func newMockConfigurationVersions(client *mockClient) *mockConfigurationVersions { - return &mockConfigurationVersions{ - client: client, - configVersions: make(map[string]*tfe.ConfigurationVersion), - uploadPaths: make(map[string]string), - uploadURLs: make(map[string]*tfe.ConfigurationVersion), - } -} - -func (m *mockConfigurationVersions) List(ctx context.Context, workspaceID string, options tfe.ConfigurationVersionListOptions) (*tfe.ConfigurationVersionList, error) { - cvl := &tfe.ConfigurationVersionList{} - for _, cv := range m.configVersions { - cvl.Items = append(cvl.Items, cv) - } - - cvl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(cvl.Items), - } - - return cvl, nil -} - -func (m *mockConfigurationVersions) Create(ctx context.Context, workspaceID string, options tfe.ConfigurationVersionCreateOptions) (*tfe.ConfigurationVersion, error) { - id := generateID("cv-") - url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) - - cv := &tfe.ConfigurationVersion{ - ID: id, - Status: tfe.ConfigurationPending, - UploadURL: url, - } - - m.configVersions[cv.ID] = cv - m.uploadURLs[url] = cv - - return cv, nil -} - -func (m *mockConfigurationVersions) Read(ctx context.Context, cvID string) (*tfe.ConfigurationVersion, error) { - cv, ok := m.configVersions[cvID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return cv, nil -} - -func (m *mockConfigurationVersions) Upload(ctx context.Context, url, path string) error { - cv, ok := m.uploadURLs[url] - if !ok { - return errors.New("404 not found") - } - m.uploadPaths[cv.ID] = path - cv.Status = tfe.ConfigurationUploaded - return nil -} - -// mockInput is a mock implementation of terraform.UIInput. -type mockInput struct { - answers map[string]string -} - -func (m *mockInput) Input(opts *terraform.InputOpts) (string, error) { - v, ok := m.answers[opts.Id] - if !ok { - return "", fmt.Errorf("unexpected input request in test: %s", opts.Id) - } - delete(m.answers, opts.Id) - return v, nil -} - -type mockOrganizations struct { - client *mockClient - organizations map[string]*tfe.Organization -} - -func newMockOrganizations(client *mockClient) *mockOrganizations { - return &mockOrganizations{ - client: client, - organizations: make(map[string]*tfe.Organization), - } -} - -func (m *mockOrganizations) List(ctx context.Context, options tfe.OrganizationListOptions) (*tfe.OrganizationList, error) { - orgl := &tfe.OrganizationList{} - for _, org := range m.organizations { - orgl.Items = append(orgl.Items, org) - } - - orgl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(orgl.Items), - } - - return orgl, nil -} - -// mockLogReader is a mock logreader that enables testing queued runs. -type mockLogReader struct { - done func() (bool, error) - logs *bytes.Buffer -} - -func (m *mockLogReader) Read(l []byte) (int, error) { - for { - if written, err := m.read(l); err != io.ErrNoProgress { - return written, err - } - time.Sleep(500 * time.Millisecond) - } -} - -func (m *mockLogReader) read(l []byte) (int, error) { - done, err := m.done() - if err != nil { - return 0, err - } - if !done { - return 0, io.ErrNoProgress - } - return m.logs.Read(l) -} - -func (m *mockOrganizations) Create(ctx context.Context, options tfe.OrganizationCreateOptions) (*tfe.Organization, error) { - org := &tfe.Organization{Name: *options.Name} - m.organizations[org.Name] = org - return org, nil -} - -func (m *mockOrganizations) Read(ctx context.Context, name string) (*tfe.Organization, error) { - org, ok := m.organizations[name] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return org, nil -} - -func (m *mockOrganizations) Update(ctx context.Context, name string, options tfe.OrganizationUpdateOptions) (*tfe.Organization, error) { - org, ok := m.organizations[name] - if !ok { - return nil, tfe.ErrResourceNotFound - } - org.Name = *options.Name - return org, nil - -} - -func (m *mockOrganizations) Delete(ctx context.Context, name string) error { - delete(m.organizations, name) - return nil -} - -func (m *mockOrganizations) Capacity(ctx context.Context, name string) (*tfe.Capacity, error) { - var pending, running int - for _, r := range m.client.Runs.runs { - if r.Status == tfe.RunPending { - pending++ - continue - } - running++ - } - return &tfe.Capacity{Pending: pending, Running: running}, nil -} - -func (m *mockOrganizations) RunQueue(ctx context.Context, name string, options tfe.RunQueueOptions) (*tfe.RunQueue, error) { - rq := &tfe.RunQueue{} - - for _, r := range m.client.Runs.runs { - rq.Items = append(rq.Items, r) - } - - rq.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(rq.Items), - } - - return rq, nil -} - -type mockPlans struct { - client *mockClient - logs map[string]string - plans map[string]*tfe.Plan -} - -func newMockPlans(client *mockClient) *mockPlans { - return &mockPlans{ - client: client, - logs: make(map[string]string), - plans: make(map[string]*tfe.Plan), - } -} - -// create is a helper function to create a mock plan that uses the configured -// working directory to find the logfile. -func (m *mockPlans) create(cvID, workspaceID string) (*tfe.Plan, error) { - id := generateID("plan-") - url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) - - p := &tfe.Plan{ - ID: id, - LogReadURL: url, - Status: tfe.PlanPending, - } - - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - m.logs[url] = filepath.Join( - m.client.ConfigurationVersions.uploadPaths[cvID], - w.WorkingDirectory, - "plan.log", - ) - m.plans[p.ID] = p - - return p, nil -} - -func (m *mockPlans) Read(ctx context.Context, planID string) (*tfe.Plan, error) { - p, ok := m.plans[planID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - // Together with the mockLogReader this allows testing queued runs. - if p.Status == tfe.PlanRunning { - p.Status = tfe.PlanFinished - } - return p, nil -} - -func (m *mockPlans) Logs(ctx context.Context, planID string) (io.Reader, error) { - p, err := m.Read(ctx, planID) - if err != nil { - return nil, err - } - - logfile, ok := m.logs[p.LogReadURL] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return bytes.NewBufferString("logfile does not exist"), nil - } - - logs, err := ioutil.ReadFile(logfile) - if err != nil { - return nil, err - } - - done := func() (bool, error) { - p, err := m.Read(ctx, planID) - if err != nil { - return false, err - } - if p.Status != tfe.PlanFinished { - return false, nil - } - return true, nil - } - - return &mockLogReader{ - done: done, - logs: bytes.NewBuffer(logs), - }, nil -} - -type mockPolicyChecks struct { - client *mockClient - checks map[string]*tfe.PolicyCheck - logs map[string]string -} - -func newMockPolicyChecks(client *mockClient) *mockPolicyChecks { - return &mockPolicyChecks{ - client: client, - checks: make(map[string]*tfe.PolicyCheck), - logs: make(map[string]string), - } -} - -// create is a helper function to create a mock policy check that uses the -// configured working directory to find the logfile. -func (m *mockPolicyChecks) create(cvID, workspaceID string) (*tfe.PolicyCheck, error) { - id := generateID("pc-") - - pc := &tfe.PolicyCheck{ - ID: id, - Actions: &tfe.PolicyActions{}, - Permissions: &tfe.PolicyPermissions{}, - Scope: tfe.PolicyScopeOrganization, - Status: tfe.PolicyPending, - } - - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - logfile := filepath.Join( - m.client.ConfigurationVersions.uploadPaths[cvID], - w.WorkingDirectory, - "policy.log", - ) - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return nil, nil - } - - m.logs[pc.ID] = logfile - m.checks[pc.ID] = pc - - return pc, nil -} - -func (m *mockPolicyChecks) List(ctx context.Context, runID string, options tfe.PolicyCheckListOptions) (*tfe.PolicyCheckList, error) { - _, ok := m.client.Runs.runs[runID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - pcl := &tfe.PolicyCheckList{} - for _, pc := range m.checks { - pcl.Items = append(pcl.Items, pc) - } - - pcl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(pcl.Items), - } - - return pcl, nil -} - -func (m *mockPolicyChecks) Read(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { - pc, ok := m.checks[policyCheckID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - logfile, ok := m.logs[pc.ID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return nil, fmt.Errorf("logfile does not exist") - } - - logs, err := ioutil.ReadFile(logfile) - if err != nil { - return nil, err - } - - switch { - case bytes.Contains(logs, []byte("Sentinel Result: true")): - pc.Status = tfe.PolicyPasses - case bytes.Contains(logs, []byte("Sentinel Result: false")): - switch { - case bytes.Contains(logs, []byte("hard-mandatory")): - pc.Status = tfe.PolicyHardFailed - case bytes.Contains(logs, []byte("soft-mandatory")): - pc.Actions.IsOverridable = true - pc.Permissions.CanOverride = true - pc.Status = tfe.PolicySoftFailed - } - default: - // As this is an unexpected state, we say the policy errored. - pc.Status = tfe.PolicyErrored - } - - return pc, nil -} - -func (m *mockPolicyChecks) Override(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { - pc, ok := m.checks[policyCheckID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - pc.Status = tfe.PolicyOverridden - return pc, nil -} - -func (m *mockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) { - pc, ok := m.checks[policyCheckID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - logfile, ok := m.logs[pc.ID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return bytes.NewBufferString("logfile does not exist"), nil - } - - logs, err := ioutil.ReadFile(logfile) - if err != nil { - return nil, err - } - - switch { - case bytes.Contains(logs, []byte("Sentinel Result: true")): - pc.Status = tfe.PolicyPasses - case bytes.Contains(logs, []byte("Sentinel Result: false")): - switch { - case bytes.Contains(logs, []byte("hard-mandatory")): - pc.Status = tfe.PolicyHardFailed - case bytes.Contains(logs, []byte("soft-mandatory")): - pc.Actions.IsOverridable = true - pc.Permissions.CanOverride = true - pc.Status = tfe.PolicySoftFailed - } - default: - // As this is an unexpected state, we say the policy errored. - pc.Status = tfe.PolicyErrored - } - - return bytes.NewBuffer(logs), nil -} - -type mockRuns struct { - client *mockClient - runs map[string]*tfe.Run - workspaces map[string][]*tfe.Run -} - -func newMockRuns(client *mockClient) *mockRuns { - return &mockRuns{ - client: client, - runs: make(map[string]*tfe.Run), - workspaces: make(map[string][]*tfe.Run), - } -} - -func (m *mockRuns) List(ctx context.Context, workspaceID string, options tfe.RunListOptions) (*tfe.RunList, error) { - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - rl := &tfe.RunList{} - for _, r := range m.workspaces[w.ID] { - rl.Items = append(rl.Items, r) - } - - rl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(rl.Items), - } - - return rl, nil -} - -func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*tfe.Run, error) { - a, err := m.client.Applies.create(options.ConfigurationVersion.ID, options.Workspace.ID) - if err != nil { - return nil, err - } - - p, err := m.client.Plans.create(options.ConfigurationVersion.ID, options.Workspace.ID) - if err != nil { - return nil, err - } - - pc, err := m.client.PolicyChecks.create(options.ConfigurationVersion.ID, options.Workspace.ID) - if err != nil { - return nil, err - } - - r := &tfe.Run{ - ID: generateID("run-"), - Actions: &tfe.RunActions{IsCancelable: true}, - Apply: a, - HasChanges: false, - Permissions: &tfe.RunPermissions{}, - Plan: p, - Status: tfe.RunPending, - } - - if pc != nil { - r.PolicyChecks = []*tfe.PolicyCheck{pc} - } - - if options.IsDestroy != nil { - r.IsDestroy = *options.IsDestroy - } - - w, ok := m.client.Workspaces.workspaceIDs[options.Workspace.ID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - if w.CurrentRun == nil { - w.CurrentRun = r - } - - m.runs[r.ID] = r - m.workspaces[options.Workspace.ID] = append(m.workspaces[options.Workspace.ID], r) - - return r, nil -} - -func (m *mockRuns) Read(ctx context.Context, runID string) (*tfe.Run, error) { - r, ok := m.runs[runID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - pending := false - for _, r := range m.runs { - if r.ID != runID && r.Status == tfe.RunPending { - pending = true - break - } - } - - if !pending && r.Status == tfe.RunPending { - // Only update the status if there are no other pending runs. - r.Status = tfe.RunPlanning - r.Plan.Status = tfe.PlanRunning - } - - logs, _ := ioutil.ReadFile(m.client.Plans.logs[r.Plan.LogReadURL]) - if r.Plan.Status == tfe.PlanFinished { - if r.IsDestroy || bytes.Contains(logs, []byte("1 to add, 0 to change, 0 to destroy")) { - r.Actions.IsCancelable = false - r.Actions.IsConfirmable = true - r.HasChanges = true - r.Permissions.CanApply = true - } - - if bytes.Contains(logs, []byte("null_resource.foo: 1 error")) { - r.Actions.IsCancelable = false - r.HasChanges = false - r.Status = tfe.RunErrored - } - } - - return r, nil -} - -func (m *mockRuns) Apply(ctx context.Context, runID string, options tfe.RunApplyOptions) error { - r, ok := m.runs[runID] - if !ok { - return tfe.ErrResourceNotFound - } - if r.Status != tfe.RunPending { - // Only update the status if the run is not pending anymore. - r.Status = tfe.RunApplying - r.Apply.Status = tfe.ApplyRunning - } - return nil -} - -func (m *mockRuns) Cancel(ctx context.Context, runID string, options tfe.RunCancelOptions) error { - panic("not implemented") -} - -func (m *mockRuns) ForceCancel(ctx context.Context, runID string, options tfe.RunForceCancelOptions) error { - panic("not implemented") -} - -func (m *mockRuns) Discard(ctx context.Context, runID string, options tfe.RunDiscardOptions) error { - panic("not implemented") -} - -type mockStateVersions struct { - client *mockClient - states map[string][]byte - stateVersions map[string]*tfe.StateVersion - workspaces map[string][]string -} - -func newMockStateVersions(client *mockClient) *mockStateVersions { - return &mockStateVersions{ - client: client, - states: make(map[string][]byte), - stateVersions: make(map[string]*tfe.StateVersion), - workspaces: make(map[string][]string), - } -} - -func (m *mockStateVersions) List(ctx context.Context, options tfe.StateVersionListOptions) (*tfe.StateVersionList, error) { - svl := &tfe.StateVersionList{} - for _, sv := range m.stateVersions { - svl.Items = append(svl.Items, sv) - } - - svl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(svl.Items), - } - - return svl, nil -} - -func (m *mockStateVersions) Create(ctx context.Context, workspaceID string, options tfe.StateVersionCreateOptions) (*tfe.StateVersion, error) { - id := generateID("sv-") - runID := os.Getenv("TFE_RUN_ID") - url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) - - if runID != "" && (options.Run == nil || runID != options.Run.ID) { - return nil, fmt.Errorf("option.Run.ID does not contain the ID exported by TFE_RUN_ID") - } - - sv := &tfe.StateVersion{ - ID: id, - DownloadURL: url, - Serial: *options.Serial, - } - - state, err := base64.StdEncoding.DecodeString(*options.State) - if err != nil { - return nil, err - } - - m.states[sv.DownloadURL] = state - m.stateVersions[sv.ID] = sv - m.workspaces[workspaceID] = append(m.workspaces[workspaceID], sv.ID) - - return sv, nil -} - -func (m *mockStateVersions) Read(ctx context.Context, svID string) (*tfe.StateVersion, error) { - sv, ok := m.stateVersions[svID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return sv, nil -} - -func (m *mockStateVersions) Current(ctx context.Context, workspaceID string) (*tfe.StateVersion, error) { - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - svs, ok := m.workspaces[w.ID] - if !ok || len(svs) == 0 { - return nil, tfe.ErrResourceNotFound - } - - sv, ok := m.stateVersions[svs[len(svs)-1]] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - return sv, nil -} - -func (m *mockStateVersions) Download(ctx context.Context, url string) ([]byte, error) { - state, ok := m.states[url] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return state, nil -} - -type mockWorkspaces struct { - client *mockClient - workspaceIDs map[string]*tfe.Workspace - workspaceNames map[string]*tfe.Workspace -} - -func newMockWorkspaces(client *mockClient) *mockWorkspaces { - return &mockWorkspaces{ - client: client, - workspaceIDs: make(map[string]*tfe.Workspace), - workspaceNames: make(map[string]*tfe.Workspace), - } -} - -func (m *mockWorkspaces) List(ctx context.Context, organization string, options tfe.WorkspaceListOptions) (*tfe.WorkspaceList, error) { - dummyWorkspaces := 10 - wl := &tfe.WorkspaceList{} - - // Get the prefix from the search options. - prefix := "" - if options.Search != nil { - prefix = *options.Search - } - - // Get all the workspaces that match the prefix. - var ws []*tfe.Workspace - for _, w := range m.workspaceIDs { - if strings.HasPrefix(w.Name, prefix) { - ws = append(ws, w) - } - } - - // Return an empty result if we have no matches. - if len(ws) == 0 { - wl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - } - return wl, nil - } - - // Return dummy workspaces for the first page to test pagination. - if options.PageNumber <= 1 { - for i := 0; i < dummyWorkspaces; i++ { - wl.Items = append(wl.Items, &tfe.Workspace{ - ID: generateID("ws-"), - Name: fmt.Sprintf("dummy-workspace-%d", i), - }) - } - - wl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 2, - TotalPages: 2, - TotalCount: len(wl.Items) + len(ws), - } - - return wl, nil - } - - // Return the actual workspaces that matched as the second page. - wl.Items = ws - wl.Pagination = &tfe.Pagination{ - CurrentPage: 2, - PreviousPage: 1, - TotalPages: 2, - TotalCount: len(wl.Items) + dummyWorkspaces, - } - - return wl, nil -} - -func (m *mockWorkspaces) Create(ctx context.Context, organization string, options tfe.WorkspaceCreateOptions) (*tfe.Workspace, error) { - w := &tfe.Workspace{ - ID: generateID("ws-"), - Name: *options.Name, - Permissions: &tfe.WorkspacePermissions{ - CanQueueRun: true, - CanUpdate: true, - }, - } - if options.AutoApply != nil { - w.AutoApply = *options.AutoApply - } - if options.VCSRepo != nil { - w.VCSRepo = &tfe.VCSRepo{} - } - m.workspaceIDs[w.ID] = w - m.workspaceNames[w.Name] = w - return w, nil -} - -func (m *mockWorkspaces) Read(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) { - w, ok := m.workspaceNames[workspace] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return w, nil -} - -func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { - w, ok := m.workspaceNames[workspace] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if options.Name != nil { - w.Name = *options.Name - } - if options.TerraformVersion != nil { - w.TerraformVersion = *options.TerraformVersion - } - if options.WorkingDirectory != nil { - w.WorkingDirectory = *options.WorkingDirectory - } - - delete(m.workspaceNames, workspace) - m.workspaceNames[w.Name] = w - - return w, nil -} - -func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace string) error { - if w, ok := m.workspaceNames[workspace]; ok { - delete(m.workspaceIDs, w.ID) - } - delete(m.workspaceNames, workspace) - return nil -} - -func (m *mockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) { - w, ok := m.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - w.Locked = true - return w, nil -} - -func (m *mockWorkspaces) Unlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { - w, ok := m.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - w.Locked = false - return w, nil -} - -func (m *mockWorkspaces) AssignSSHKey(ctx context.Context, workspaceID string, options tfe.WorkspaceAssignSSHKeyOptions) (*tfe.Workspace, error) { - panic("not implemented") -} - -func (m *mockWorkspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { - panic("not implemented") -} - -const alphanumeric = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - -func generateID(s string) string { - b := make([]byte, 16) - for i := range b { - b[i] = alphanumeric[rand.Intn(len(alphanumeric))] - } - return s + string(b) -} diff --git a/backend/remote/backend_plan.go b/backend/remote/backend_plan.go deleted file mode 100644 index 621932334..000000000 --- a/backend/remote/backend_plan.go +++ /dev/null @@ -1,319 +0,0 @@ -package remote - -import ( - "bufio" - "context" - "errors" - "fmt" - "io/ioutil" - "log" - "os" - "path/filepath" - "strings" - "syscall" - "time" - - tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/backend" -) - -func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operation) (*tfe.Run, error) { - log.Printf("[INFO] backend/remote: starting Plan operation") - - // Retrieve the workspace used to run this operation in. - w, err := b.client.Workspaces.Read(stopCtx, b.organization, op.Workspace) - if err != nil { - return nil, generalError("error retrieving workspace", err) - } - - if !w.Permissions.CanQueueRun { - return nil, fmt.Errorf(strings.TrimSpace(fmt.Sprintf(planErrNoQueueRunRights))) - } - - if op.ModuleDepth != defaultModuleDepth { - return nil, fmt.Errorf(strings.TrimSpace(planErrModuleDepthNotSupported)) - } - - if op.Parallelism != defaultParallelism { - return nil, fmt.Errorf(strings.TrimSpace(planErrParallelismNotSupported)) - } - - if op.Plan != nil { - return nil, fmt.Errorf(strings.TrimSpace(planErrPlanNotSupported)) - } - - if op.PlanOutPath != "" { - return nil, fmt.Errorf(strings.TrimSpace(planErrOutPathNotSupported)) - } - - if !op.PlanRefresh { - return nil, fmt.Errorf(strings.TrimSpace(planErrNoRefreshNotSupported)) - } - - if op.Targets != nil { - return nil, fmt.Errorf(strings.TrimSpace(planErrTargetsNotSupported)) - } - - if op.Variables != nil { - return nil, fmt.Errorf(strings.TrimSpace( - fmt.Sprintf(planErrVariablesNotSupported, b.hostname, b.organization, op.Workspace))) - } - - if (op.Module == nil || op.Module.Config().Dir == "") && !op.Destroy { - return nil, fmt.Errorf(strings.TrimSpace(planErrNoConfig)) - } - - return b.plan(stopCtx, cancelCtx, op, w) -} - -func (b *Remote) plan(stopCtx, cancelCtx context.Context, op *backend.Operation, w *tfe.Workspace) (*tfe.Run, error) { - configOptions := tfe.ConfigurationVersionCreateOptions{ - AutoQueueRuns: tfe.Bool(false), - Speculative: tfe.Bool(op.Type == backend.OperationTypePlan), - } - - cv, err := b.client.ConfigurationVersions.Create(stopCtx, w.ID, configOptions) - if err != nil { - return nil, generalError("error creating configuration version", err) - } - - var configDir string - if op.Module != nil && op.Module.Config().Dir != "" { - // Make sure to take the working directory into account by removing - // the working directory from the current path. This will result in - // a path that points to the expected root of the workspace. - configDir = filepath.Clean(strings.TrimSuffix( - filepath.Clean(op.Module.Config().Dir), - filepath.Clean(w.WorkingDirectory), - )) - } else { - // We did a check earlier to make sure we either have a config dir, - // or the plan is run with -destroy. So this else clause will only - // be executed when we are destroying and doesn't need the config. - configDir, err = ioutil.TempDir("", "tf") - if err != nil { - return nil, generalError("error creating temporary directory", err) - } - defer os.RemoveAll(configDir) - - // Make sure the configured working directory exists. - err = os.MkdirAll(filepath.Join(configDir, w.WorkingDirectory), 0700) - if err != nil { - return nil, generalError( - "error creating temporary working directory", err) - } - } - - err = b.client.ConfigurationVersions.Upload(stopCtx, cv.UploadURL, configDir) - if err != nil { - return nil, generalError("error uploading configuration files", err) - } - - uploaded := false - for i := 0; i < 60 && !uploaded; i++ { - select { - case <-stopCtx.Done(): - return nil, context.Canceled - case <-cancelCtx.Done(): - return nil, context.Canceled - case <-time.After(500 * time.Millisecond): - cv, err = b.client.ConfigurationVersions.Read(stopCtx, cv.ID) - if err != nil { - return nil, generalError("error retrieving configuration version", err) - } - - if cv.Status == tfe.ConfigurationUploaded { - uploaded = true - } - } - } - - if !uploaded { - return nil, generalError( - "error uploading configuration files", errors.New("operation timed out")) - } - - runOptions := tfe.RunCreateOptions{ - IsDestroy: tfe.Bool(op.Destroy), - Message: tfe.String("Queued manually using Terraform"), - ConfigurationVersion: cv, - Workspace: w, - } - - r, err := b.client.Runs.Create(stopCtx, runOptions) - if err != nil { - return r, generalError("error creating run", err) - } - - // When the lock timeout is set, - if op.StateLockTimeout > 0 { - go func() { - select { - case <-stopCtx.Done(): - return - case <-cancelCtx.Done(): - return - case <-time.After(op.StateLockTimeout): - // Retrieve the run to get its current status. - r, err := b.client.Runs.Read(cancelCtx, r.ID) - if err != nil { - log.Printf("[ERROR] error reading run: %v", err) - return - } - - if r.Status == tfe.RunPending && r.Actions.IsCancelable { - if b.CLI != nil { - b.CLI.Output(b.Colorize().Color(strings.TrimSpace(lockTimeoutErr))) - } - - // We abuse the auto aprove flag to indicate that we do not - // want to ask if the remote operation should be canceled. - op.AutoApprove = true - - p, err := os.FindProcess(os.Getpid()) - if err != nil { - log.Printf("[ERROR] error searching process ID: %v", err) - return - } - p.Signal(syscall.SIGINT) - } - } - }() - } - - if b.CLI != nil { - header := planDefaultHeader - if op.Type == backend.OperationTypeApply { - header = applyDefaultHeader - } - b.CLI.Output(b.Colorize().Color(strings.TrimSpace(fmt.Sprintf( - header, b.hostname, b.organization, op.Workspace, r.ID)) + "\n")) - } - - r, err = b.waitForRun(stopCtx, cancelCtx, op, "plan", r, w) - if err != nil { - return r, err - } - - logs, err := b.client.Plans.Logs(stopCtx, r.Plan.ID) - if err != nil { - return r, generalError("error retrieving logs", err) - } - scanner := bufio.NewScanner(logs) - - for scanner.Scan() { - if b.CLI != nil { - b.CLI.Output(b.Colorize().Color(scanner.Text())) - } - } - if err := scanner.Err(); err != nil { - return r, generalError("error reading logs", err) - } - - // Retrieve the run to get its current status. - r, err = b.client.Runs.Read(stopCtx, r.ID) - if err != nil { - return r, generalError("error retrieving run", err) - } - - // Return if there are no changes or the run errored. We return - // without an error, even if the run errored, as the error is - // already displayed by the output of the remote run. - if !r.HasChanges || r.Status == tfe.RunErrored { - return r, nil - } - - // Check any configured sentinel policies. - if len(r.PolicyChecks) > 0 { - err = b.checkPolicy(stopCtx, cancelCtx, op, r) - if err != nil { - return r, err - } - } - - return r, nil -} - -const planErrNoQueueRunRights = ` -Insufficient rights to generate a plan! - -[reset][yellow]The provided credentials have insufficient rights to generate a plan. In order -to generate plans, at least plan permissions on the workspace are required.[reset] -` - -const planErrModuleDepthNotSupported = ` -Custom module depths are currently not supported! - -The "remote" backend does not support setting a custom module -depth at this time. -` - -const planErrParallelismNotSupported = ` -Custom parallelism values are currently not supported! - -The "remote" backend does not support setting a custom parallelism -value at this time. -` - -const planErrPlanNotSupported = ` -Displaying a saved plan is currently not supported! - -The "remote" backend currently requires configuration to be present and -does not accept an existing saved plan as an argument at this time. -` - -const planErrOutPathNotSupported = ` -Saving a generated plan is currently not supported! - -The "remote" backend does not support saving the generated execution -plan locally at this time. -` - -const planErrNoRefreshNotSupported = ` -Planning without refresh is currently not supported! - -Currently the "remote" backend will always do an in-memory refresh of -the Terraform state prior to generating the plan. -` - -const planErrTargetsNotSupported = ` -Resource targeting is currently not supported! - -The "remote" backend does not support resource targeting at this time. -` - -const planErrVariablesNotSupported = ` -Run variables are currently not supported! - -The "remote" backend does not support setting run variables at this time. -Currently the only to way to pass variables to the remote backend is by -creating a '*.auto.tfvars' variables file. This file will automatically -be loaded by the "remote" backend when the workspace is configured to use -Terraform v0.10.0 or later. - -Additionally you can also set variables on the workspace in the web UI: -https://%s/app/%s/%s/variables -` - -const planErrNoConfig = ` -No configuration files found! - -Plan requires configuration to be present. Planning without a configuration -would mark everything for destruction, which is normally not what is desired. -If you would like to destroy everything, please run plan with the "-destroy" -flag or create a single empty configuration file. Otherwise, please create -a Terraform configuration file in the path being executed and try again. -` - -const planDefaultHeader = ` -[reset][yellow]Running plan in the remote backend. Output will stream here. Pressing Ctrl-C -will stop streaming the logs, but will not stop the plan running remotely. -To view this run in a browser, visit: -https://%s/app/%s/%s/runs/%s[reset] -` - -// The newline in this error is to make it look good in the CLI! -const lockTimeoutErr = ` -[reset][red]Lock timeout exceeded, sending interrupt to cancel the remote operation. -[reset] -` diff --git a/backend/remote/backend_plan_test.go b/backend/remote/backend_plan_test.go deleted file mode 100644 index 5dde929fb..000000000 --- a/backend/remote/backend_plan_test.go +++ /dev/null @@ -1,597 +0,0 @@ -package remote - -import ( - "context" - "os" - "os/signal" - "strings" - "syscall" - "testing" - "time" - - tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/config/module" - "github.com/hashicorp/terraform/terraform" - "github.com/mitchellh/cli" -) - -func testOperationPlan() *backend.Operation { - return &backend.Operation{ - ModuleDepth: defaultModuleDepth, - Parallelism: defaultParallelism, - PlanRefresh: true, - Type: backend.OperationTypePlan, - } -} - -func TestRemote_planBasic(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.PlanEmpty { - t.Fatal("expected a non-empty plan") - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } -} - -func TestRemote_planWithoutPermissions(t *testing.T) { - b := testBackendNoDefault(t) - - // Create a named workspace without permissions. - w, err := b.client.Workspaces.Create( - context.Background(), - b.organization, - tfe.WorkspaceCreateOptions{ - Name: tfe.String(b.prefix + "prod"), - }, - ) - if err != nil { - t.Fatalf("error creating named workspace: %v", err) - } - w.Permissions.CanQueueRun = false - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.Workspace = "prod" - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected a plan error, got: %v", run.Err) - } - if !strings.Contains(run.Err.Error(), "insufficient rights to generate a plan") { - t.Fatalf("expected a permissions error, got: %v", run.Err) - } -} - -func TestRemote_planWithModuleDepth(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.ModuleDepth = 1 - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected a plan error, got: %v", run.Err) - } - if !strings.Contains(run.Err.Error(), "module depths are currently not supported") { - t.Fatalf("expected a module depth error, got: %v", run.Err) - } -} - -func TestRemote_planWithParallelism(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.Parallelism = 3 - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected a plan error, got: %v", run.Err) - } - if !strings.Contains(run.Err.Error(), "parallelism values are currently not supported") { - t.Fatalf("expected a parallelism error, got: %v", run.Err) - } -} - -func TestRemote_planWithPlan(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.Plan = &terraform.Plan{} - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected a plan error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "saved plan is currently not supported") { - t.Fatalf("expected a saved plan error, got: %v", run.Err) - } -} - -func TestRemote_planWithPath(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.PlanOutPath = "./test-fixtures/plan" - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected a plan error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "generated plan is currently not supported") { - t.Fatalf("expected a generated plan error, got: %v", run.Err) - } -} - -func TestRemote_planWithoutRefresh(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.PlanRefresh = false - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected a plan error, got: %v", run.Err) - } - if !strings.Contains(run.Err.Error(), "refresh is currently not supported") { - t.Fatalf("expected a refresh error, got: %v", run.Err) - } -} - -func TestRemote_planWithTarget(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.Targets = []string{"null_resource.foo"} - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected a plan error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "targeting is currently not supported") { - t.Fatalf("expected a targeting error, got: %v", run.Err) - } -} - -func TestRemote_planWithVariables(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.Variables = map[string]interface{}{"foo": "bar"} - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected an plan error, got: %v", run.Err) - } - if !strings.Contains(run.Err.Error(), "variables are currently not supported") { - t.Fatalf("expected a variables error, got: %v", run.Err) - } -} - -func TestRemote_planNoConfig(t *testing.T) { - b := testBackendDefault(t) - - op := testOperationPlan() - op.Module = nil - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected a plan error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "configuration files found") { - t.Fatalf("expected configuration files error, got: %v", run.Err) - } -} - -func TestRemote_planLockTimeout(t *testing.T) { - b := testBackendDefault(t) - ctx := context.Background() - - // Retrieve the workspace used to run this operation in. - w, err := b.client.Workspaces.Read(ctx, b.organization, b.workspace) - if err != nil { - t.Fatalf("error retrieving workspace: %v", err) - } - - // Create a new configuration version. - c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{}) - if err != nil { - t.Fatalf("error creating configuration version: %v", err) - } - - // Create a pending run to block this run. - _, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{ - ConfigurationVersion: c, - Workspace: w, - }) - if err != nil { - t.Fatalf("error creating pending run: %v", err) - } - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - input := testInput(t, map[string]string{ - "cancel": "yes", - "approve": "yes", - }) - - op := testOperationPlan() - op.StateLockTimeout = 5 * time.Second - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - _, err = b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - sigint := make(chan os.Signal, 1) - signal.Notify(sigint, syscall.SIGINT) - select { - case <-sigint: - // Stop redirecting SIGINT signals. - signal.Stop(sigint) - case <-time.After(10 * time.Second): - t.Fatalf("expected lock timeout after 5 seconds, waited 10 seconds") - } - - if len(input.answers) != 2 { - t.Fatalf("expected unused answers, got: %v", input.answers) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "Lock timeout exceeded") { - t.Fatalf("missing lock timout error in output: %s", output) - } - if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("unexpected plan summery in output: %s", output) - } -} - -func TestRemote_planDestroy(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") - defer modCleanup() - - op := testOperationPlan() - op.Destroy = true - op.Module = mod - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("unexpected plan error: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } -} - -func TestRemote_planDestroyNoConfig(t *testing.T) { - b := testBackendDefault(t) - - op := testOperationPlan() - op.Destroy = true - op.Module = nil - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("unexpected plan error: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } -} - -func TestRemote_planWithWorkingDirectory(t *testing.T) { - b := testBackendDefault(t) - - options := tfe.WorkspaceUpdateOptions{ - WorkingDirectory: tfe.String("terraform"), - } - - // Configure the workspace to use a custom working direcrtory. - _, err := b.client.Workspaces.Update(context.Background(), b.organization, b.workspace, options) - if err != nil { - t.Fatalf("error configuring working directory: %v", err) - } - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-with-working-directory/terraform") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } -} - -func TestRemote_planPolicyPass(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-policy-passed") - defer modCleanup() - - input := testInput(t, map[string]string{}) - - op := testOperationPlan() - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.PlanEmpty { - t.Fatalf("expected a non-empty plan") - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "Sentinel Result: true") { - t.Fatalf("missing polic check result in output: %s", output) - } -} - -func TestRemote_planPolicyHardFail(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-policy-hard-failed") - defer modCleanup() - - input := testInput(t, map[string]string{}) - - op := testOperationPlan() - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected a plan error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "hard failed") { - t.Fatalf("expected a policy check error, got: %v", run.Err) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "Sentinel Result: false") { - t.Fatalf("missing policy check result in output: %s", output) - } -} - -func TestRemote_planPolicySoftFail(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-policy-soft-failed") - defer modCleanup() - - input := testInput(t, map[string]string{}) - - op := testOperationPlan() - op.Module = mod - op.UIIn = input - op.UIOut = b.CLI - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err == nil { - t.Fatalf("expected a plan error, got: %v", run.Err) - } - if !run.PlanEmpty { - t.Fatalf("expected plan to be empty") - } - if !strings.Contains(run.Err.Error(), "soft failed") { - t.Fatalf("expected a policy check error, got: %v", run.Err) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { - t.Fatalf("missing plan summery in output: %s", output) - } - if !strings.Contains(output, "Sentinel Result: false") { - t.Fatalf("missing policy check result in output: %s", output) - } -} - -func TestRemote_planWithRemoteError(t *testing.T) { - b := testBackendDefault(t) - - mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-with-error") - defer modCleanup() - - op := testOperationPlan() - op.Module = mod - op.Workspace = backend.DefaultStateName - - run, err := b.Operation(context.Background(), op) - if err != nil { - t.Fatalf("error starting operation: %v", err) - } - - <-run.Done() - if run.Err != nil { - t.Fatalf("error running operation: %v", run.Err) - } - if run.ExitCode != 1 { - t.Fatalf("expected exit code 1, got %d", run.ExitCode) - } - - output := b.CLI.(*cli.MockUi).OutputWriter.String() - if !strings.Contains(output, "null_resource.foo: 1 error") { - t.Fatalf("missing plan error in output: %s", output) - } -} diff --git a/backend/remote/backend_state.go b/backend/remote/backend_state.go deleted file mode 100644 index 5c2a6cbfb..000000000 --- a/backend/remote/backend_state.go +++ /dev/null @@ -1,181 +0,0 @@ -package remote - -import ( - "bytes" - "context" - "crypto/md5" - "encoding/base64" - "fmt" - - tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/state" - "github.com/hashicorp/terraform/state/remote" - "github.com/hashicorp/terraform/terraform" -) - -type remoteClient struct { - client *tfe.Client - lockInfo *state.LockInfo - organization string - runID string - workspace string -} - -// Get the remote state. -func (r *remoteClient) Get() (*remote.Payload, error) { - ctx := context.Background() - - // Retrieve the workspace for which to create a new state. - w, err := r.client.Workspaces.Read(ctx, r.organization, r.workspace) - if err != nil { - if err == tfe.ErrResourceNotFound { - // If no state exists, then return nil. - return nil, nil - } - return nil, fmt.Errorf("Error retrieving workspace: %v", err) - } - - sv, err := r.client.StateVersions.Current(ctx, w.ID) - if err != nil { - if err == tfe.ErrResourceNotFound { - // If no state exists, then return nil. - return nil, nil - } - return nil, fmt.Errorf("Error retrieving remote state: %v", err) - } - - state, err := r.client.StateVersions.Download(ctx, sv.DownloadURL) - if err != nil { - return nil, fmt.Errorf("Error downloading remote state: %v", err) - } - - // If the state is empty, then return nil. - if len(state) == 0 { - return nil, nil - } - - // Get the MD5 checksum of the state. - sum := md5.Sum(state) - - return &remote.Payload{ - Data: state, - MD5: sum[:], - }, nil -} - -// Put the remote state. -func (r *remoteClient) Put(state []byte) error { - ctx := context.Background() - - // Retrieve the workspace for which to create a new state. - w, err := r.client.Workspaces.Read(ctx, r.organization, r.workspace) - if err != nil { - return fmt.Errorf("Error retrieving workspace: %v", err) - } - - // Read the raw state into a Terraform state. - tfState, err := terraform.ReadState(bytes.NewReader(state)) - if err != nil { - return fmt.Errorf("Error reading state: %s", err) - } - - options := tfe.StateVersionCreateOptions{ - Lineage: tfe.String(tfState.Lineage), - Serial: tfe.Int64(tfState.Serial), - MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))), - State: tfe.String(base64.StdEncoding.EncodeToString(state)), - } - - // If we have a run ID, make sure to add it to the options - // so the state will be properly associated with the run. - if r.runID != "" { - options.Run = &tfe.Run{ID: r.runID} - } - - // Create the new state. - _, err = r.client.StateVersions.Create(ctx, w.ID, options) - if err != nil { - return fmt.Errorf("Error creating remote state: %v", err) - } - - return nil -} - -// Delete the remote state. -func (r *remoteClient) Delete() error { - err := r.client.Workspaces.Delete(context.Background(), r.organization, r.workspace) - if err != nil && err != tfe.ErrResourceNotFound { - return fmt.Errorf("Error deleting workspace %s: %v", r.workspace, err) - } - - return nil -} - -// Lock the remote state. -func (r *remoteClient) Lock(info *state.LockInfo) (string, error) { - ctx := context.Background() - - lockErr := &state.LockError{Info: r.lockInfo} - - // Retrieve the workspace to lock. - w, err := r.client.Workspaces.Read(ctx, r.organization, r.workspace) - if err != nil { - lockErr.Err = err - return "", lockErr - } - - // Check if the workspace is already locked. - if w.Locked { - lockErr.Err = fmt.Errorf( - "remote state already\nlocked (lock ID: \"%s/%s\")", r.organization, r.workspace) - return "", lockErr - } - - // Lock the workspace. - w, err = r.client.Workspaces.Lock(ctx, w.ID, tfe.WorkspaceLockOptions{ - Reason: tfe.String("Locked by Terraform"), - }) - if err != nil { - lockErr.Err = err - return "", lockErr - } - - r.lockInfo = info - - return r.lockInfo.ID, nil -} - -// Unlock the remote state. -func (r *remoteClient) Unlock(id string) error { - ctx := context.Background() - - lockErr := &state.LockError{Info: r.lockInfo} - - // Verify the expected lock ID. - if r.lockInfo != nil && r.lockInfo.ID != id { - lockErr.Err = fmt.Errorf("lock ID does not match existing lock") - return lockErr - } - - // Verify the optional force-unlock lock ID. - if r.lockInfo == nil && r.organization+"/"+r.workspace != id { - lockErr.Err = fmt.Errorf("lock ID does not match existing lock") - return lockErr - } - - // Retrieve the workspace to lock. - w, err := r.client.Workspaces.Read(ctx, r.organization, r.workspace) - if err != nil { - lockErr.Err = err - return lockErr - } - - // Unlock the workspace. - w, err = r.client.Workspaces.Unlock(ctx, w.ID) - if err != nil { - lockErr.Err = err - return lockErr - } - - return nil -} diff --git a/backend/remote/backend_state_test.go b/backend/remote/backend_state_test.go deleted file mode 100644 index b2e82c548..000000000 --- a/backend/remote/backend_state_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package remote - -import ( - "bytes" - "os" - "testing" - - "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/state/remote" - "github.com/hashicorp/terraform/terraform" -) - -func TestRemoteClient_impl(t *testing.T) { - var _ remote.Client = new(remoteClient) -} - -func TestRemoteClient(t *testing.T) { - client := testRemoteClient(t) - remote.TestClient(t, client) -} - -func TestRemoteClient_stateLock(t *testing.T) { - b := testBackendDefault(t) - - s1, err := b.State(backend.DefaultStateName) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - s2, err := b.State(backend.DefaultStateName) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client) -} - -func TestRemoteClient_withRunID(t *testing.T) { - // Set the TFE_RUN_ID environment variable before creating the client! - if err := os.Setenv("TFE_RUN_ID", generateID("run-")); err != nil { - t.Fatalf("error setting env var TFE_RUN_ID: %v", err) - } - - // Create a new test client. - client := testRemoteClient(t) - - // Create a new empty state. - state := bytes.NewBuffer(nil) - if err := terraform.WriteState(terraform.NewState(), state); err != nil { - t.Fatalf("expected no error, got: %v", err) - } - - // Store the new state to verify (this will be done - // by the mock that is used) that the run ID is set. - if err := client.Put(state.Bytes()); err != nil { - t.Fatalf("expected no error, got %v", err) - } -} diff --git a/backend/remote/backend_test.go b/backend/remote/backend_test.go deleted file mode 100644 index 28553cae1..000000000 --- a/backend/remote/backend_test.go +++ /dev/null @@ -1,240 +0,0 @@ -package remote - -import ( - "errors" - "reflect" - "strings" - "testing" - - "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/config" - "github.com/hashicorp/terraform/terraform" -) - -func TestRemote(t *testing.T) { - var _ backend.Enhanced = New(nil) - var _ backend.CLI = New(nil) -} - -func TestRemote_backendDefault(t *testing.T) { - b := testBackendDefault(t) - backend.TestBackendStates(t, b) - backend.TestBackendStateLocks(t, b, b) - backend.TestBackendStateForceUnlock(t, b, b) -} - -func TestRemote_backendNoDefault(t *testing.T) { - b := testBackendNoDefault(t) - backend.TestBackendStates(t, b) -} - -func TestRemote_config(t *testing.T) { - cases := map[string]struct { - config map[string]interface{} - err error - }{ - "with_a_name": { - config: map[string]interface{}{ - "organization": "hashicorp", - "workspaces": []interface{}{ - map[string]interface{}{ - "name": "prod", - }, - }, - }, - err: nil, - }, - "with_a_prefix": { - config: map[string]interface{}{ - "organization": "hashicorp", - "workspaces": []interface{}{ - map[string]interface{}{ - "prefix": "my-app-", - }, - }, - }, - err: nil, - }, - "without_either_a_name_and_a_prefix": { - config: map[string]interface{}{ - "organization": "hashicorp", - "workspaces": []interface{}{ - map[string]interface{}{}, - }, - }, - err: errors.New("either workspace 'name' or 'prefix' is required"), - }, - "with_both_a_name_and_a_prefix": { - config: map[string]interface{}{ - "organization": "hashicorp", - "workspaces": []interface{}{ - map[string]interface{}{ - "name": "prod", - "prefix": "my-app-", - }, - }, - }, - err: errors.New("only one of workspace 'name' or 'prefix' is allowed"), - }, - "with_an_unknown_host": { - config: map[string]interface{}{ - "hostname": "nonexisting.local", - "organization": "hashicorp", - "workspaces": []interface{}{ - map[string]interface{}{ - "name": "prod", - }, - }, - }, - err: errors.New("host nonexisting.local does not provide a remote backend API"), - }, - } - - for name, tc := range cases { - s := testServer(t) - b := New(testDisco(s)) - - // Get the proper config structure - rc, err := config.NewRawConfig(tc.config) - if err != nil { - t.Fatalf("%s: error creating raw config: %v", name, err) - } - conf := terraform.NewResourceConfig(rc) - - // Validate - warns, errs := b.Validate(conf) - if len(warns) > 0 { - t.Fatalf("%s: validation warnings: %v", name, warns) - } - if len(errs) > 0 { - t.Fatalf("%s: validation errors: %v", name, errs) - } - - // Configure - err = b.Configure(conf) - if err != tc.err && err != nil && tc.err != nil && err.Error() != tc.err.Error() { - t.Fatalf("%s: expected error %q, got: %q", name, tc.err, err) - } - } -} - -func TestRemote_nonexistingOrganization(t *testing.T) { - msg := "does not exist" - - b := testBackendNoDefault(t) - b.organization = "nonexisting" - - if _, err := b.State("prod"); err == nil || !strings.Contains(err.Error(), msg) { - t.Fatalf("expected %q error, got: %v", msg, err) - } - - if err := b.DeleteState("prod"); err == nil || !strings.Contains(err.Error(), msg) { - t.Fatalf("expected %q error, got: %v", msg, err) - } - - if _, err := b.States(); err == nil || !strings.Contains(err.Error(), msg) { - t.Fatalf("expected %q error, got: %v", msg, err) - } -} - -func TestRemote_addAndRemoveStatesDefault(t *testing.T) { - b := testBackendDefault(t) - if _, err := b.States(); err != backend.ErrNamedStatesNotSupported { - t.Fatalf("expected error %v, got %v", backend.ErrNamedStatesNotSupported, err) - } - - if _, err := b.State(backend.DefaultStateName); err != nil { - t.Fatalf("expected no error, got %v", err) - } - - if _, err := b.State("prod"); err != backend.ErrNamedStatesNotSupported { - t.Fatalf("expected error %v, got %v", backend.ErrNamedStatesNotSupported, err) - } - - if err := b.DeleteState(backend.DefaultStateName); err != nil { - t.Fatalf("expected no error, got %v", err) - } - - if err := b.DeleteState("prod"); err != backend.ErrNamedStatesNotSupported { - t.Fatalf("expected error %v, got %v", backend.ErrNamedStatesNotSupported, err) - } -} - -func TestRemote_addAndRemoveStatesNoDefault(t *testing.T) { - b := testBackendNoDefault(t) - states, err := b.States() - if err != nil { - t.Fatal(err) - } - - expectedStates := []string(nil) - if !reflect.DeepEqual(states, expectedStates) { - t.Fatalf("expected states %#+v, got %#+v", expectedStates, states) - } - - if _, err := b.State(backend.DefaultStateName); err != backend.ErrDefaultStateNotSupported { - t.Fatalf("expected error %v, got %v", backend.ErrDefaultStateNotSupported, err) - } - - expectedA := "test_A" - if _, err := b.State(expectedA); err != nil { - t.Fatal(err) - } - - states, err = b.States() - if err != nil { - t.Fatal(err) - } - - expectedStates = append(expectedStates, expectedA) - if !reflect.DeepEqual(states, expectedStates) { - t.Fatalf("expected %#+v, got %#+v", expectedStates, states) - } - - expectedB := "test_B" - if _, err := b.State(expectedB); err != nil { - t.Fatal(err) - } - - states, err = b.States() - if err != nil { - t.Fatal(err) - } - - expectedStates = append(expectedStates, expectedB) - if !reflect.DeepEqual(states, expectedStates) { - t.Fatalf("expected %#+v, got %#+v", expectedStates, states) - } - - if err := b.DeleteState(backend.DefaultStateName); err != backend.ErrDefaultStateNotSupported { - t.Fatalf("expected error %v, got %v", backend.ErrDefaultStateNotSupported, err) - } - - if err := b.DeleteState(expectedA); err != nil { - t.Fatal(err) - } - - states, err = b.States() - if err != nil { - t.Fatal(err) - } - - expectedStates = []string{expectedB} - if !reflect.DeepEqual(states, expectedStates) { - t.Fatalf("expected %#+v got %#+v", expectedStates, states) - } - - if err := b.DeleteState(expectedB); err != nil { - t.Fatal(err) - } - - states, err = b.States() - if err != nil { - t.Fatal(err) - } - - expectedStates = []string(nil) - if !reflect.DeepEqual(states, expectedStates) { - t.Fatalf("expected %#+v, got %#+v", expectedStates, states) - } -} diff --git a/backend/remote/cli.go b/backend/remote/cli.go deleted file mode 100644 index 9339c1091..000000000 --- a/backend/remote/cli.go +++ /dev/null @@ -1,13 +0,0 @@ -package remote - -import ( - "github.com/hashicorp/terraform/backend" -) - -// CLIInit implements backend.CLI -func (b *Remote) CLIInit(opts *backend.CLIOpts) error { - b.CLI = opts.CLI - b.CLIColor = opts.CLIColor - b.ContextOpts = opts.ContextOpts - return nil -} diff --git a/backend/remote/colorize.go b/backend/remote/colorize.go deleted file mode 100644 index 0f877c007..000000000 --- a/backend/remote/colorize.go +++ /dev/null @@ -1,47 +0,0 @@ -package remote - -import ( - "regexp" - - "github.com/mitchellh/colorstring" -) - -// colorsRe is used to find ANSI escaped color codes. -var colorsRe = regexp.MustCompile("\033\\[\\d{1,3}m") - -// Colorer is the interface that must be implemented to colorize strings. -type Colorer interface { - Color(v string) string -} - -// Colorize is used to print output when the -no-color flag is used. It will -// strip all ANSI escaped color codes which are set while the operation was -// executed in Terraform Enterprise. -// -// When Terraform Enterprise supports run specific variables, this code can be -// removed as we can then pass the CLI flag to the backend and prevent the color -// codes from being written to the output. -type Colorize struct { - cliColor *colorstring.Colorize -} - -// Color will strip all ANSI escaped color codes and return a uncolored string. -func (c *Colorize) Color(v string) string { - return colorsRe.ReplaceAllString(c.cliColor.Color(v), "") -} - -// Colorize returns the Colorize structure that can be used for colorizing -// output. This is guaranteed to always return a non-nil value and so is useful -// as a helper to wrap any potentially colored strings. -func (b *Remote) Colorize() Colorer { - if b.CLIColor != nil && !b.CLIColor.Disable { - return b.CLIColor - } - if b.CLIColor != nil { - return &Colorize{cliColor: b.CLIColor} - } - return &Colorize{cliColor: &colorstring.Colorize{ - Colors: colorstring.DefaultColors, - Disable: true, - }} -} diff --git a/backend/remote/test-fixtures/apply-destroy/apply.log b/backend/remote/test-fixtures/apply-destroy/apply.log deleted file mode 100644 index 34adfcd6b..000000000 --- a/backend/remote/test-fixtures/apply-destroy/apply.log +++ /dev/null @@ -1,4 +0,0 @@ -null_resource.hello: Destroying... (ID: 8657651096157629581) -null_resource.hello: Destruction complete after 0s - -Apply complete! Resources: 0 added, 0 changed, 1 destroyed. diff --git a/backend/remote/test-fixtures/apply-destroy/main.tf b/backend/remote/test-fixtures/apply-destroy/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/apply-destroy/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/apply-destroy/plan.log b/backend/remote/test-fixtures/apply-destroy/plan.log deleted file mode 100644 index 1d38d4168..000000000 --- a/backend/remote/test-fixtures/apply-destroy/plan.log +++ /dev/null @@ -1,22 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - -null_resource.hello: Refreshing state... (ID: 8657651096157629581) - ------------------------------------------------------------------------- - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: - - destroy - -Terraform will perform the following actions: - - - null_resource.hello - - -Plan: 0 to add, 0 to change, 1 to destroy. diff --git a/backend/remote/test-fixtures/apply-no-changes/main.tf b/backend/remote/test-fixtures/apply-no-changes/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/apply-no-changes/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/apply-no-changes/plan.log b/backend/remote/test-fixtures/apply-no-changes/plan.log deleted file mode 100644 index 704168151..000000000 --- a/backend/remote/test-fixtures/apply-no-changes/plan.log +++ /dev/null @@ -1,17 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - -null_resource.hello: Refreshing state... (ID: 8657651096157629581) - ------------------------------------------------------------------------- - -No changes. Infrastructure is up-to-date. - -This means that Terraform did not detect any differences between your -configuration and real physical resources that exist. As a result, no -actions need to be performed. diff --git a/backend/remote/test-fixtures/apply-policy-hard-failed/main.tf b/backend/remote/test-fixtures/apply-policy-hard-failed/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/apply-policy-hard-failed/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/apply-policy-hard-failed/plan.log b/backend/remote/test-fixtures/apply-policy-hard-failed/plan.log deleted file mode 100644 index 5849e5759..000000000 --- a/backend/remote/test-fixtures/apply-policy-hard-failed/plan.log +++ /dev/null @@ -1,21 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - ------------------------------------------------------------------------- - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: - + create - -Terraform will perform the following actions: - - + null_resource.foo - id: - - -Plan: 1 to add, 0 to change, 0 to destroy. diff --git a/backend/remote/test-fixtures/apply-policy-hard-failed/policy.log b/backend/remote/test-fixtures/apply-policy-hard-failed/policy.log deleted file mode 100644 index 5d6e6935b..000000000 --- a/backend/remote/test-fixtures/apply-policy-hard-failed/policy.log +++ /dev/null @@ -1,12 +0,0 @@ -Sentinel Result: false - -Sentinel evaluated to false because one or more Sentinel policies evaluated -to false. This false was not due to an undefined value or runtime error. - -1 policies evaluated. - -## Policy 1: Passthrough.sentinel (hard-mandatory) - -Result: false - -FALSE - Passthrough.sentinel:1:1 - Rule "main" diff --git a/backend/remote/test-fixtures/apply-policy-passed/apply.log b/backend/remote/test-fixtures/apply-policy-passed/apply.log deleted file mode 100644 index 89c0dbc42..000000000 --- a/backend/remote/test-fixtures/apply-policy-passed/apply.log +++ /dev/null @@ -1,4 +0,0 @@ -null_resource.hello: Creating... -null_resource.hello: Creation complete after 0s (ID: 8657651096157629581) - -Apply complete! Resources: 1 added, 0 changed, 0 destroyed. diff --git a/backend/remote/test-fixtures/apply-policy-passed/main.tf b/backend/remote/test-fixtures/apply-policy-passed/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/apply-policy-passed/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/apply-policy-passed/plan.log b/backend/remote/test-fixtures/apply-policy-passed/plan.log deleted file mode 100644 index 5849e5759..000000000 --- a/backend/remote/test-fixtures/apply-policy-passed/plan.log +++ /dev/null @@ -1,21 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - ------------------------------------------------------------------------- - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: - + create - -Terraform will perform the following actions: - - + null_resource.foo - id: - - -Plan: 1 to add, 0 to change, 0 to destroy. diff --git a/backend/remote/test-fixtures/apply-policy-passed/policy.log b/backend/remote/test-fixtures/apply-policy-passed/policy.log deleted file mode 100644 index b0cb1e598..000000000 --- a/backend/remote/test-fixtures/apply-policy-passed/policy.log +++ /dev/null @@ -1,12 +0,0 @@ -Sentinel Result: true - -This result means that Sentinel policies returned true and the protected -behavior is allowed by Sentinel policies. - -1 policies evaluated. - -## Policy 1: Passthrough.sentinel (soft-mandatory) - -Result: true - -TRUE - Passthrough.sentinel:1:1 - Rule "main" diff --git a/backend/remote/test-fixtures/apply-policy-soft-failed/apply.log b/backend/remote/test-fixtures/apply-policy-soft-failed/apply.log deleted file mode 100644 index 89c0dbc42..000000000 --- a/backend/remote/test-fixtures/apply-policy-soft-failed/apply.log +++ /dev/null @@ -1,4 +0,0 @@ -null_resource.hello: Creating... -null_resource.hello: Creation complete after 0s (ID: 8657651096157629581) - -Apply complete! Resources: 1 added, 0 changed, 0 destroyed. diff --git a/backend/remote/test-fixtures/apply-policy-soft-failed/main.tf b/backend/remote/test-fixtures/apply-policy-soft-failed/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/apply-policy-soft-failed/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/apply-policy-soft-failed/plan.log b/backend/remote/test-fixtures/apply-policy-soft-failed/plan.log deleted file mode 100644 index 5849e5759..000000000 --- a/backend/remote/test-fixtures/apply-policy-soft-failed/plan.log +++ /dev/null @@ -1,21 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - ------------------------------------------------------------------------- - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: - + create - -Terraform will perform the following actions: - - + null_resource.foo - id: - - -Plan: 1 to add, 0 to change, 0 to destroy. diff --git a/backend/remote/test-fixtures/apply-policy-soft-failed/policy.log b/backend/remote/test-fixtures/apply-policy-soft-failed/policy.log deleted file mode 100644 index 3e4ebedf6..000000000 --- a/backend/remote/test-fixtures/apply-policy-soft-failed/policy.log +++ /dev/null @@ -1,12 +0,0 @@ -Sentinel Result: false - -Sentinel evaluated to false because one or more Sentinel policies evaluated -to false. This false was not due to an undefined value or runtime error. - -1 policies evaluated. - -## Policy 1: Passthrough.sentinel (soft-mandatory) - -Result: false - -FALSE - Passthrough.sentinel:1:1 - Rule "main" diff --git a/backend/remote/test-fixtures/apply-with-error/main.tf b/backend/remote/test-fixtures/apply-with-error/main.tf deleted file mode 100644 index bc45f28f5..000000000 --- a/backend/remote/test-fixtures/apply-with-error/main.tf +++ /dev/null @@ -1,5 +0,0 @@ -resource "null_resource" "foo" { - triggers { - random = "${guid()}" - } -} diff --git a/backend/remote/test-fixtures/apply-with-error/plan.log b/backend/remote/test-fixtures/apply-with-error/plan.log deleted file mode 100644 index 4344a3722..000000000 --- a/backend/remote/test-fixtures/apply-with-error/plan.log +++ /dev/null @@ -1,10 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... - -Error: null_resource.foo: 1 error(s) occurred: - -* null_resource.foo: 1:3: unknown function called: guid in: - -${guid()} diff --git a/backend/remote/test-fixtures/apply/apply.log b/backend/remote/test-fixtures/apply/apply.log deleted file mode 100644 index 89c0dbc42..000000000 --- a/backend/remote/test-fixtures/apply/apply.log +++ /dev/null @@ -1,4 +0,0 @@ -null_resource.hello: Creating... -null_resource.hello: Creation complete after 0s (ID: 8657651096157629581) - -Apply complete! Resources: 1 added, 0 changed, 0 destroyed. diff --git a/backend/remote/test-fixtures/apply/main.tf b/backend/remote/test-fixtures/apply/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/apply/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/apply/plan.log b/backend/remote/test-fixtures/apply/plan.log deleted file mode 100644 index 5849e5759..000000000 --- a/backend/remote/test-fixtures/apply/plan.log +++ /dev/null @@ -1,21 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - ------------------------------------------------------------------------- - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: - + create - -Terraform will perform the following actions: - - + null_resource.foo - id: - - -Plan: 1 to add, 0 to change, 0 to destroy. diff --git a/backend/remote/test-fixtures/plan-policy-hard-failed/main.tf b/backend/remote/test-fixtures/plan-policy-hard-failed/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/plan-policy-hard-failed/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/plan-policy-hard-failed/plan.log b/backend/remote/test-fixtures/plan-policy-hard-failed/plan.log deleted file mode 100644 index 5849e5759..000000000 --- a/backend/remote/test-fixtures/plan-policy-hard-failed/plan.log +++ /dev/null @@ -1,21 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - ------------------------------------------------------------------------- - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: - + create - -Terraform will perform the following actions: - - + null_resource.foo - id: - - -Plan: 1 to add, 0 to change, 0 to destroy. diff --git a/backend/remote/test-fixtures/plan-policy-hard-failed/policy.log b/backend/remote/test-fixtures/plan-policy-hard-failed/policy.log deleted file mode 100644 index 5d6e6935b..000000000 --- a/backend/remote/test-fixtures/plan-policy-hard-failed/policy.log +++ /dev/null @@ -1,12 +0,0 @@ -Sentinel Result: false - -Sentinel evaluated to false because one or more Sentinel policies evaluated -to false. This false was not due to an undefined value or runtime error. - -1 policies evaluated. - -## Policy 1: Passthrough.sentinel (hard-mandatory) - -Result: false - -FALSE - Passthrough.sentinel:1:1 - Rule "main" diff --git a/backend/remote/test-fixtures/plan-policy-passed/main.tf b/backend/remote/test-fixtures/plan-policy-passed/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/plan-policy-passed/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/plan-policy-passed/plan.log b/backend/remote/test-fixtures/plan-policy-passed/plan.log deleted file mode 100644 index 5849e5759..000000000 --- a/backend/remote/test-fixtures/plan-policy-passed/plan.log +++ /dev/null @@ -1,21 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - ------------------------------------------------------------------------- - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: - + create - -Terraform will perform the following actions: - - + null_resource.foo - id: - - -Plan: 1 to add, 0 to change, 0 to destroy. diff --git a/backend/remote/test-fixtures/plan-policy-passed/policy.log b/backend/remote/test-fixtures/plan-policy-passed/policy.log deleted file mode 100644 index b0cb1e598..000000000 --- a/backend/remote/test-fixtures/plan-policy-passed/policy.log +++ /dev/null @@ -1,12 +0,0 @@ -Sentinel Result: true - -This result means that Sentinel policies returned true and the protected -behavior is allowed by Sentinel policies. - -1 policies evaluated. - -## Policy 1: Passthrough.sentinel (soft-mandatory) - -Result: true - -TRUE - Passthrough.sentinel:1:1 - Rule "main" diff --git a/backend/remote/test-fixtures/plan-policy-soft-failed/main.tf b/backend/remote/test-fixtures/plan-policy-soft-failed/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/plan-policy-soft-failed/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/plan-policy-soft-failed/plan.log b/backend/remote/test-fixtures/plan-policy-soft-failed/plan.log deleted file mode 100644 index 5849e5759..000000000 --- a/backend/remote/test-fixtures/plan-policy-soft-failed/plan.log +++ /dev/null @@ -1,21 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - ------------------------------------------------------------------------- - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: - + create - -Terraform will perform the following actions: - - + null_resource.foo - id: - - -Plan: 1 to add, 0 to change, 0 to destroy. diff --git a/backend/remote/test-fixtures/plan-policy-soft-failed/policy.log b/backend/remote/test-fixtures/plan-policy-soft-failed/policy.log deleted file mode 100644 index 3e4ebedf6..000000000 --- a/backend/remote/test-fixtures/plan-policy-soft-failed/policy.log +++ /dev/null @@ -1,12 +0,0 @@ -Sentinel Result: false - -Sentinel evaluated to false because one or more Sentinel policies evaluated -to false. This false was not due to an undefined value or runtime error. - -1 policies evaluated. - -## Policy 1: Passthrough.sentinel (soft-mandatory) - -Result: false - -FALSE - Passthrough.sentinel:1:1 - Rule "main" diff --git a/backend/remote/test-fixtures/plan-with-error/main.tf b/backend/remote/test-fixtures/plan-with-error/main.tf deleted file mode 100644 index bc45f28f5..000000000 --- a/backend/remote/test-fixtures/plan-with-error/main.tf +++ /dev/null @@ -1,5 +0,0 @@ -resource "null_resource" "foo" { - triggers { - random = "${guid()}" - } -} diff --git a/backend/remote/test-fixtures/plan-with-error/plan.log b/backend/remote/test-fixtures/plan-with-error/plan.log deleted file mode 100644 index 4344a3722..000000000 --- a/backend/remote/test-fixtures/plan-with-error/plan.log +++ /dev/null @@ -1,10 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... - -Error: null_resource.foo: 1 error(s) occurred: - -* null_resource.foo: 1:3: unknown function called: guid in: - -${guid()} diff --git a/backend/remote/test-fixtures/plan-with-working-directory/terraform/main.tf b/backend/remote/test-fixtures/plan-with-working-directory/terraform/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/plan-with-working-directory/terraform/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/plan-with-working-directory/terraform/plan.log b/backend/remote/test-fixtures/plan-with-working-directory/terraform/plan.log deleted file mode 100644 index 5849e5759..000000000 --- a/backend/remote/test-fixtures/plan-with-working-directory/terraform/plan.log +++ /dev/null @@ -1,21 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - ------------------------------------------------------------------------- - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: - + create - -Terraform will perform the following actions: - - + null_resource.foo - id: - - -Plan: 1 to add, 0 to change, 0 to destroy. diff --git a/backend/remote/test-fixtures/plan/main.tf b/backend/remote/test-fixtures/plan/main.tf deleted file mode 100644 index 3911a2a9b..000000000 --- a/backend/remote/test-fixtures/plan/main.tf +++ /dev/null @@ -1 +0,0 @@ -resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/plan/plan.log b/backend/remote/test-fixtures/plan/plan.log deleted file mode 100644 index 5849e5759..000000000 --- a/backend/remote/test-fixtures/plan/plan.log +++ /dev/null @@ -1,21 +0,0 @@ -Terraform v0.11.7 - -Configuring remote state backend... -Initializing Terraform configuration... -Refreshing Terraform state in-memory prior to plan... -The refreshed state will be used to calculate this plan, but will not be -persisted to local or remote state storage. - ------------------------------------------------------------------------- - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: - + create - -Terraform will perform the following actions: - - + null_resource.foo - id: - - -Plan: 1 to add, 0 to change, 0 to destroy. diff --git a/backend/remote/testing.go b/backend/remote/testing.go deleted file mode 100644 index a21905216..000000000 --- a/backend/remote/testing.go +++ /dev/null @@ -1,137 +0,0 @@ -package remote - -import ( - "context" - "fmt" - "io" - "net/http" - "net/http/httptest" - "testing" - - tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/state/remote" - "github.com/hashicorp/terraform/svchost" - "github.com/hashicorp/terraform/svchost/auth" - "github.com/hashicorp/terraform/svchost/disco" - "github.com/mitchellh/cli" -) - -const ( - testCred = "test-auth-token" -) - -var ( - tfeHost = svchost.Hostname(defaultHostname) - credsSrc = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{ - tfeHost: {"token": testCred}, - }) -) - -func testInput(t *testing.T, answers map[string]string) *mockInput { - return &mockInput{answers: answers} -} - -func testBackendDefault(t *testing.T) *Remote { - c := map[string]interface{}{ - "organization": "hashicorp", - "workspaces": []interface{}{ - map[string]interface{}{ - "name": "prod", - }, - }, - } - return testBackend(t, c) -} - -func testBackendNoDefault(t *testing.T) *Remote { - c := map[string]interface{}{ - "organization": "hashicorp", - "workspaces": []interface{}{ - map[string]interface{}{ - "prefix": "my-app-", - }, - }, - } - return testBackend(t, c) -} - -func testRemoteClient(t *testing.T) remote.Client { - b := testBackendDefault(t) - raw, err := b.State(backend.DefaultStateName) - if err != nil { - t.Fatalf("error: %v", err) - } - s := raw.(*remote.State) - return s.Client -} - -func testBackend(t *testing.T, c map[string]interface{}) *Remote { - s := testServer(t) - b := New(testDisco(s)) - - // Configure the backend so the client is created. - backend.TestBackendConfig(t, b, c) - - // Get a new mock client. - mc := newMockClient() - - // Replace the services we use with our mock services. - b.CLI = cli.NewMockUi() - b.client.Applies = mc.Applies - b.client.ConfigurationVersions = mc.ConfigurationVersions - b.client.Organizations = mc.Organizations - b.client.Plans = mc.Plans - b.client.PolicyChecks = mc.PolicyChecks - b.client.Runs = mc.Runs - b.client.StateVersions = mc.StateVersions - b.client.Workspaces = mc.Workspaces - - ctx := context.Background() - - // Create the organization. - _, err := b.client.Organizations.Create(ctx, tfe.OrganizationCreateOptions{ - Name: tfe.String(b.organization), - }) - if err != nil { - t.Fatalf("error: %v", err) - } - - // Create the default workspace if required. - if b.workspace != "" { - _, err = b.client.Workspaces.Create(ctx, b.organization, tfe.WorkspaceCreateOptions{ - Name: tfe.String(b.workspace), - }) - if err != nil { - t.Fatalf("error: %v", err) - } - } - - return b -} - -// testServer returns a *httptest.Server used for local testing. -func testServer(t *testing.T) *httptest.Server { - mux := http.NewServeMux() - - // Respond to service discovery calls. - mux.HandleFunc("/well-known/terraform.json", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{"tfe.v2":"/api/v2/"}`) - }) - - return httptest.NewServer(mux) -} - -// testDisco returns a *disco.Disco mapping app.terraform.io and -// localhost to a local test server. -func testDisco(s *httptest.Server) *disco.Disco { - services := map[string]interface{}{ - "tfe.v2": fmt.Sprintf("%s/api/v2/", s.URL), - } - d := disco.NewWithCredentialsSource(credsSrc) - - d.ForceHostServices(svchost.Hostname(defaultHostname), services) - d.ForceHostServices(svchost.Hostname("localhost"), services) - return d -} diff --git a/builtin/providers/terraform/data_source_state_test.go b/builtin/providers/terraform/data_source_state_test.go index f29ca87a6..5491c3531 100644 --- a/builtin/providers/terraform/data_source_state_test.go +++ b/builtin/providers/terraform/data_source_state_test.go @@ -155,7 +155,7 @@ func TestState_basic(t *testing.T) { t.Fatalf("unexpected errors: %s", diags.Err()) } - if !got.RawEquals(test.Want) { + if !test.Want.RawEquals(got) { t.Errorf("wrong result\nconfig: %sgot: %swant: %s", dump.Value(config), dump.Value(got), dump.Value(test.Want)) } }) diff --git a/builtin/providers/terraform/provider_test.go b/builtin/providers/terraform/provider_test.go index c2eaf3fd0..2baa338d3 100644 --- a/builtin/providers/terraform/provider_test.go +++ b/builtin/providers/terraform/provider_test.go @@ -3,6 +3,7 @@ package terraform import ( "testing" + backendinit "github.com/hashicorp/terraform/backend/init" "github.com/hashicorp/terraform/providers" ) @@ -14,6 +15,7 @@ func init() { testAccProviders = map[string]*Provider{ "terraform": testAccProvider, } + backendinit.Init(nil) } func TestProvider_impl(t *testing.T) { diff --git a/command/command_test.go b/command/command_test.go index 4cb872eb5..b473e9664 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -22,6 +22,7 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/addrs" + backendinit "github.com/hashicorp/terraform/backend/init" "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs/configload" "github.com/hashicorp/terraform/configs/configschema" @@ -46,9 +47,6 @@ var testingDir string func init() { test = true - // Initialize the backends - backendInit.Init(nil) - // Expand the fixture dir on init because we change the working // directory in some tests. var err error @@ -75,6 +73,9 @@ func TestMain(m *testing.M) { log.SetOutput(ioutil.Discard) } + // Make sure backend init is initialized, since our tests tend to assume it. + backendinit.Init(nil) + os.Exit(m.Run()) } diff --git a/command/init.go b/command/init.go index 76299167c..f14c6e127 100644 --- a/command/init.go +++ b/command/init.go @@ -156,8 +156,7 @@ func (c *InitCommand) Run(args []string) int { if empty, err := config.IsEmptyDir(path); err != nil { diags = diags.Append(fmt.Errorf("Error checking configuration: %s", err)) return 1 - } - if empty { + } else if empty { c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty))) return 0 } @@ -276,12 +275,14 @@ func (c *InitCommand) Run(args []string) int { if back != nil { sMgr, err := back.StateMgr(c.Workspace()) if err != nil { - c.Ui.Error(fmt.Sprintf("Error loading state: %s", err)) + c.Ui.Error(fmt.Sprintf( + "Error loading state: %s", err)) return 1 } if err := sMgr.RefreshState(); err != nil { - c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) + c.Ui.Error(fmt.Sprintf( + "Error refreshing state: %s", err)) return 1 } diff --git a/command/meta.go b/command/meta.go index a8c1e323f..da6d1c09b 100644 --- a/command/meta.go +++ b/command/meta.go @@ -52,6 +52,10 @@ type Meta struct { // "terraform-native' services running at a specific user-facing hostname. Services *disco.Disco + // Credentials provides access to credentials for "terraform-native" + // services, which are accessed by a service hostname. + Credentials auth.CredentialsSource + // RunningInAutomation indicates that commands are being run by an // automated system rather than directly at a command prompt. // diff --git a/command/meta_backend.go b/command/meta_backend.go index 3c38a8ea5..7b3925789 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -122,7 +122,7 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics } // Build the local backend - local := backendLocal.NewWithBackend(b) + local := &backendlocal.Local{Backend: b} if err := local.CLIInit(cliOpts); err != nil { // Local backend isn't allowed to fail. It would be a bug. panic(err) @@ -238,7 +238,7 @@ func (m *Meta) backendCLIOpts() *backend.CLIOpts { // for some checks that require a remote backend. func (m *Meta) IsLocalBackend(b backend.Backend) bool { // Is it a local backend? - bLocal, ok := b.(*backendLocal.Local) + bLocal, ok := b.(*backendlocal.Local) // If it is, does it not have an alternate state backend? if ok { @@ -610,7 +610,10 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *state.LocalSta return nil, diags } - if len(localStates) > 0 { + // If the local state is not empty, we need to potentially do a + // state migration to the new backend (with user permission), unless the + // destination is also "local" + if localS := localState.State(); !localS.Empty() { // Perform the migration err = m.backendMigrateState(&backendMigrateOpts{ OneType: "local", @@ -628,8 +631,8 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *state.LocalSta // can get us here too. Don't delete our state if the old and new paths // are the same. erase := true - if newLocalB, ok := b.(*backendLocal.Local); ok { - if localB, ok := localB.(*backendLocal.Local); ok { + if newLocalB, ok := b.(*backendlocal.Local); ok { + if localB, ok := localB.(*backendlocal.Local); ok { if newLocalB.StatePath == localB.StatePath { erase = false } @@ -794,7 +797,7 @@ func (m *Meta) backend_C_r_S_unchanged(c *configs.Backend, cHash int, sMgr *stat } // Get the backend - f := backendInit.Backend(s.Backend.Type) + f := backendinit.Backend(s.Backend.Type) if f == nil { diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Backend.Type)) return nil, diags @@ -858,7 +861,7 @@ func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.V var diags tfdiags.Diagnostics // Get the backend - f := backendInit.Backend(c.Type) + f := backendinit.Backend(c.Type) if f == nil { diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendNewUnknown), c.Type)) return nil, cty.NilVal, diags @@ -898,7 +901,7 @@ func (m *Meta) backendInitFromSaved(s *terraform.BackendState) (backend.Backend, var diags tfdiags.Diagnostics // Get the backend - f := backendInit.Backend(s.Type) + f := backendinit.Backend(s.Type) if f == nil { diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendSavedUnknown), s.Type)) return nil, diags diff --git a/command/meta_backend_migrate.go b/command/meta_backend_migrate.go index a8116adf2..2976cd63b 100644 --- a/command/meta_backend_migrate.go +++ b/command/meta_backend_migrate.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" "sort" - "strconv" "strings" "github.com/hashicorp/terraform/states" @@ -20,17 +19,6 @@ import ( "github.com/hashicorp/terraform/terraform" ) -type backendMigrateOpts struct { - OneType, TwoType string - One, Two backend.Backend - - // Fields below are set internally when migrate is called - - oneEnv string // source env - twoEnv string // dest env - force bool // if true, won't ask for confirmation -} - // backendMigrateState handles migrating (copying) state from one backend // to another. This function handles asking the user for confirmation // as well as the copy itself. @@ -172,56 +160,7 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error { } } - // Its possible that the currently selected workspace is not migrated, - // so we call selectWorkspace to ensure a valid workspace is selected. - return m.selectWorkspace(opts.Two) -} - -// selectWorkspace gets a list of migrated workspaces and then checks -// if the currently selected workspace is valid. If not, it will ask -// the user to select a workspace from the list. -func (m *Meta) selectWorkspace(b backend.Backend) error { - workspaces, err := b.States() - if err != nil { - return fmt.Errorf("Failed to get migrated workspaces: %s", err) - } - if len(workspaces) == 0 { - return fmt.Errorf(errBackendNoMigratedWorkspaces) - } - - // Get the currently selected workspace. - workspace := m.Workspace() - - // Check if any of the migrated workspaces match the selected workspace - // and create a numbered list with migrated workspaces. - var list strings.Builder - for i, w := range workspaces { - if w == workspace { - return nil - } - fmt.Fprintf(&list, "%d. %s\n", i+1, w) - } - - // If the selected workspace is not migrated, ask the user to select - // a workspace from the list of migrated workspaces. - v, err := m.UIInput().Input(&terraform.InputOpts{ - Id: "select-workspace", - Query: fmt.Sprintf( - "[reset][bold][yellow]The currently selected workspace (%s) is not migrated.[reset]", - workspace), - Description: fmt.Sprintf( - strings.TrimSpace(inputBackendSelectWorkspace), list.String()), - }) - if err != nil { - return fmt.Errorf("Error asking to select workspace: %s", err) - } - - idx, err := strconv.Atoi(v) - if err != nil || (idx < 1 || idx > len(workspaces)) { - return fmt.Errorf("Error selecting workspace: input not a valid number") - } - - return m.SetWorkspace(workspaces[idx-1]) + return nil } // Multi-state to single state. @@ -442,6 +381,17 @@ func (m *Meta) backendMigrateNonEmptyConfirm( return m.confirm(inputOpts) } +type backendMigrateOpts struct { + OneType, TwoType string + One, Two backend.Backend + + // Fields below are set internally when migrate is called + + oneEnv string // source env + twoEnv string // dest env + force bool // if true, won't ask for confirmation +} + const errMigrateLoadStates = ` Error inspecting states in the %q backend: %s @@ -464,8 +414,8 @@ above error and try again. ` const errMigrateMulti = ` -Error migrating the workspace %q from the previous %q backend -to the newly configured %q backend: +Error migrating the workspace %q from the previous %q backend to the newly +configured %q backend: %s Terraform copies workspaces in alphabetical order. Any workspaces @@ -478,22 +428,13 @@ This will attempt to copy (with permission) all workspaces again. ` const errBackendStateCopy = ` -Error copying state from the previous %q backend to the newly configured -%q backend: +Error copying state from the previous %q backend to the newly configured %q backend: %s The state in the previous backend remains intact and unmodified. Please resolve the error above and try again. ` -const errBackendNoMigratedWorkspaces = ` -No workspaces are migrated. Use the "terraform workspace" command to create -and select a new workspace. - -If the backend already contains existing workspaces, you may need to update -the workspace name or prefix in the backend configuration. -` - const inputBackendMigrateEmpty = ` Pre-existing state was found while migrating the previous %q backend to the newly configured %q backend. No existing state was found in the newly @@ -525,9 +466,9 @@ up, or cancel altogether, answer "no" and Terraform will abort. ` const inputBackendMigrateMultiToMulti = ` -Both the existing %[1]q backend and the newly configured %[2]q backend -support workspaces. When migrating between backends, Terraform will copy -all workspaces (with the same names). THIS WILL OVERWRITE any conflicting +Both the existing %[1]q backend and the newly configured %[2]q backend support +workspaces. When migrating between backends, Terraform will copy all +workspaces (with the same names). THIS WILL OVERWRITE any conflicting states in the destination. Terraform initialization doesn't currently migrate only select workspaces. @@ -537,15 +478,3 @@ pull and push those states. If you answer "yes", Terraform will migrate all states. If you answer "no", Terraform will abort. ` - -const inputBackendNewWorkspaceName = ` -Please provide a new workspace name (e.g. dev, test) that will be used -to migrate the existing default workspace. -` - -const inputBackendSelectWorkspace = ` -This is expected behavior when the selected workspace did not have an -existing non-empty state. Please enter a number to select a workspace: - -%s -` diff --git a/command/meta_backend_test.go b/command/meta_backend_test.go index a17f666e7..ab8e58e4e 100644 --- a/command/meta_backend_test.go +++ b/command/meta_backend_test.go @@ -12,8 +12,8 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/backend" - backendInit "github.com/hashicorp/terraform/backend/init" - backendLocal "github.com/hashicorp/terraform/backend/local" + backendinit "github.com/hashicorp/terraform/backend/init" + backendlocal "github.com/hashicorp/terraform/backend/local" "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/helper/copy" "github.com/hashicorp/terraform/plans" @@ -745,8 +745,8 @@ func TestMetaBackend_reconfigureChange(t *testing.T) { defer testChdir(t, td)() // Register the single-state backend - backendInit.Set("local-single", backendLocal.TestNewLocalSingle) - defer backendInit.Set("local-single", nil) + backendinit.Set("local-single", backendlocal.TestNewLocalSingle) + defer backendinit.Set("local-single", nil) // Setup the meta m := testMetaBackend(t, nil) @@ -844,11 +844,12 @@ func TestMetaBackend_configuredChangeCopy_singleState(t *testing.T) { defer testChdir(t, td)() // Register the single-state backend - backendInit.Set("local-single", backendLocal.TestNewLocalSingle) - defer backendInit.Set("local-single", nil) + backendinit.Set("local-single", backendlocal.TestNewLocalSingle) + defer backendinit.Set("local-single", nil) // Ask input defer testInputMap(t, map[string]string{ + "backend-migrate-to-new": "yes", "backend-migrate-copy-to-empty": "yes", })() @@ -899,11 +900,12 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleDefault(t *testing.T) { defer testChdir(t, td)() // Register the single-state backend - backendInit.Set("local-single", backendLocal.TestNewLocalSingle) - defer backendInit.Set("local-single", nil) + backendinit.Set("local-single", backendlocal.TestNewLocalSingle) + defer backendinit.Set("local-single", nil) // Ask input defer testInputMap(t, map[string]string{ + "backend-migrate-to-new": "yes", "backend-migrate-copy-to-empty": "yes", })() @@ -953,11 +955,12 @@ func TestMetaBackend_configuredChangeCopy_multiToSingle(t *testing.T) { defer testChdir(t, td)() // Register the single-state backend - backendInit.Set("local-single", backendLocal.TestNewLocalSingle) - defer backendInit.Set("local-single", nil) + backendinit.Set("local-single", backendlocal.TestNewLocalSingle) + defer backendinit.Set("local-single", nil) // Ask input defer testInputMap(t, map[string]string{ + "backend-migrate-to-new": "yes", "backend-migrate-multistate-to-single": "yes", "backend-migrate-copy-to-empty": "yes", })() @@ -998,7 +1001,7 @@ func TestMetaBackend_configuredChangeCopy_multiToSingle(t *testing.T) { } // Verify existing workspaces exist - envPath := filepath.Join(backendLocal.DefaultWorkspaceDir, "env2", backendLocal.DefaultStateFilename) + envPath := filepath.Join(backendlocal.DefaultWorkspaceDir, "env2", backendlocal.DefaultStateFilename) if _, err := os.Stat(envPath); err != nil { t.Fatal("env should exist") } @@ -1019,11 +1022,12 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleCurrentEnv(t *testing.T) defer testChdir(t, td)() // Register the single-state backend - backendInit.Set("local-single", backendLocal.TestNewLocalSingle) - defer backendInit.Set("local-single", nil) + backendinit.Set("local-single", backendlocal.TestNewLocalSingle) + defer backendinit.Set("local-single", nil) // Ask input defer testInputMap(t, map[string]string{ + "backend-migrate-to-new": "yes", "backend-migrate-multistate-to-single": "yes", "backend-migrate-copy-to-empty": "yes", })() @@ -1069,7 +1073,7 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleCurrentEnv(t *testing.T) } // Verify existing workspaces exist - envPath := filepath.Join(backendLocal.DefaultWorkspaceDir, "env2", backendLocal.DefaultStateFilename) + envPath := filepath.Join(backendlocal.DefaultWorkspaceDir, "env2", backendlocal.DefaultStateFilename) if _, err := os.Stat(envPath); err != nil { t.Fatal("env should exist") } @@ -1086,6 +1090,7 @@ func TestMetaBackend_configuredChangeCopy_multiToMulti(t *testing.T) { // Ask input defer testInputMap(t, map[string]string{ + "backend-migrate-to-new": "yes", "backend-migrate-multistate-to-multistate": "yes", })() @@ -1153,7 +1158,7 @@ func TestMetaBackend_configuredChangeCopy_multiToMulti(t *testing.T) { { // Verify existing workspaces exist - envPath := filepath.Join(backendLocal.DefaultWorkspaceDir, "env2", backendLocal.DefaultStateFilename) + envPath := filepath.Join(backendlocal.DefaultWorkspaceDir, "env2", backendlocal.DefaultStateFilename) if _, err := os.Stat(envPath); err != nil { t.Fatal("env should exist") } @@ -1161,159 +1166,7 @@ func TestMetaBackend_configuredChangeCopy_multiToMulti(t *testing.T) { { // Verify new workspaces exist - envPath := filepath.Join("envdir-new", "env2", backendLocal.DefaultStateFilename) - if _, err := os.Stat(envPath); err != nil { - t.Fatal("env should exist") - } - } -} - -// Changing a configured backend that supports multi-state to a -// backend that also supports multi-state, but doesn't allow a -// default state while the default state is non-empty. -func TestMetaBackend_configuredChangeCopy_multiToNoDefaultWithDefault(t *testing.T) { - // Create a temporary working directory that is empty - td := tempDir(t) - copy.CopyDir(testFixturePath("backend-change-multi-to-no-default-with-default"), td) - defer os.RemoveAll(td) - defer testChdir(t, td)() - - // Register the single-state backend - backendInit.Set("local-no-default", backendLocal.TestNewLocalNoDefault) - defer backendInit.Set("local-no-default", nil) - - // Ask input - defer testInputMap(t, map[string]string{ - "backend-migrate-multistate-to-multistate": "yes", - "new-state-name": "env1", - })() - - // Setup the meta - m := testMetaBackend(t, nil) - - // Get the backend - b, err := m.Backend(&BackendOpts{Init: true}) - if err != nil { - t.Fatalf("bad: %s", err) - } - - // Check resulting states - states, err := b.States() - if err != nil { - t.Fatalf("bad: %s", err) - } - - sort.Strings(states) - expected := []string{"env1", "env2"} - if !reflect.DeepEqual(states, expected) { - t.Fatalf("bad: %#v", states) - } - - { - // Check the renamed default state - s, err := b.State("env1") - if err != nil { - t.Fatalf("bad: %s", err) - } - if err := s.RefreshState(); err != nil { - t.Fatalf("bad: %s", err) - } - state := s.State() - if state == nil { - t.Fatal("state should not be nil") - } - if state.Lineage != "backend-change-env1" { - t.Fatalf("bad: %#v", state) - } - } - - { - // Verify existing workspaces exist - envPath := filepath.Join(backendLocal.DefaultWorkspaceDir, "env2", backendLocal.DefaultStateFilename) - if _, err := os.Stat(envPath); err != nil { - t.Fatal("env should exist") - } - } - - { - // Verify new workspaces exist - envPath := filepath.Join("envdir-new", "env2", backendLocal.DefaultStateFilename) - if _, err := os.Stat(envPath); err != nil { - t.Fatal("env should exist") - } - } -} - -// Changing a configured backend that supports multi-state to a -// backend that also supports multi-state, but doesn't allow a -// default state while the default state is empty. -func TestMetaBackend_configuredChangeCopy_multiToNoDefaultWithoutDefault(t *testing.T) { - // Create a temporary working directory that is empty - td := tempDir(t) - copy.CopyDir(testFixturePath("backend-change-multi-to-no-default-without-default"), td) - defer os.RemoveAll(td) - defer testChdir(t, td)() - - // Register the single-state backend - backendInit.Set("local-no-default", backendLocal.TestNewLocalNoDefault) - defer backendInit.Set("local-no-default", nil) - - // Ask input - defer testInputMap(t, map[string]string{ - "backend-migrate-multistate-to-multistate": "yes", - "select-workspace": "1", - })() - - // Setup the meta - m := testMetaBackend(t, nil) - - // Get the backend - b, err := m.Backend(&BackendOpts{Init: true}) - if err != nil { - t.Fatalf("bad: %s", err) - } - - // Check resulting states - states, err := b.States() - if err != nil { - t.Fatalf("bad: %s", err) - } - - sort.Strings(states) - expected := []string{"env2"} - if !reflect.DeepEqual(states, expected) { - t.Fatalf("bad: %#v", states) - } - - { - // Check the named state - s, err := b.State("env2") - if err != nil { - t.Fatalf("bad: %s", err) - } - if err := s.RefreshState(); err != nil { - t.Fatalf("bad: %s", err) - } - state := s.State() - if state == nil { - t.Fatal("state should not be nil") - } - if state.Lineage != "backend-change-env2" { - t.Fatalf("bad: %#v", state) - } - } - - { - // Verify existing workspaces exist - envPath := filepath.Join(backendLocal.DefaultWorkspaceDir, "env2", backendLocal.DefaultStateFilename) - if _, err := os.Stat(envPath); err != nil { - t.Fatal("env should exist") - } - } - - { - // Verify new workspaces exist - envPath := filepath.Join("envdir-new", "env2", backendLocal.DefaultStateFilename) + envPath := filepath.Join("envdir-new", "env2", backendlocal.DefaultStateFilename) if _, err := os.Stat(envPath); err != nil { t.Fatal("env should exist") } diff --git a/command/meta_config.go b/command/meta_config.go index 84f127a8b..061e50f43 100644 --- a/command/meta_config.go +++ b/command/meta_config.go @@ -320,7 +320,6 @@ func (m *Meta) initConfigLoader() (*configload.Loader, error) { loader, err := configload.NewLoader(&configload.Config{ ModulesDir: m.modulesDir(), Services: m.Services, - Creds: m.Credentials, }) if err != nil { return nil, err diff --git a/command/state_meta.go b/command/state_meta.go index cc66d7586..6beee5248 100644 --- a/command/state_meta.go +++ b/command/state_meta.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - backendLocal "github.com/hashicorp/terraform/backend/local" + backendlocal "github.com/hashicorp/terraform/backend/local" "github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/terraform" diff --git a/vendor/github.com/Azure/go-autorest/autorest/adal/README.md b/vendor/github.com/Azure/go-autorest/autorest/adal/README.md index 08966c9cf..a17cf98c6 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/adal/README.md +++ b/vendor/github.com/Azure/go-autorest/autorest/adal/README.md @@ -218,40 +218,6 @@ if (err == nil) { } ``` -#### Username password authenticate - -```Go -spt, err := adal.NewServicePrincipalTokenFromUsernamePassword( - oauthConfig, - applicationID, - username, - password, - resource, - callbacks...) - -if (err == nil) { - token := spt.Token -} -``` - -#### Authorization code authenticate - -``` Go -spt, err := adal.NewServicePrincipalTokenFromAuthorizationCode( - oauthConfig, - applicationID, - clientSecret, - authorizationCode, - redirectURI, - resource, - callbacks...) - -err = spt.Refresh() -if (err == nil) { - token := spt.Token -} -``` - ### Command Line Tool A command line tool is available in `cmd/adal.go` that can acquire a token for a given resource. It supports all flows mentioned above. diff --git a/vendor/github.com/Azure/go-autorest/autorest/adal/config.go b/vendor/github.com/Azure/go-autorest/autorest/adal/config.go index f570d540a..12375e0e4 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/adal/config.go +++ b/vendor/github.com/Azure/go-autorest/autorest/adal/config.go @@ -1,19 +1,5 @@ package adal -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "fmt" "net/url" @@ -32,24 +18,8 @@ type OAuthConfig struct { DeviceCodeEndpoint url.URL } -// IsZero returns true if the OAuthConfig object is zero-initialized. -func (oac OAuthConfig) IsZero() bool { - return oac == OAuthConfig{} -} - -func validateStringParam(param, name string) error { - if len(param) == 0 { - return fmt.Errorf("parameter '" + name + "' cannot be empty") - } - return nil -} - // NewOAuthConfig returns an OAuthConfig with tenant specific urls func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) { - if err := validateStringParam(activeDirectoryEndpoint, "activeDirectoryEndpoint"); err != nil { - return nil, err - } - // it's legal for tenantID to be empty so don't validate it const activeDirectoryEndpointTemplate = "%s/oauth2/%s?api-version=%s" u, err := url.Parse(activeDirectoryEndpoint) if err != nil { diff --git a/vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go b/vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go index b38f4c245..6c511f8c8 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go +++ b/vendor/github.com/Azure/go-autorest/autorest/adal/devicetoken.go @@ -1,19 +1,5 @@ package adal -// Copyright 2017 Microsoft Corporation -// -// 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. - /* This file is largely based on rjw57/oauth2device's code, with the follow differences: * scope -> resource, and only allow a single one diff --git a/vendor/github.com/Azure/go-autorest/autorest/adal/msi.go b/vendor/github.com/Azure/go-autorest/autorest/adal/msi.go deleted file mode 100644 index 5e02d52ac..000000000 --- a/vendor/github.com/Azure/go-autorest/autorest/adal/msi.go +++ /dev/null @@ -1,20 +0,0 @@ -// +build !windows - -package adal - -// Copyright 2017 Microsoft Corporation -// -// 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. - -// msiPath is the path to the MSI Extension settings file (to discover the endpoint) -var msiPath = "/var/lib/waagent/ManagedIdentity-Settings" diff --git a/vendor/github.com/Azure/go-autorest/autorest/adal/msi_windows.go b/vendor/github.com/Azure/go-autorest/autorest/adal/msi_windows.go deleted file mode 100644 index 261b56882..000000000 --- a/vendor/github.com/Azure/go-autorest/autorest/adal/msi_windows.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build windows - -package adal - -// Copyright 2017 Microsoft Corporation -// -// 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. - -import ( - "os" - "strings" -) - -// msiPath is the path to the MSI Extension settings file (to discover the endpoint) -var msiPath = strings.Join([]string{os.Getenv("SystemDrive"), "WindowsAzure/Config/ManagedIdentity-Settings"}, "/") diff --git a/vendor/github.com/Azure/go-autorest/autorest/adal/persist.go b/vendor/github.com/Azure/go-autorest/autorest/adal/persist.go index 9e15f2751..73711c667 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/adal/persist.go +++ b/vendor/github.com/Azure/go-autorest/autorest/adal/persist.go @@ -1,19 +1,5 @@ package adal -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "encoding/json" "fmt" diff --git a/vendor/github.com/Azure/go-autorest/autorest/adal/sender.go b/vendor/github.com/Azure/go-autorest/autorest/adal/sender.go index 0e5ad14d3..7928c971a 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/adal/sender.go +++ b/vendor/github.com/Azure/go-autorest/autorest/adal/sender.go @@ -1,19 +1,5 @@ package adal -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "net/http" ) diff --git a/vendor/github.com/Azure/go-autorest/autorest/adal/token.go b/vendor/github.com/Azure/go-autorest/autorest/adal/token.go index 941af281b..55361139a 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/adal/token.go +++ b/vendor/github.com/Azure/go-autorest/autorest/adal/token.go @@ -1,19 +1,5 @@ package adal -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "crypto/rand" "crypto/rsa" @@ -27,15 +13,14 @@ import ( "net/url" "strconv" "strings" - "sync" "time" - "github.com/Azure/go-autorest/autorest/date" "github.com/dgrijalva/jwt-go" ) const ( defaultRefresh = 5 * time.Minute + tokenBaseDate = "1970-01-01T00:00:00Z" // OAuthGrantTypeDeviceCode is the "grant_type" identifier used in device flow OAuthGrantTypeDeviceCode = "device_code" @@ -43,30 +28,27 @@ const ( // OAuthGrantTypeClientCredentials is the "grant_type" identifier used in credential flows OAuthGrantTypeClientCredentials = "client_credentials" - // OAuthGrantTypeUserPass is the "grant_type" identifier used in username and password auth flows - OAuthGrantTypeUserPass = "password" - // OAuthGrantTypeRefreshToken is the "grant_type" identifier used in refresh token flows OAuthGrantTypeRefreshToken = "refresh_token" - // OAuthGrantTypeAuthorizationCode is the "grant_type" identifier used in authorization code flows - OAuthGrantTypeAuthorizationCode = "authorization_code" + // managedIdentitySettingsPath is the path to the MSI Extension settings file (to discover the endpoint) + managedIdentitySettingsPath = "/var/lib/waagent/ManagedIdentity-Settings" // metadataHeader is the header required by MSI extension metadataHeader = "Metadata" ) +var expirationBase time.Time + +func init() { + expirationBase, _ = time.Parse(time.RFC3339, tokenBaseDate) +} + // OAuthTokenProvider is an interface which should be implemented by an access token retriever type OAuthTokenProvider interface { OAuthToken() string } -// TokenRefreshError is an interface used by errors returned during token refresh. -type TokenRefreshError interface { - error - Response() *http.Response -} - // Refresher is an interface for token refresh functionality type Refresher interface { Refresh() error @@ -91,21 +73,13 @@ type Token struct { Type string `json:"token_type"` } -// IsZero returns true if the token object is zero-initialized. -func (t Token) IsZero() bool { - return t == Token{} -} - // Expires returns the time.Time when the Token expires. func (t Token) Expires() time.Time { s, err := strconv.Atoi(t.ExpiresOn) if err != nil { s = -3600 } - - expiration := date.NewUnixTimeFromSeconds(float64(s)) - - return time.Time(expiration).UTC() + return expirationBase.Add(time.Duration(s) * time.Second).UTC() } // IsExpired returns true if the Token is expired, false otherwise. @@ -163,36 +137,10 @@ type ServicePrincipalCertificateSecret struct { type ServicePrincipalMSISecret struct { } -// ServicePrincipalUsernamePasswordSecret implements ServicePrincipalSecret for username and password auth. -type ServicePrincipalUsernamePasswordSecret struct { - Username string - Password string -} - -// ServicePrincipalAuthorizationCodeSecret implements ServicePrincipalSecret for authorization code auth. -type ServicePrincipalAuthorizationCodeSecret struct { - ClientSecret string - AuthorizationCode string - RedirectURI string -} - -// SetAuthenticationValues is a method of the interface ServicePrincipalSecret. -func (secret *ServicePrincipalAuthorizationCodeSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error { - v.Set("code", secret.AuthorizationCode) - v.Set("client_secret", secret.ClientSecret) - v.Set("redirect_uri", secret.RedirectURI) - return nil -} - -// SetAuthenticationValues is a method of the interface ServicePrincipalSecret. -func (secret *ServicePrincipalUsernamePasswordSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error { - v.Set("username", secret.Username) - v.Set("password", secret.Password) - return nil -} - // SetAuthenticationValues is a method of the interface ServicePrincipalSecret. +// MSI extension requires the authority field to be set to the real tenant authority endpoint func (msiSecret *ServicePrincipalMSISecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error { + v.Set("authority", spt.oauthConfig.AuthorityEndpoint.String()) return nil } @@ -245,46 +193,25 @@ func (secret *ServicePrincipalCertificateSecret) SetAuthenticationValues(spt *Se type ServicePrincipalToken struct { Token - secret ServicePrincipalSecret - oauthConfig OAuthConfig - clientID string - resource string - autoRefresh bool - autoRefreshLock *sync.Mutex - refreshWithin time.Duration - sender Sender + secret ServicePrincipalSecret + oauthConfig OAuthConfig + clientID string + resource string + autoRefresh bool + refreshWithin time.Duration + sender Sender refreshCallbacks []TokenRefreshCallback } -func validateOAuthConfig(oac OAuthConfig) error { - if oac.IsZero() { - return fmt.Errorf("parameter 'oauthConfig' cannot be zero-initialized") - } - return nil -} - // NewServicePrincipalTokenWithSecret create a ServicePrincipalToken using the supplied ServicePrincipalSecret implementation. func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, resource string, secret ServicePrincipalSecret, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - if err := validateOAuthConfig(oauthConfig); err != nil { - return nil, err - } - if err := validateStringParam(id, "id"); err != nil { - return nil, err - } - if err := validateStringParam(resource, "resource"); err != nil { - return nil, err - } - if secret == nil { - return nil, fmt.Errorf("parameter 'secret' cannot be nil") - } spt := &ServicePrincipalToken{ oauthConfig: oauthConfig, secret: secret, clientID: id, resource: resource, autoRefresh: true, - autoRefreshLock: &sync.Mutex{}, refreshWithin: defaultRefresh, sender: &http.Client{}, refreshCallbacks: callbacks, @@ -294,18 +221,6 @@ func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, reso // NewServicePrincipalTokenFromManualToken creates a ServicePrincipalToken using the supplied token func NewServicePrincipalTokenFromManualToken(oauthConfig OAuthConfig, clientID string, resource string, token Token, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - if err := validateOAuthConfig(oauthConfig); err != nil { - return nil, err - } - if err := validateStringParam(clientID, "clientID"); err != nil { - return nil, err - } - if err := validateStringParam(resource, "resource"); err != nil { - return nil, err - } - if token.IsZero() { - return nil, fmt.Errorf("parameter 'token' cannot be zero-initialized") - } spt, err := NewServicePrincipalTokenWithSecret( oauthConfig, clientID, @@ -324,18 +239,6 @@ func NewServicePrincipalTokenFromManualToken(oauthConfig OAuthConfig, clientID s // NewServicePrincipalToken creates a ServicePrincipalToken from the supplied Service Principal // credentials scoped to the named resource. func NewServicePrincipalToken(oauthConfig OAuthConfig, clientID string, secret string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - if err := validateOAuthConfig(oauthConfig); err != nil { - return nil, err - } - if err := validateStringParam(clientID, "clientID"); err != nil { - return nil, err - } - if err := validateStringParam(secret, "secret"); err != nil { - return nil, err - } - if err := validateStringParam(resource, "resource"); err != nil { - return nil, err - } return NewServicePrincipalTokenWithSecret( oauthConfig, clientID, @@ -347,23 +250,8 @@ func NewServicePrincipalToken(oauthConfig OAuthConfig, clientID string, secret s ) } -// NewServicePrincipalTokenFromCertificate creates a ServicePrincipalToken from the supplied pkcs12 bytes. +// NewServicePrincipalTokenFromCertificate create a ServicePrincipalToken from the supplied pkcs12 bytes. func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID string, certificate *x509.Certificate, privateKey *rsa.PrivateKey, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - if err := validateOAuthConfig(oauthConfig); err != nil { - return nil, err - } - if err := validateStringParam(clientID, "clientID"); err != nil { - return nil, err - } - if err := validateStringParam(resource, "resource"); err != nil { - return nil, err - } - if certificate == nil { - return nil, fmt.Errorf("parameter 'certificate' cannot be nil") - } - if privateKey == nil { - return nil, fmt.Errorf("parameter 'privateKey' cannot be nil") - } return NewServicePrincipalTokenWithSecret( oauthConfig, clientID, @@ -376,175 +264,57 @@ func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID s ) } -// NewServicePrincipalTokenFromUsernamePassword creates a ServicePrincipalToken from the username and password. -func NewServicePrincipalTokenFromUsernamePassword(oauthConfig OAuthConfig, clientID string, username string, password string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - if err := validateOAuthConfig(oauthConfig); err != nil { - return nil, err - } - if err := validateStringParam(clientID, "clientID"); err != nil { - return nil, err - } - if err := validateStringParam(username, "username"); err != nil { - return nil, err - } - if err := validateStringParam(password, "password"); err != nil { - return nil, err - } - if err := validateStringParam(resource, "resource"); err != nil { - return nil, err - } - return NewServicePrincipalTokenWithSecret( - oauthConfig, - clientID, - resource, - &ServicePrincipalUsernamePasswordSecret{ - Username: username, - Password: password, - }, - callbacks..., - ) +// NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension. +func NewServicePrincipalTokenFromMSI(oauthConfig OAuthConfig, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { + return newServicePrincipalTokenFromMSI(oauthConfig, resource, managedIdentitySettingsPath, callbacks...) } -// NewServicePrincipalTokenFromAuthorizationCode creates a ServicePrincipalToken from the -func NewServicePrincipalTokenFromAuthorizationCode(oauthConfig OAuthConfig, clientID string, clientSecret string, authorizationCode string, redirectURI string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - - if err := validateOAuthConfig(oauthConfig); err != nil { - return nil, err - } - if err := validateStringParam(clientID, "clientID"); err != nil { - return nil, err - } - if err := validateStringParam(clientSecret, "clientSecret"); err != nil { - return nil, err - } - if err := validateStringParam(authorizationCode, "authorizationCode"); err != nil { - return nil, err - } - if err := validateStringParam(redirectURI, "redirectURI"); err != nil { - return nil, err - } - if err := validateStringParam(resource, "resource"); err != nil { - return nil, err - } - - return NewServicePrincipalTokenWithSecret( - oauthConfig, - clientID, - resource, - &ServicePrincipalAuthorizationCodeSecret{ - ClientSecret: clientSecret, - AuthorizationCode: authorizationCode, - RedirectURI: redirectURI, - }, - callbacks..., - ) -} - -// GetMSIVMEndpoint gets the MSI endpoint on Virtual Machines. -func GetMSIVMEndpoint() (string, error) { - return getMSIVMEndpoint(msiPath) -} - -func getMSIVMEndpoint(path string) (string, error) { +func newServicePrincipalTokenFromMSI(oauthConfig OAuthConfig, resource, settingsPath string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { // Read MSI settings - bytes, err := ioutil.ReadFile(path) + bytes, err := ioutil.ReadFile(settingsPath) if err != nil { - return "", err + return nil, err } msiSettings := struct { URL string `json:"url"` }{} err = json.Unmarshal(bytes, &msiSettings) if err != nil { - return "", err - } - - return msiSettings.URL, nil -} - -// NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension. -// It will use the system assigned identity when creating the token. -func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - return newServicePrincipalTokenFromMSI(msiEndpoint, resource, nil, callbacks...) -} - -// NewServicePrincipalTokenFromMSIWithUserAssignedID creates a ServicePrincipalToken via the MSI VM Extension. -// It will use the specified user assigned identity when creating the token. -func NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, resource string, userAssignedID string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - return newServicePrincipalTokenFromMSI(msiEndpoint, resource, &userAssignedID, callbacks...) -} - -func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedID *string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - if err := validateStringParam(msiEndpoint, "msiEndpoint"); err != nil { return nil, err } - if err := validateStringParam(resource, "resource"); err != nil { - return nil, err - } - if userAssignedID != nil { - if err := validateStringParam(*userAssignedID, "userAssignedID"); err != nil { - return nil, err - } - } + // We set the oauth config token endpoint to be MSI's endpoint - msiEndpointURL, err := url.Parse(msiEndpoint) + // We leave the authority as-is so MSI can POST it with the token request + msiEndpointURL, err := url.Parse(msiSettings.URL) if err != nil { return nil, err } - oauthConfig, err := NewOAuthConfig(msiEndpointURL.String(), "") + msiTokenEndpointURL, err := msiEndpointURL.Parse("/oauth2/token") if err != nil { return nil, err } + oauthConfig.TokenEndpoint = *msiTokenEndpointURL + spt := &ServicePrincipalToken{ - oauthConfig: *oauthConfig, + oauthConfig: oauthConfig, secret: &ServicePrincipalMSISecret{}, resource: resource, autoRefresh: true, - autoRefreshLock: &sync.Mutex{}, refreshWithin: defaultRefresh, sender: &http.Client{}, refreshCallbacks: callbacks, } - if userAssignedID != nil { - spt.clientID = *userAssignedID - } - return spt, nil } -// internal type that implements TokenRefreshError -type tokenRefreshError struct { - message string - resp *http.Response -} - -// Error implements the error interface which is part of the TokenRefreshError interface. -func (tre tokenRefreshError) Error() string { - return tre.message -} - -// Response implements the TokenRefreshError interface, it returns the raw HTTP response from the refresh operation. -func (tre tokenRefreshError) Response() *http.Response { - return tre.resp -} - -func newTokenRefreshError(message string, resp *http.Response) TokenRefreshError { - return tokenRefreshError{message: message, resp: resp} -} - // EnsureFresh will refresh the token if it will expire within the refresh window (as set by -// RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use. +// RefreshWithin) and autoRefresh flag is on. func (spt *ServicePrincipalToken) EnsureFresh() error { if spt.autoRefresh && spt.WillExpireIn(spt.refreshWithin) { - // take the lock then check to see if the token was already refreshed - spt.autoRefreshLock.Lock() - defer spt.autoRefreshLock.Unlock() - if spt.WillExpireIn(spt.refreshWithin) { - return spt.Refresh() - } + return spt.Refresh() } return nil } @@ -563,28 +333,15 @@ func (spt *ServicePrincipalToken) InvokeRefreshCallbacks(token Token) error { } // Refresh obtains a fresh token for the Service Principal. -// This method is not safe for concurrent use and should be syncrhonized. func (spt *ServicePrincipalToken) Refresh() error { return spt.refreshInternal(spt.resource) } // RefreshExchange refreshes the token, but for a different resource. -// This method is not safe for concurrent use and should be syncrhonized. func (spt *ServicePrincipalToken) RefreshExchange(resource string) error { return spt.refreshInternal(resource) } -func (spt *ServicePrincipalToken) getGrantType() string { - switch spt.secret.(type) { - case *ServicePrincipalUsernamePasswordSecret: - return OAuthGrantTypeUserPass - case *ServicePrincipalAuthorizationCodeSecret: - return OAuthGrantTypeAuthorizationCode - default: - return OAuthGrantTypeClientCredentials - } -} - func (spt *ServicePrincipalToken) refreshInternal(resource string) error { v := url.Values{} v.Set("client_id", spt.clientID) @@ -594,7 +351,7 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error { v.Set("grant_type", OAuthGrantTypeRefreshToken) v.Set("refresh_token", spt.RefreshToken) } else { - v.Set("grant_type", spt.getGrantType()) + v.Set("grant_type", OAuthGrantTypeClientCredentials) err := spt.secret.SetAuthenticationValues(spt, &v) if err != nil { return err @@ -617,17 +374,12 @@ func (spt *ServicePrincipalToken) refreshInternal(resource string) error { if err != nil { return fmt.Errorf("adal: Failed to execute the refresh request. Error = '%v'", err) } - defer resp.Body.Close() - rb, err := ioutil.ReadAll(resp.Body) - if resp.StatusCode != http.StatusOK { - if err != nil { - return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body", resp.StatusCode), resp) - } - return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb)), resp) + return fmt.Errorf("adal: Refresh request failed. Status Code = '%d'", resp.StatusCode) } + rb, err := ioutil.ReadAll(resp.Body) if err != nil { return fmt.Errorf("adal: Failed to read a new service principal token during refresh. Error = '%v'", err) } diff --git a/vendor/github.com/Azure/go-autorest/autorest/authorization.go b/vendor/github.com/Azure/go-autorest/autorest/authorization.go index 4a602f676..314ed7876 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/authorization.go +++ b/vendor/github.com/Azure/go-autorest/autorest/authorization.go @@ -1,19 +1,5 @@ package autorest -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "fmt" "net/http" @@ -24,12 +10,9 @@ import ( ) const ( - bearerChallengeHeader = "Www-Authenticate" - bearer = "Bearer" - tenantID = "tenantID" - apiKeyAuthorizerHeader = "Ocp-Apim-Subscription-Key" - bingAPISdkHeader = "X-BingApis-SDK-Client" - golangBingAPISdkHeaderValue = "Go-SDK" + bearerChallengeHeader = "Www-Authenticate" + bearer = "Bearer" + tenantID = "tenantID" ) // Authorizer is the interface that provides a PrepareDecorator used to supply request @@ -47,53 +30,6 @@ func (na NullAuthorizer) WithAuthorization() PrepareDecorator { return WithNothing() } -// APIKeyAuthorizer implements API Key authorization. -type APIKeyAuthorizer struct { - headers map[string]interface{} - queryParameters map[string]interface{} -} - -// NewAPIKeyAuthorizerWithHeaders creates an ApiKeyAuthorizer with headers. -func NewAPIKeyAuthorizerWithHeaders(headers map[string]interface{}) *APIKeyAuthorizer { - return NewAPIKeyAuthorizer(headers, nil) -} - -// NewAPIKeyAuthorizerWithQueryParameters creates an ApiKeyAuthorizer with query parameters. -func NewAPIKeyAuthorizerWithQueryParameters(queryParameters map[string]interface{}) *APIKeyAuthorizer { - return NewAPIKeyAuthorizer(nil, queryParameters) -} - -// NewAPIKeyAuthorizer creates an ApiKeyAuthorizer with headers. -func NewAPIKeyAuthorizer(headers map[string]interface{}, queryParameters map[string]interface{}) *APIKeyAuthorizer { - return &APIKeyAuthorizer{headers: headers, queryParameters: queryParameters} -} - -// WithAuthorization returns a PrepareDecorator that adds an HTTP headers and Query Paramaters -func (aka *APIKeyAuthorizer) WithAuthorization() PrepareDecorator { - return func(p Preparer) Preparer { - return DecoratePreparer(p, WithHeaders(aka.headers), WithQueryParameters(aka.queryParameters)) - } -} - -// CognitiveServicesAuthorizer implements authorization for Cognitive Services. -type CognitiveServicesAuthorizer struct { - subscriptionKey string -} - -// NewCognitiveServicesAuthorizer is -func NewCognitiveServicesAuthorizer(subscriptionKey string) *CognitiveServicesAuthorizer { - return &CognitiveServicesAuthorizer{subscriptionKey: subscriptionKey} -} - -// WithAuthorization is -func (csa *CognitiveServicesAuthorizer) WithAuthorization() PrepareDecorator { - headers := make(map[string]interface{}) - headers[apiKeyAuthorizerHeader] = csa.subscriptionKey - headers[bingAPISdkHeader] = golangBingAPISdkHeaderValue - - return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization() -} - // BearerAuthorizer implements the bearer authorization type BearerAuthorizer struct { tokenProvider adal.OAuthTokenProvider @@ -119,11 +55,7 @@ func (ba *BearerAuthorizer) WithAuthorization() PrepareDecorator { if ok { err := refresher.EnsureFresh() if err != nil { - var resp *http.Response - if tokError, ok := err.(adal.TokenRefreshError); ok { - resp = tokError.Response() - } - return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", resp, + return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", nil, "Failed to refresh the Token for request to %s", r.URL) } } @@ -233,22 +165,3 @@ func newBearerChallenge(resp *http.Response) (bc bearerChallenge, err error) { return bc, err } - -// EventGridKeyAuthorizer implements authorization for event grid using key authentication. -type EventGridKeyAuthorizer struct { - topicKey string -} - -// NewEventGridKeyAuthorizer creates a new EventGridKeyAuthorizer -// with the specified topic key. -func NewEventGridKeyAuthorizer(topicKey string) EventGridKeyAuthorizer { - return EventGridKeyAuthorizer{topicKey: topicKey} -} - -// WithAuthorization returns a PrepareDecorator that adds the aeg-sas-key authentication header. -func (egta EventGridKeyAuthorizer) WithAuthorization() PrepareDecorator { - headers := map[string]interface{}{ - "aeg-sas-key": egta.topicKey, - } - return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization() -} diff --git a/vendor/github.com/Azure/go-autorest/autorest/autorest.go b/vendor/github.com/Azure/go-autorest/autorest/autorest.go index f86b66a41..51f1c4bbc 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/autorest.go +++ b/vendor/github.com/Azure/go-autorest/autorest/autorest.go @@ -57,20 +57,6 @@ generated clients, see the Client described below. */ package autorest -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "net/http" "time" @@ -87,9 +73,6 @@ const ( // ResponseHasStatusCode returns true if the status code in the HTTP Response is in the passed set // and false otherwise. func ResponseHasStatusCode(resp *http.Response, codes ...int) bool { - if resp == nil { - return false - } return containsInt(codes, resp.StatusCode) } diff --git a/vendor/github.com/Azure/go-autorest/autorest/azure/async.go b/vendor/github.com/Azure/go-autorest/autorest/azure/async.go index 366fc5379..332a8909d 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/azure/async.go +++ b/vendor/github.com/Azure/go-autorest/autorest/azure/async.go @@ -1,23 +1,7 @@ package azure -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "bytes" - "context" - "encoding/json" "fmt" "io/ioutil" "net/http" @@ -39,152 +23,6 @@ const ( operationSucceeded string = "Succeeded" ) -var pollingCodes = [...]int{http.StatusNoContent, http.StatusAccepted, http.StatusCreated, http.StatusOK} - -// Future provides a mechanism to access the status and results of an asynchronous request. -// Since futures are stateful they should be passed by value to avoid race conditions. -type Future struct { - req *http.Request - resp *http.Response - ps pollingState -} - -// NewFuture returns a new Future object initialized with the specified request. -func NewFuture(req *http.Request) Future { - return Future{req: req} -} - -// Response returns the last HTTP response or nil if there isn't one. -func (f Future) Response() *http.Response { - return f.resp -} - -// Status returns the last status message of the operation. -func (f Future) Status() string { - if f.ps.State == "" { - return "Unknown" - } - return f.ps.State -} - -// PollingMethod returns the method used to monitor the status of the asynchronous operation. -func (f Future) PollingMethod() PollingMethodType { - return f.ps.PollingMethod -} - -// Done queries the service to see if the operation has completed. -func (f *Future) Done(sender autorest.Sender) (bool, error) { - // exit early if this future has terminated - if f.ps.hasTerminated() { - return true, f.errorInfo() - } - - resp, err := sender.Do(f.req) - f.resp = resp - if err != nil || !autorest.ResponseHasStatusCode(resp, pollingCodes[:]...) { - return false, err - } - - err = updatePollingState(resp, &f.ps) - if err != nil { - return false, err - } - - if f.ps.hasTerminated() { - return true, f.errorInfo() - } - - f.req, err = newPollingRequest(f.ps) - return false, err -} - -// GetPollingDelay returns a duration the application should wait before checking -// the status of the asynchronous request and true; this value is returned from -// the service via the Retry-After response header. If the header wasn't returned -// then the function returns the zero-value time.Duration and false. -func (f Future) GetPollingDelay() (time.Duration, bool) { - if f.resp == nil { - return 0, false - } - - retry := f.resp.Header.Get(autorest.HeaderRetryAfter) - if retry == "" { - return 0, false - } - - d, err := time.ParseDuration(retry + "s") - if err != nil { - panic(err) - } - - return d, true -} - -// WaitForCompletion will return when one of the following conditions is met: the long -// running operation has completed, the provided context is cancelled, or the client's -// polling duration has been exceeded. It will retry failed polling attempts based on -// the retry value defined in the client up to the maximum retry attempts. -func (f Future) WaitForCompletion(ctx context.Context, client autorest.Client) error { - ctx, cancel := context.WithTimeout(ctx, client.PollingDuration) - defer cancel() - - done, err := f.Done(client) - for attempts := 0; !done; done, err = f.Done(client) { - if attempts >= client.RetryAttempts { - return autorest.NewErrorWithError(err, "azure", "WaitForCompletion", f.resp, "the number of retries has been exceeded") - } - // we want delayAttempt to be zero in the non-error case so - // that DelayForBackoff doesn't perform exponential back-off - var delayAttempt int - var delay time.Duration - if err == nil { - // check for Retry-After delay, if not present use the client's polling delay - var ok bool - delay, ok = f.GetPollingDelay() - if !ok { - delay = client.PollingDelay - } - } else { - // there was an error polling for status so perform exponential - // back-off based on the number of attempts using the client's retry - // duration. update attempts after delayAttempt to avoid off-by-one. - delayAttempt = attempts - delay = client.RetryDuration - attempts++ - } - // wait until the delay elapses or the context is cancelled - delayElapsed := autorest.DelayForBackoff(delay, delayAttempt, ctx.Done()) - if !delayElapsed { - return autorest.NewErrorWithError(ctx.Err(), "azure", "WaitForCompletion", f.resp, "context has been cancelled") - } - } - return err -} - -// if the operation failed the polling state will contain -// error information and implements the error interface -func (f *Future) errorInfo() error { - if !f.ps.hasSucceeded() { - return f.ps - } - return nil -} - -// MarshalJSON implements the json.Marshaler interface. -func (f Future) MarshalJSON() ([]byte, error) { - return json.Marshal(&f.ps) -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -func (f *Future) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, &f.ps) - if err != nil { - return err - } - f.req, err = newPollingRequest(f.ps) - return err -} - // DoPollForAsynchronous returns a SendDecorator that polls if the http.Response is for an Azure // long-running operation. It will delay between requests for the duration specified in the // RetryAfter header or, if the header is absent, the passed delay. Polling may be canceled by @@ -196,7 +34,8 @@ func DoPollForAsynchronous(delay time.Duration) autorest.SendDecorator { if err != nil { return resp, err } - if !autorest.ResponseHasStatusCode(resp, pollingCodes[:]...) { + pollingCodes := []int{http.StatusAccepted, http.StatusCreated, http.StatusOK} + if !autorest.ResponseHasStatusCode(resp, pollingCodes...) { return resp, nil } @@ -213,11 +52,10 @@ func DoPollForAsynchronous(delay time.Duration) autorest.SendDecorator { break } - r, err = newPollingRequest(ps) + r, err = newPollingRequest(resp, ps) if err != nil { return resp, err } - r.Cancel = resp.Request.Cancel delay = autorest.GetRetryAfter(resp, delay) resp, err = autorest.SendWithSender(s, r, @@ -234,15 +72,20 @@ func getAsyncOperation(resp *http.Response) string { } func hasSucceeded(state string) bool { - return strings.EqualFold(state, operationSucceeded) + return state == operationSucceeded } func hasTerminated(state string) bool { - return strings.EqualFold(state, operationCanceled) || strings.EqualFold(state, operationFailed) || strings.EqualFold(state, operationSucceeded) + switch state { + case operationCanceled, operationFailed, operationSucceeded: + return true + default: + return false + } } func hasFailed(state string) bool { - return strings.EqualFold(state, operationFailed) + return state == operationFailed } type provisioningTracker interface { @@ -303,42 +146,36 @@ func (ps provisioningStatus) hasProvisioningError() bool { return ps.ProvisioningError != ServiceError{} } -// PollingMethodType defines a type used for enumerating polling mechanisms. -type PollingMethodType string +type pollingResponseFormat string const ( - // PollingAsyncOperation indicates the polling method uses the Azure-AsyncOperation header. - PollingAsyncOperation PollingMethodType = "AsyncOperation" - - // PollingLocation indicates the polling method uses the Location header. - PollingLocation PollingMethodType = "Location" - - // PollingUnknown indicates an unknown polling method and is the default value. - PollingUnknown PollingMethodType = "" + usesOperationResponse pollingResponseFormat = "OperationResponse" + usesProvisioningStatus pollingResponseFormat = "ProvisioningStatus" + formatIsUnknown pollingResponseFormat = "" ) type pollingState struct { - PollingMethod PollingMethodType `json:"pollingMethod"` - URI string `json:"uri"` - State string `json:"state"` - Code string `json:"code"` - Message string `json:"message"` + responseFormat pollingResponseFormat + uri string + state string + code string + message string } func (ps pollingState) hasSucceeded() bool { - return hasSucceeded(ps.State) + return hasSucceeded(ps.state) } func (ps pollingState) hasTerminated() bool { - return hasTerminated(ps.State) + return hasTerminated(ps.state) } func (ps pollingState) hasFailed() bool { - return hasFailed(ps.State) + return hasFailed(ps.state) } func (ps pollingState) Error() string { - return fmt.Sprintf("Long running operation terminated with status '%s': Code=%q Message=%q", ps.State, ps.Code, ps.Message) + return fmt.Sprintf("Long running operation terminated with status '%s': Code=%q Message=%q", ps.state, ps.code, ps.message) } // updatePollingState maps the operation status -- retrieved from either a provisioningState @@ -353,7 +190,7 @@ func updatePollingState(resp *http.Response, ps *pollingState) error { // -- The first response will always be a provisioningStatus response; only the polling requests, // depending on the header returned, may be something otherwise. var pt provisioningTracker - if ps.PollingMethod == PollingAsyncOperation { + if ps.responseFormat == usesOperationResponse { pt = &operationResource{} } else { pt = &provisioningStatus{} @@ -361,30 +198,30 @@ func updatePollingState(resp *http.Response, ps *pollingState) error { // If this is the first request (that is, the polling response shape is unknown), determine how // to poll and what to expect - if ps.PollingMethod == PollingUnknown { + if ps.responseFormat == formatIsUnknown { req := resp.Request if req == nil { return autorest.NewError("azure", "updatePollingState", "Azure Polling Error - Original HTTP request is missing") } // Prefer the Azure-AsyncOperation header - ps.URI = getAsyncOperation(resp) - if ps.URI != "" { - ps.PollingMethod = PollingAsyncOperation + ps.uri = getAsyncOperation(resp) + if ps.uri != "" { + ps.responseFormat = usesOperationResponse } else { - ps.PollingMethod = PollingLocation + ps.responseFormat = usesProvisioningStatus } // Else, use the Location header - if ps.URI == "" { - ps.URI = autorest.GetLocation(resp) + if ps.uri == "" { + ps.uri = autorest.GetLocation(resp) } // Lastly, requests against an existing resource, use the last request URI - if ps.URI == "" { + if ps.uri == "" { m := strings.ToUpper(req.Method) if m == http.MethodPatch || m == http.MethodPut || m == http.MethodGet { - ps.URI = req.URL.String() + ps.uri = req.URL.String() } } } @@ -405,23 +242,23 @@ func updatePollingState(resp *http.Response, ps *pollingState) error { // -- Unknown states are per-service inprogress states // -- Otherwise, infer state from HTTP status code if pt.hasTerminated() { - ps.State = pt.state() + ps.state = pt.state() } else if pt.state() != "" { - ps.State = operationInProgress + ps.state = operationInProgress } else { switch resp.StatusCode { case http.StatusAccepted: - ps.State = operationInProgress + ps.state = operationInProgress case http.StatusNoContent, http.StatusCreated, http.StatusOK: - ps.State = operationSucceeded + ps.state = operationSucceeded default: - ps.State = operationFailed + ps.state = operationFailed } } - if strings.EqualFold(ps.State, operationInProgress) && ps.URI == "" { + if ps.state == operationInProgress && ps.uri == "" { return autorest.NewError("azure", "updatePollingState", "Azure Polling Error - Unable to obtain polling URI for %s %s", resp.Request.Method, resp.Request.URL) } @@ -430,49 +267,36 @@ func updatePollingState(resp *http.Response, ps *pollingState) error { // -- Response // -- Otherwise, Unknown if ps.hasFailed() { - if ps.PollingMethod == PollingAsyncOperation { + if ps.responseFormat == usesOperationResponse { or := pt.(*operationResource) - ps.Code = or.OperationError.Code - ps.Message = or.OperationError.Message + ps.code = or.OperationError.Code + ps.message = or.OperationError.Message } else { p := pt.(*provisioningStatus) if p.hasProvisioningError() { - ps.Code = p.ProvisioningError.Code - ps.Message = p.ProvisioningError.Message + ps.code = p.ProvisioningError.Code + ps.message = p.ProvisioningError.Message } else { - ps.Code = "Unknown" - ps.Message = "None" + ps.code = "Unknown" + ps.message = "None" } } } return nil } -func newPollingRequest(ps pollingState) (*http.Request, error) { - reqPoll, err := autorest.Prepare(&http.Request{}, +func newPollingRequest(resp *http.Response, ps pollingState) (*http.Request, error) { + req := resp.Request + if req == nil { + return nil, autorest.NewError("azure", "newPollingRequest", "Azure Polling Error - Original HTTP request is missing") + } + + reqPoll, err := autorest.Prepare(&http.Request{Cancel: req.Cancel}, autorest.AsGet(), - autorest.WithBaseURL(ps.URI)) + autorest.WithBaseURL(ps.uri)) if err != nil { - return nil, autorest.NewErrorWithError(err, "azure", "newPollingRequest", nil, "Failure creating poll request to %s", ps.URI) + return nil, autorest.NewErrorWithError(err, "azure", "newPollingRequest", nil, "Failure creating poll request to %s", ps.uri) } return reqPoll, nil } - -// AsyncOpIncompleteError is the type that's returned from a future that has not completed. -type AsyncOpIncompleteError struct { - // FutureType is the name of the type composed of a azure.Future. - FutureType string -} - -// Error returns an error message including the originating type name of the error. -func (e AsyncOpIncompleteError) Error() string { - return fmt.Sprintf("%s: asynchronous operation has not completed", e.FutureType) -} - -// NewAsyncOpIncompleteError creates a new AsyncOpIncompleteError with the specified parameters. -func NewAsyncOpIncompleteError(futureType string) AsyncOpIncompleteError { - return AsyncOpIncompleteError{ - FutureType: futureType, - } -} diff --git a/vendor/github.com/Azure/go-autorest/autorest/azure/azure.go b/vendor/github.com/Azure/go-autorest/autorest/azure/azure.go index fa1835647..3f4d13421 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/azure/azure.go +++ b/vendor/github.com/Azure/go-autorest/autorest/azure/azure.go @@ -5,20 +5,6 @@ See the included examples for more detail. */ package azure -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "encoding/json" "fmt" @@ -179,13 +165,7 @@ func WithErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator { if decodeErr != nil { return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b.String(), decodeErr) } else if e.ServiceError == nil { - // Check if error is unwrapped ServiceError - if err := json.Unmarshal(b.Bytes(), &e.ServiceError); err != nil || e.ServiceError.Message == "" { - e.ServiceError = &ServiceError{ - Code: "Unknown", - Message: "Unknown service error", - } - } + e.ServiceError = &ServiceError{Code: "Unknown", Message: "Unknown service error"} } e.RequestID = ExtractRequestID(resp) diff --git a/vendor/github.com/Azure/go-autorest/autorest/azure/environments.go b/vendor/github.com/Azure/go-autorest/autorest/azure/environments.go index 0916b14f3..1cf55651f 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/azure/environments.go +++ b/vendor/github.com/Azure/go-autorest/autorest/azure/environments.go @@ -1,31 +1,10 @@ package azure -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( - "encoding/json" "fmt" - "io/ioutil" - "os" "strings" ) -// EnvironmentFilepathName captures the name of the environment variable containing the path to the file -// to be used while populating the Azure Environment. -const EnvironmentFilepathName = "AZURE_ENVIRONMENT_FILEPATH" - var environments = map[string]Environment{ "AZURECHINACLOUD": ChinaCloud, "AZUREGERMANCLOUD": GermanCloud, @@ -44,7 +23,6 @@ type Environment struct { GalleryEndpoint string `json:"galleryEndpoint"` KeyVaultEndpoint string `json:"keyVaultEndpoint"` GraphEndpoint string `json:"graphEndpoint"` - ServiceBusEndpoint string `json:"serviceBusEndpoint"` StorageEndpointSuffix string `json:"storageEndpointSuffix"` SQLDatabaseDNSSuffix string `json:"sqlDatabaseDNSSuffix"` TrafficManagerDNSSuffix string `json:"trafficManagerDNSSuffix"` @@ -67,12 +45,11 @@ var ( GalleryEndpoint: "https://gallery.azure.com/", KeyVaultEndpoint: "https://vault.azure.net/", GraphEndpoint: "https://graph.windows.net/", - ServiceBusEndpoint: "https://servicebus.windows.net/", StorageEndpointSuffix: "core.windows.net", SQLDatabaseDNSSuffix: "database.windows.net", TrafficManagerDNSSuffix: "trafficmanager.net", KeyVaultDNSSuffix: "vault.azure.net", - ServiceBusEndpointSuffix: "servicebus.windows.net", + ServiceBusEndpointSuffix: "servicebus.azure.com", ServiceManagementVMDNSSuffix: "cloudapp.net", ResourceManagerVMDNSSuffix: "cloudapp.azure.com", ContainerRegistryDNSSuffix: "azurecr.io", @@ -85,11 +62,10 @@ var ( PublishSettingsURL: "https://manage.windowsazure.us/publishsettings/index", ServiceManagementEndpoint: "https://management.core.usgovcloudapi.net/", ResourceManagerEndpoint: "https://management.usgovcloudapi.net/", - ActiveDirectoryEndpoint: "https://login.microsoftonline.us/", + ActiveDirectoryEndpoint: "https://login.microsoftonline.com/", GalleryEndpoint: "https://gallery.usgovcloudapi.net/", KeyVaultEndpoint: "https://vault.usgovcloudapi.net/", - GraphEndpoint: "https://graph.windows.net/", - ServiceBusEndpoint: "https://servicebus.usgovcloudapi.net/", + GraphEndpoint: "https://graph.usgovcloudapi.net/", StorageEndpointSuffix: "core.usgovcloudapi.net", SQLDatabaseDNSSuffix: "database.usgovcloudapi.net", TrafficManagerDNSSuffix: "usgovtrafficmanager.net", @@ -111,12 +87,11 @@ var ( GalleryEndpoint: "https://gallery.chinacloudapi.cn/", KeyVaultEndpoint: "https://vault.azure.cn/", GraphEndpoint: "https://graph.chinacloudapi.cn/", - ServiceBusEndpoint: "https://servicebus.chinacloudapi.cn/", StorageEndpointSuffix: "core.chinacloudapi.cn", SQLDatabaseDNSSuffix: "database.chinacloudapi.cn", TrafficManagerDNSSuffix: "trafficmanager.cn", KeyVaultDNSSuffix: "vault.azure.cn", - ServiceBusEndpointSuffix: "servicebus.chinacloudapi.cn", + ServiceBusEndpointSuffix: "servicebus.chinacloudapi.net", ServiceManagementVMDNSSuffix: "chinacloudapp.cn", ResourceManagerVMDNSSuffix: "cloudapp.azure.cn", ContainerRegistryDNSSuffix: "azurecr.io", @@ -133,7 +108,6 @@ var ( GalleryEndpoint: "https://gallery.cloudapi.de/", KeyVaultEndpoint: "https://vault.microsoftazure.de/", GraphEndpoint: "https://graph.cloudapi.de/", - ServiceBusEndpoint: "https://servicebus.cloudapi.de/", StorageEndpointSuffix: "core.cloudapi.de", SQLDatabaseDNSSuffix: "database.cloudapi.de", TrafficManagerDNSSuffix: "azuretrafficmanager.de", @@ -145,37 +119,12 @@ var ( } ) -// EnvironmentFromName returns an Environment based on the common name specified. +// EnvironmentFromName returns an Environment based on the common name specified func EnvironmentFromName(name string) (Environment, error) { - // IMPORTANT - // As per @radhikagupta5: - // This is technical debt, fundamentally here because Kubernetes is not currently accepting - // contributions to the providers. Once that is an option, the provider should be updated to - // directly call `EnvironmentFromFile`. Until then, we rely on dispatching Azure Stack environment creation - // from this method based on the name that is provided to us. - if strings.EqualFold(name, "AZURESTACKCLOUD") { - return EnvironmentFromFile(os.Getenv(EnvironmentFilepathName)) - } - name = strings.ToUpper(name) env, ok := environments[name] if !ok { return env, fmt.Errorf("autorest/azure: There is no cloud environment matching the name %q", name) } - return env, nil } - -// EnvironmentFromFile loads an Environment from a configuration file available on disk. -// This function is particularly useful in the Hybrid Cloud model, where one must define their own -// endpoints. -func EnvironmentFromFile(location string) (unmarshaled Environment, err error) { - fileContents, err := ioutil.ReadFile(location) - if err != nil { - return - } - - err = json.Unmarshal(fileContents, &unmarshaled) - - return -} diff --git a/vendor/github.com/Azure/go-autorest/autorest/azure/rp.go b/vendor/github.com/Azure/go-autorest/autorest/azure/rp.go deleted file mode 100644 index b6b95d6fd..000000000 --- a/vendor/github.com/Azure/go-autorest/autorest/azure/rp.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2017 Microsoft Corporation -// -// 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 azure - -import ( - "errors" - "fmt" - "net/http" - "net/url" - "strings" - "time" - - "github.com/Azure/go-autorest/autorest" -) - -// DoRetryWithRegistration tries to register the resource provider in case it is unregistered. -// It also handles request retries -func DoRetryWithRegistration(client autorest.Client) autorest.SendDecorator { - return func(s autorest.Sender) autorest.Sender { - return autorest.SenderFunc(func(r *http.Request) (resp *http.Response, err error) { - rr := autorest.NewRetriableRequest(r) - for currentAttempt := 0; currentAttempt < client.RetryAttempts; currentAttempt++ { - err = rr.Prepare() - if err != nil { - return resp, err - } - - resp, err = autorest.SendWithSender(s, rr.Request(), - autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...), - ) - if err != nil { - return resp, err - } - - if resp.StatusCode != http.StatusConflict || client.SkipResourceProviderRegistration { - return resp, err - } - var re RequestError - err = autorest.Respond( - resp, - autorest.ByUnmarshallingJSON(&re), - ) - if err != nil { - return resp, err - } - err = re - - if re.ServiceError != nil && re.ServiceError.Code == "MissingSubscriptionRegistration" { - regErr := register(client, r, re) - if regErr != nil { - return resp, fmt.Errorf("failed auto registering Resource Provider: %s. Original error: %s", regErr, err) - } - } - } - return resp, fmt.Errorf("failed request: %s", err) - }) - } -} - -func getProvider(re RequestError) (string, error) { - if re.ServiceError != nil { - if re.ServiceError.Details != nil && len(*re.ServiceError.Details) > 0 { - detail := (*re.ServiceError.Details)[0].(map[string]interface{}) - return detail["target"].(string), nil - } - } - return "", errors.New("provider was not found in the response") -} - -func register(client autorest.Client, originalReq *http.Request, re RequestError) error { - subID := getSubscription(originalReq.URL.Path) - if subID == "" { - return errors.New("missing parameter subscriptionID to register resource provider") - } - providerName, err := getProvider(re) - if err != nil { - return fmt.Errorf("missing parameter provider to register resource provider: %s", err) - } - newURL := url.URL{ - Scheme: originalReq.URL.Scheme, - Host: originalReq.URL.Host, - } - - // taken from the resources SDK - // with almost identical code, this sections are easier to mantain - // It is also not a good idea to import the SDK here - // https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L252 - pathParameters := map[string]interface{}{ - "resourceProviderNamespace": autorest.Encode("path", providerName), - "subscriptionId": autorest.Encode("path", subID), - } - - const APIVersion = "2016-09-01" - queryParameters := map[string]interface{}{ - "api-version": APIVersion, - } - - preparer := autorest.CreatePreparer( - autorest.AsPost(), - autorest.WithBaseURL(newURL.String()), - autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}/register", pathParameters), - autorest.WithQueryParameters(queryParameters), - ) - - req, err := preparer.Prepare(&http.Request{}) - if err != nil { - return err - } - req.Cancel = originalReq.Cancel - - resp, err := autorest.SendWithSender(client, req, - autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...), - ) - if err != nil { - return err - } - - type Provider struct { - RegistrationState *string `json:"registrationState,omitempty"` - } - var provider Provider - - err = autorest.Respond( - resp, - WithErrorUnlessStatusCode(http.StatusOK), - autorest.ByUnmarshallingJSON(&provider), - autorest.ByClosing(), - ) - if err != nil { - return err - } - - // poll for registered provisioning state - now := time.Now() - for err == nil && time.Since(now) < client.PollingDuration { - // taken from the resources SDK - // https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L45 - preparer := autorest.CreatePreparer( - autorest.AsGet(), - autorest.WithBaseURL(newURL.String()), - autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}", pathParameters), - autorest.WithQueryParameters(queryParameters), - ) - req, err = preparer.Prepare(&http.Request{}) - if err != nil { - return err - } - req.Cancel = originalReq.Cancel - - resp, err := autorest.SendWithSender(client, req, - autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...), - ) - if err != nil { - return err - } - - err = autorest.Respond( - resp, - WithErrorUnlessStatusCode(http.StatusOK), - autorest.ByUnmarshallingJSON(&provider), - autorest.ByClosing(), - ) - if err != nil { - return err - } - - if provider.RegistrationState != nil && - *provider.RegistrationState == "Registered" { - break - } - - delayed := autorest.DelayWithRetryAfter(resp, originalReq.Cancel) - if !delayed { - autorest.DelayForBackoff(client.PollingDelay, 0, originalReq.Cancel) - } - } - if !(time.Since(now) < client.PollingDuration) { - return errors.New("polling for resource provider registration has exceeded the polling duration") - } - return err -} - -func getSubscription(path string) string { - parts := strings.Split(path, "/") - for i, v := range parts { - if v == "subscriptions" && (i+1) < len(parts) { - return parts[i+1] - } - } - return "" -} diff --git a/vendor/github.com/Azure/go-autorest/autorest/client.go b/vendor/github.com/Azure/go-autorest/autorest/client.go index d329cb737..5f1e72fbe 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/client.go +++ b/vendor/github.com/Azure/go-autorest/autorest/client.go @@ -1,19 +1,5 @@ package autorest -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "bytes" "fmt" @@ -35,9 +21,6 @@ const ( // DefaultRetryAttempts is number of attempts for retry status codes (5xx). DefaultRetryAttempts = 3 - - // DefaultRetryDuration is the duration to wait between retries. - DefaultRetryDuration = 30 * time.Second ) var ( @@ -50,8 +33,7 @@ var ( Version(), ) - // StatusCodesForRetry are a defined group of status code for which the client will retry - StatusCodesForRetry = []int{ + statusCodesForRetry = []int{ http.StatusRequestTimeout, // 408 http.StatusTooManyRequests, // 429 http.StatusInternalServerError, // 500 @@ -166,9 +148,6 @@ type Client struct { UserAgent string Jar http.CookieJar - - // Set to true to skip attempted registration of resource providers (false by default). - SkipResourceProviderRegistration bool } // NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed @@ -178,10 +157,9 @@ func NewClientWithUserAgent(ua string) Client { PollingDelay: DefaultPollingDelay, PollingDuration: DefaultPollingDuration, RetryAttempts: DefaultRetryAttempts, - RetryDuration: DefaultRetryDuration, + RetryDuration: 30 * time.Second, UserAgent: defaultUserAgent, } - c.Sender = c.sender() c.AddToUserAgent(ua) return c } @@ -207,17 +185,12 @@ func (c Client) Do(r *http.Request) (*http.Response, error) { c.WithInspection(), c.WithAuthorization()) if err != nil { - var resp *http.Response - if detErr, ok := err.(DetailedError); ok { - // if the authorization failed (e.g. invalid credentials) there will - // be a response associated with the error, be sure to return it. - resp = detErr.Response - } - return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed") + return nil, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed") } - - resp, err := SendWithSender(c.sender(), r) - Respond(resp, c.ByInspecting()) + resp, err := SendWithSender(c.sender(), r, + DoRetryForStatusCodes(c.RetryAttempts, c.RetryDuration, statusCodesForRetry...)) + Respond(resp, + c.ByInspecting()) return resp, err } diff --git a/vendor/github.com/Azure/go-autorest/autorest/date/date.go b/vendor/github.com/Azure/go-autorest/autorest/date/date.go index c45710656..80ca60e9b 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/date/date.go +++ b/vendor/github.com/Azure/go-autorest/autorest/date/date.go @@ -5,20 +5,6 @@ time.Time types. And both convert to time.Time through a ToTime method. */ package date -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "fmt" "time" diff --git a/vendor/github.com/Azure/go-autorest/autorest/date/time.go b/vendor/github.com/Azure/go-autorest/autorest/date/time.go index b453fad04..c1af62963 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/date/time.go +++ b/vendor/github.com/Azure/go-autorest/autorest/date/time.go @@ -1,19 +1,5 @@ package date -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "regexp" "time" diff --git a/vendor/github.com/Azure/go-autorest/autorest/date/timerfc1123.go b/vendor/github.com/Azure/go-autorest/autorest/date/timerfc1123.go index 48fb39ba9..11995fb9f 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/date/timerfc1123.go +++ b/vendor/github.com/Azure/go-autorest/autorest/date/timerfc1123.go @@ -1,19 +1,5 @@ package date -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "errors" "time" diff --git a/vendor/github.com/Azure/go-autorest/autorest/date/unixtime.go b/vendor/github.com/Azure/go-autorest/autorest/date/unixtime.go index 7073959b2..e085c77ee 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/date/unixtime.go +++ b/vendor/github.com/Azure/go-autorest/autorest/date/unixtime.go @@ -1,19 +1,5 @@ package date -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "bytes" "encoding/binary" diff --git a/vendor/github.com/Azure/go-autorest/autorest/date/utility.go b/vendor/github.com/Azure/go-autorest/autorest/date/utility.go index 12addf0eb..207b1a240 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/date/utility.go +++ b/vendor/github.com/Azure/go-autorest/autorest/date/utility.go @@ -1,19 +1,5 @@ package date -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "strings" "time" diff --git a/vendor/github.com/Azure/go-autorest/autorest/error.go b/vendor/github.com/Azure/go-autorest/autorest/error.go index f724f3332..aaef2ac8e 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/error.go +++ b/vendor/github.com/Azure/go-autorest/autorest/error.go @@ -1,19 +1,5 @@ package autorest -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "fmt" "net/http" diff --git a/vendor/github.com/Azure/go-autorest/autorest/preparer.go b/vendor/github.com/Azure/go-autorest/autorest/preparer.go index 6d67bd733..afd114821 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/preparer.go +++ b/vendor/github.com/Azure/go-autorest/autorest/preparer.go @@ -1,19 +1,5 @@ package autorest -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "bytes" "encoding/json" @@ -27,9 +13,8 @@ import ( ) const ( - mimeTypeJSON = "application/json" - mimeTypeOctetStream = "application/octet-stream" - mimeTypeFormPost = "application/x-www-form-urlencoded" + mimeTypeJSON = "application/json" + mimeTypeFormPost = "application/x-www-form-urlencoded" headerAuthorization = "Authorization" headerContentType = "Content-Type" @@ -113,28 +98,6 @@ func WithHeader(header string, value string) PrepareDecorator { } } -// WithHeaders returns a PrepareDecorator that sets the specified HTTP headers of the http.Request to -// the passed value. It canonicalizes the passed headers name (via http.CanonicalHeaderKey) before -// adding them. -func WithHeaders(headers map[string]interface{}) PrepareDecorator { - h := ensureValueStrings(headers) - return func(p Preparer) Preparer { - return PreparerFunc(func(r *http.Request) (*http.Request, error) { - r, err := p.Prepare(r) - if err == nil { - if r.Header == nil { - r.Header = make(http.Header) - } - - for name, value := range h { - r.Header.Set(http.CanonicalHeaderKey(name), value) - } - } - return r, err - }) - } -} - // WithBearerAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose // value is "Bearer " followed by the supplied token. func WithBearerAuthorization(token string) PrepareDecorator { @@ -165,11 +128,6 @@ func AsJSON() PrepareDecorator { return AsContentType(mimeTypeJSON) } -// AsOctetStream returns a PrepareDecorator that adds the "application/octet-stream" Content-Type header. -func AsOctetStream() PrepareDecorator { - return AsContentType(mimeTypeOctetStream) -} - // WithMethod returns a PrepareDecorator that sets the HTTP method of the passed request. The // decorator does not validate that the passed method string is a known HTTP method. func WithMethod(method string) PrepareDecorator { @@ -243,11 +201,6 @@ func WithFormData(v url.Values) PrepareDecorator { r, err := p.Prepare(r) if err == nil { s := v.Encode() - - if r.Header == nil { - r.Header = make(http.Header) - } - r.Header.Set(http.CanonicalHeaderKey(headerContentType), mimeTypeFormPost) r.ContentLength = int64(len(s)) r.Body = ioutil.NopCloser(strings.NewReader(s)) } @@ -463,16 +416,11 @@ func WithQueryParameters(queryParameters map[string]interface{}) PrepareDecorato if r.URL == nil { return r, NewError("autorest", "WithQueryParameters", "Invoked with a nil URL") } - v := r.URL.Query() for key, value := range parameters { - d, err := url.QueryUnescape(value) - if err != nil { - return r, err - } - v.Add(key, d) + v.Add(key, value) } - r.URL.RawQuery = v.Encode() + r.URL.RawQuery = createQuery(v) } return r, err }) diff --git a/vendor/github.com/Azure/go-autorest/autorest/responder.go b/vendor/github.com/Azure/go-autorest/autorest/responder.go index a908a0adb..87f71e585 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/responder.go +++ b/vendor/github.com/Azure/go-autorest/autorest/responder.go @@ -1,19 +1,5 @@ package autorest -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "bytes" "encoding/json" diff --git a/vendor/github.com/Azure/go-autorest/autorest/retriablerequest.go b/vendor/github.com/Azure/go-autorest/autorest/retriablerequest.go index fa11dbed7..0ab1eb300 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/retriablerequest.go +++ b/vendor/github.com/Azure/go-autorest/autorest/retriablerequest.go @@ -1,19 +1,5 @@ package autorest -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "bytes" "io" diff --git a/vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.7.go b/vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.7.go index 7143cc61b..e28eb2cbd 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.7.go +++ b/vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.7.go @@ -1,31 +1,17 @@ // +build !go1.8 -// Copyright 2017 Microsoft Corporation -// -// 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 autorest import ( "bytes" - "io/ioutil" "net/http" ) // RetriableRequest provides facilities for retrying an HTTP request. type RetriableRequest struct { - req *http.Request - br *bytes.Reader + req *http.Request + br *bytes.Reader + reset bool } // Prepare signals that the request is about to be sent. @@ -33,17 +19,21 @@ func (rr *RetriableRequest) Prepare() (err error) { // preserve the request body; this is to support retry logic as // the underlying transport will always close the reqeust body if rr.req.Body != nil { - if rr.br != nil { - _, err = rr.br.Seek(0, 0 /*io.SeekStart*/) - rr.req.Body = ioutil.NopCloser(rr.br) - } - if err != nil { - return err + if rr.reset { + if rr.br != nil { + _, err = rr.br.Seek(0, 0 /*io.SeekStart*/) + } + rr.reset = false + if err != nil { + return err + } } if rr.br == nil { // fall back to making a copy (only do this once) err = rr.prepareFromByteReader() } + // indicates that the request body needs to be reset + rr.reset = true } return err } diff --git a/vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.8.go b/vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.8.go index ae15c6bf9..8c1d1aec8 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.8.go +++ b/vendor/github.com/Azure/go-autorest/autorest/retriablerequest_1.8.go @@ -1,33 +1,19 @@ // +build go1.8 -// Copyright 2017 Microsoft Corporation -// -// 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 autorest import ( "bytes" "io" - "io/ioutil" "net/http" ) // RetriableRequest provides facilities for retrying an HTTP request. type RetriableRequest struct { - req *http.Request - rc io.ReadCloser - br *bytes.Reader + req *http.Request + rc io.ReadCloser + br *bytes.Reader + reset bool } // Prepare signals that the request is about to be sent. @@ -35,14 +21,16 @@ func (rr *RetriableRequest) Prepare() (err error) { // preserve the request body; this is to support retry logic as // the underlying transport will always close the reqeust body if rr.req.Body != nil { - if rr.rc != nil { - rr.req.Body = rr.rc - } else if rr.br != nil { - _, err = rr.br.Seek(0, io.SeekStart) - rr.req.Body = ioutil.NopCloser(rr.br) - } - if err != nil { - return err + if rr.reset { + if rr.rc != nil { + rr.req.Body = rr.rc + } else if rr.br != nil { + _, err = rr.br.Seek(0, io.SeekStart) + } + rr.reset = false + if err != nil { + return err + } } if rr.req.GetBody != nil { // this will allow us to preserve the body without having to @@ -55,6 +43,8 @@ func (rr *RetriableRequest) Prepare() (err error) { // fall back to making a copy (only do this once) err = rr.prepareFromByteReader() } + // indicates that the request body needs to be reset + rr.reset = true } return err } diff --git a/vendor/github.com/Azure/go-autorest/autorest/sender.go b/vendor/github.com/Azure/go-autorest/autorest/sender.go index c5efd59a2..94b029847 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/sender.go +++ b/vendor/github.com/Azure/go-autorest/autorest/sender.go @@ -1,19 +1,5 @@ package autorest -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "fmt" "log" @@ -215,26 +201,19 @@ func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) Se rr := NewRetriableRequest(r) // Increment to add the first call (attempts denotes number of retries) attempts++ - for attempt := 0; attempt < attempts; { + for attempt := 0; attempt < attempts; attempt++ { err = rr.Prepare() if err != nil { return resp, err } resp, err = s.Do(rr.Request()) - // we want to retry if err is not nil (e.g. transient network failure). note that for failed authentication - // resp and err will both have a value, so in this case we don't want to retry as it will never succeed. - if err == nil && !ResponseHasStatusCode(resp, codes...) || IsTokenRefreshError(err) { + if err != nil || !ResponseHasStatusCode(resp, codes...) { return resp, err } delayed := DelayWithRetryAfter(resp, r.Cancel) if !delayed { DelayForBackoff(backoff, attempt, r.Cancel) } - // don't count a 429 against the number of attempts - // so that we continue to retry until it succeeds - if resp == nil || resp.StatusCode != http.StatusTooManyRequests { - attempt++ - } } return resp, err }) @@ -244,9 +223,6 @@ func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) Se // DelayWithRetryAfter invokes time.After for the duration specified in the "Retry-After" header in // responses with status code 429 func DelayWithRetryAfter(resp *http.Response, cancel <-chan struct{}) bool { - if resp == nil { - return false - } retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After")) if resp.StatusCode == http.StatusTooManyRequests && retryAfter > 0 { select { diff --git a/vendor/github.com/Azure/go-autorest/autorest/to/convert.go b/vendor/github.com/Azure/go-autorest/autorest/to/convert.go index fdda2ce1a..7b180b866 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/to/convert.go +++ b/vendor/github.com/Azure/go-autorest/autorest/to/convert.go @@ -3,20 +3,6 @@ Package to provides helpers to ease working with pointer values of marshalled st */ package to -// Copyright 2017 Microsoft Corporation -// -// 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. - // String returns a string value for the passed string pointer. It returns the empty string if the // pointer is nil. func String(s *string) string { diff --git a/vendor/github.com/Azure/go-autorest/autorest/utility.go b/vendor/github.com/Azure/go-autorest/autorest/utility.go index afb3e4e16..78067148b 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/utility.go +++ b/vendor/github.com/Azure/go-autorest/autorest/utility.go @@ -1,31 +1,15 @@ package autorest -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "bytes" "encoding/json" "encoding/xml" "fmt" "io" - "net/http" "net/url" "reflect" + "sort" "strings" - - "github.com/Azure/go-autorest/autorest/adal" ) // EncodedAs is a series of constants specifying various data encodings @@ -139,38 +123,13 @@ func MapToValues(m map[string]interface{}) url.Values { return v } -// AsStringSlice method converts interface{} to []string. This expects a -//that the parameter passed to be a slice or array of a type that has the underlying -//type a string. -func AsStringSlice(s interface{}) ([]string, error) { - v := reflect.ValueOf(s) - if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { - return nil, NewError("autorest", "AsStringSlice", "the value's type is not an array.") - } - stringSlice := make([]string, 0, v.Len()) - - for i := 0; i < v.Len(); i++ { - stringSlice = append(stringSlice, v.Index(i).String()) - } - return stringSlice, nil -} - // String method converts interface v to string. If interface is a list, it -// joins list elements using the seperator. Note that only sep[0] will be used for -// joining if any separator is specified. +// joins list elements using separator. func String(v interface{}, sep ...string) string { - if len(sep) == 0 { - return ensureValueString(v) + if len(sep) > 0 { + return ensureValueString(strings.Join(v.([]string), sep[0])) } - stringSlice, ok := v.([]string) - if ok == false { - var err error - stringSlice, err = AsStringSlice(v) - if err != nil { - panic(fmt.Sprintf("autorest: Couldn't convert value to a string %s.", err)) - } - } - return ensureValueString(strings.Join(stringSlice, sep[0])) + return ensureValueString(v) } // Encode method encodes url path and query parameters. @@ -194,25 +153,26 @@ func queryEscape(s string) string { return url.QueryEscape(s) } -// ChangeToGet turns the specified http.Request into a GET (it assumes it wasn't). -// This is mainly useful for long-running operations that use the Azure-AsyncOperation -// header, so we change the initial PUT into a GET to retrieve the final result. -func ChangeToGet(req *http.Request) *http.Request { - req.Method = "GET" - req.Body = nil - req.ContentLength = 0 - req.Header.Del("Content-Length") - return req -} - -// IsTokenRefreshError returns true if the specified error implements the TokenRefreshError -// interface. If err is a DetailedError it will walk the chain of Original errors. -func IsTokenRefreshError(err error) bool { - if _, ok := err.(adal.TokenRefreshError); ok { - return true +// This method is same as Encode() method of "net/url" go package, +// except it does not encode the query parameters because they +// already come encoded. It formats values map in query format (bar=foo&a=b). +func createQuery(v url.Values) string { + var buf bytes.Buffer + keys := make([]string, 0, len(v)) + for k := range v { + keys = append(keys, k) } - if de, ok := err.(DetailedError); ok { - return IsTokenRefreshError(de.Original) + sort.Strings(keys) + for _, k := range keys { + vs := v[k] + prefix := url.QueryEscape(k) + "=" + for _, v := range vs { + if buf.Len() > 0 { + buf.WriteByte('&') + } + buf.WriteString(prefix) + buf.WriteString(v) + } } - return false + return buf.String() } diff --git a/vendor/github.com/Azure/go-autorest/autorest/validation/validation.go b/vendor/github.com/Azure/go-autorest/autorest/validation/validation.go index 3fe62c930..38f0074d0 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/validation/validation.go +++ b/vendor/github.com/Azure/go-autorest/autorest/validation/validation.go @@ -3,20 +3,6 @@ Package validation provides methods for validating parameter value using reflect */ package validation -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "fmt" "reflect" @@ -105,12 +91,15 @@ func validateStruct(x reflect.Value, v Constraint, name ...string) error { return createError(x, v, fmt.Sprintf("field %q doesn't exist", v.Target)) } - return Validate([]Validation{ + if err := Validate([]Validation{ { TargetValue: getInterfaceValue(f), Constraints: []Constraint{v}, }, - }) + }); err != nil { + return err + } + return nil } func validatePtr(x reflect.Value, v Constraint) error { diff --git a/vendor/github.com/Azure/go-autorest/autorest/version.go b/vendor/github.com/Azure/go-autorest/autorest/version.go index a19c0d35a..a222e8efa 100644 --- a/vendor/github.com/Azure/go-autorest/autorest/version.go +++ b/vendor/github.com/Azure/go-autorest/autorest/version.go @@ -1,19 +1,5 @@ package autorest -// Copyright 2017 Microsoft Corporation -// -// 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. - import ( "bytes" "fmt" @@ -22,9 +8,9 @@ import ( ) const ( - major = 9 - minor = 8 - patch = 1 + major = 8 + minor = 0 + patch = 0 tag = "" ) diff --git a/vendor/github.com/google/go-querystring/LICENSE b/vendor/github.com/google/go-querystring/LICENSE deleted file mode 100644 index ae121a1e4..000000000 --- a/vendor/github.com/google/go-querystring/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013 Google. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/go-querystring/query/encode.go b/vendor/github.com/google/go-querystring/query/encode.go deleted file mode 100644 index 37080b19b..000000000 --- a/vendor/github.com/google/go-querystring/query/encode.go +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package query implements encoding of structs into URL query parameters. -// -// As a simple example: -// -// type Options struct { -// Query string `url:"q"` -// ShowAll bool `url:"all"` -// Page int `url:"page"` -// } -// -// opt := Options{ "foo", true, 2 } -// v, _ := query.Values(opt) -// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2" -// -// The exact mapping between Go values and url.Values is described in the -// documentation for the Values() function. -package query - -import ( - "bytes" - "fmt" - "net/url" - "reflect" - "strconv" - "strings" - "time" -) - -var timeType = reflect.TypeOf(time.Time{}) - -var encoderType = reflect.TypeOf(new(Encoder)).Elem() - -// Encoder is an interface implemented by any type that wishes to encode -// itself into URL values in a non-standard way. -type Encoder interface { - EncodeValues(key string, v *url.Values) error -} - -// Values returns the url.Values encoding of v. -// -// Values expects to be passed a struct, and traverses it recursively using the -// following encoding rules. -// -// Each exported struct field is encoded as a URL parameter unless -// -// - the field's tag is "-", or -// - the field is empty and its tag specifies the "omitempty" option -// -// The empty values are false, 0, any nil pointer or interface value, any array -// slice, map, or string of length zero, and any time.Time that returns true -// for IsZero(). -// -// The URL parameter name defaults to the struct field name but can be -// specified in the struct field's tag value. The "url" key in the struct -// field's tag value is the key name, followed by an optional comma and -// options. For example: -// -// // Field is ignored by this package. -// Field int `url:"-"` -// -// // Field appears as URL parameter "myName". -// Field int `url:"myName"` -// -// // Field appears as URL parameter "myName" and the field is omitted if -// // its value is empty -// Field int `url:"myName,omitempty"` -// -// // Field appears as URL parameter "Field" (the default), but the field -// // is skipped if empty. Note the leading comma. -// Field int `url:",omitempty"` -// -// For encoding individual field values, the following type-dependent rules -// apply: -// -// Boolean values default to encoding as the strings "true" or "false". -// Including the "int" option signals that the field should be encoded as the -// strings "1" or "0". -// -// time.Time values default to encoding as RFC3339 timestamps. Including the -// "unix" option signals that the field should be encoded as a Unix time (see -// time.Unix()) -// -// Slice and Array values default to encoding as multiple URL values of the -// same name. Including the "comma" option signals that the field should be -// encoded as a single comma-delimited value. Including the "space" option -// similarly encodes the value as a single space-delimited string. Including -// the "semicolon" option will encode the value as a semicolon-delimited string. -// Including the "brackets" option signals that the multiple URL values should -// have "[]" appended to the value name. "numbered" will append a number to -// the end of each incidence of the value name, example: -// name0=value0&name1=value1, etc. -// -// Anonymous struct fields are usually encoded as if their inner exported -// fields were fields in the outer struct, subject to the standard Go -// visibility rules. An anonymous struct field with a name given in its URL -// tag is treated as having that name, rather than being anonymous. -// -// Non-nil pointer values are encoded as the value pointed to. -// -// Nested structs are encoded including parent fields in value names for -// scoping. e.g: -// -// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO" -// -// All other values are encoded using their default string representation. -// -// Multiple fields that encode to the same URL parameter name will be included -// as multiple URL values of the same name. -func Values(v interface{}) (url.Values, error) { - values := make(url.Values) - val := reflect.ValueOf(v) - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return values, nil - } - val = val.Elem() - } - - if v == nil { - return values, nil - } - - if val.Kind() != reflect.Struct { - return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) - } - - err := reflectValue(values, val, "") - return values, err -} - -// reflectValue populates the values parameter from the struct fields in val. -// Embedded structs are followed recursively (using the rules defined in the -// Values function documentation) breadth-first. -func reflectValue(values url.Values, val reflect.Value, scope string) error { - var embedded []reflect.Value - - typ := val.Type() - for i := 0; i < typ.NumField(); i++ { - sf := typ.Field(i) - if sf.PkgPath != "" && !sf.Anonymous { // unexported - continue - } - - sv := val.Field(i) - tag := sf.Tag.Get("url") - if tag == "-" { - continue - } - name, opts := parseTag(tag) - if name == "" { - if sf.Anonymous && sv.Kind() == reflect.Struct { - // save embedded struct for later processing - embedded = append(embedded, sv) - continue - } - - name = sf.Name - } - - if scope != "" { - name = scope + "[" + name + "]" - } - - if opts.Contains("omitempty") && isEmptyValue(sv) { - continue - } - - if sv.Type().Implements(encoderType) { - if !reflect.Indirect(sv).IsValid() { - sv = reflect.New(sv.Type().Elem()) - } - - m := sv.Interface().(Encoder) - if err := m.EncodeValues(name, &values); err != nil { - return err - } - continue - } - - if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { - var del byte - if opts.Contains("comma") { - del = ',' - } else if opts.Contains("space") { - del = ' ' - } else if opts.Contains("semicolon") { - del = ';' - } else if opts.Contains("brackets") { - name = name + "[]" - } - - if del != 0 { - s := new(bytes.Buffer) - first := true - for i := 0; i < sv.Len(); i++ { - if first { - first = false - } else { - s.WriteByte(del) - } - s.WriteString(valueString(sv.Index(i), opts)) - } - values.Add(name, s.String()) - } else { - for i := 0; i < sv.Len(); i++ { - k := name - if opts.Contains("numbered") { - k = fmt.Sprintf("%s%d", name, i) - } - values.Add(k, valueString(sv.Index(i), opts)) - } - } - continue - } - - for sv.Kind() == reflect.Ptr { - if sv.IsNil() { - break - } - sv = sv.Elem() - } - - if sv.Type() == timeType { - values.Add(name, valueString(sv, opts)) - continue - } - - if sv.Kind() == reflect.Struct { - reflectValue(values, sv, name) - continue - } - - values.Add(name, valueString(sv, opts)) - } - - for _, f := range embedded { - if err := reflectValue(values, f, scope); err != nil { - return err - } - } - - return nil -} - -// valueString returns the string representation of a value. -func valueString(v reflect.Value, opts tagOptions) string { - for v.Kind() == reflect.Ptr { - if v.IsNil() { - return "" - } - v = v.Elem() - } - - if v.Kind() == reflect.Bool && opts.Contains("int") { - if v.Bool() { - return "1" - } - return "0" - } - - if v.Type() == timeType { - t := v.Interface().(time.Time) - if opts.Contains("unix") { - return strconv.FormatInt(t.Unix(), 10) - } - return t.Format(time.RFC3339) - } - - return fmt.Sprint(v.Interface()) -} - -// isEmptyValue checks if a value should be considered empty for the purposes -// of omitting fields with the "omitempty" option. -func isEmptyValue(v reflect.Value) bool { - switch v.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - return v.Len() == 0 - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Ptr: - return v.IsNil() - } - - if v.Type() == timeType { - return v.Interface().(time.Time).IsZero() - } - - return false -} - -// tagOptions is the string following a comma in a struct field's "url" tag, or -// the empty string. It does not include the leading comma. -type tagOptions []string - -// parseTag splits a struct field's url tag into its name and comma-separated -// options. -func parseTag(tag string) (string, tagOptions) { - s := strings.Split(tag, ",") - return s[0], s[1:] -} - -// Contains checks whether the tagOptions contains the specified option. -func (o tagOptions) Contains(option string) bool { - for _, s := range o { - if s == option { - return true - } - } - return false -} diff --git a/vendor/github.com/hashicorp/go-slug/LICENSE b/vendor/github.com/hashicorp/go-slug/LICENSE deleted file mode 100644 index a612ad981..000000000 --- a/vendor/github.com/hashicorp/go-slug/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/go-slug/README.md b/vendor/github.com/hashicorp/go-slug/README.md deleted file mode 100644 index 978314f1b..000000000 --- a/vendor/github.com/hashicorp/go-slug/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# go-slug - -[![Build Status](https://travis-ci.org/hashicorp/go-slug.svg?branch=master)](https://travis-ci.org/hashicorp/go-slug) -[![GitHub license](https://img.shields.io/github/license/hashicorp/go-slug.svg)](https://github.com/hashicorp/go-slug/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/hashicorp/go-slug?status.svg)](https://godoc.org/github.com/hashicorp/go-slug) -[![Go Report Card](https://goreportcard.com/badge/github.com/hashicorp/go-slug)](https://goreportcard.com/report/github.com/hashicorp/go-slug) -[![GitHub issues](https://img.shields.io/github/issues/hashicorp/go-slug.svg)](https://github.com/hashicorp/go-slug/issues) - -Package `go-slug` offers functions for packing and unpacking Terraform Enterprise -compatible slugs. Slugs are gzip compressed tar files containing Terraform configuration files. - -## Installation - -Installation can be done with a normal `go get`: - -``` -go get -u github.com/hashicorp/go-slug -``` - -## Documentation - -For the complete usage of `go-slug`, see the full [package docs](https://godoc.org/github.com/hashicorp/go-slug). - -## Example - -Packing or unpacking a slug is pretty straight forward as shown in the -following example: - -```go -package main - -import ( - "bytes" - "ioutil" - "log" - "os" - - slug "github.com/hashicorp/go-slug" -) - -func main() { - // First create a buffer for storing the slug. - slug := bytes.NewBuffer(nil) - - // Then call the Pack function with a directory path containing the - // configuration files and an io.Writer to write the slug to. - if _, err := Pack("test-fixtures/archive-dir", slug); err != nil { - log.Fatal(err) - } - - // Create a directory to unpack the slug contents into. - dst, err := ioutil.TempDir("", "slug") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(dst) - - // Unpacking a slug is done by calling the Unpack function with an - // io.Reader to read the slug from and a directory path of an existing - // directory to store the unpacked configuration files. - if err := Unpack(slug, dst); err != nil { - log.Fatal(err) - } -} -``` - -## Issues and Contributing - -If you find an issue with this package, please report an issue. If you'd like, -we welcome any contributions. Fork this repository and submit a pull request. diff --git a/vendor/github.com/hashicorp/go-slug/slug.go b/vendor/github.com/hashicorp/go-slug/slug.go deleted file mode 100644 index b7a62c963..000000000 --- a/vendor/github.com/hashicorp/go-slug/slug.go +++ /dev/null @@ -1,215 +0,0 @@ -package slug - -import ( - "archive/tar" - "compress/gzip" - "fmt" - "io" - "os" - "path/filepath" -) - -// Meta provides detailed information about a slug. -type Meta struct { - // The list of files contained in the slug. - Files []string - - // Total size of the slug in bytes. - Size int64 -} - -// Pack creates a slug from a directory src, and writes the new -// slug to w. Returns metadata about the slug and any error. -func Pack(src string, w io.Writer) (*Meta, error) { - // Gzip compress all the output data - gzipW := gzip.NewWriter(w) - - // Tar the file contents - tarW := tar.NewWriter(gzipW) - - // Track the metadata details as we go. - meta := &Meta{} - - // Walk the tree of files - err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // Check the file type and if we need to write the body - keepFile, writeBody := checkFileMode(info.Mode()) - if !keepFile { - return nil - } - - // Get the relative path from the unpack directory - subpath, err := filepath.Rel(src, path) - if err != nil { - return fmt.Errorf("Failed to get relative path for file %q: %v", path, err) - } - if subpath == "." { - return nil - } - - // Read the symlink target. We don't track the error because - // it doesn't matter if there is an error. - target, _ := os.Readlink(path) - - // Build the file header for the tar entry - header, err := tar.FileInfoHeader(info, target) - if err != nil { - return fmt.Errorf("Failed creating archive header for file %q: %v", path, err) - } - - // Modify the header to properly be the full subpath - header.Name = subpath - if info.IsDir() { - header.Name += "/" - } - - // Write the header first to the archive. - if err := tarW.WriteHeader(header); err != nil { - return fmt.Errorf("Failed writing archive header for file %q: %v", path, err) - } - - // Account for the file in the list - meta.Files = append(meta.Files, header.Name) - - // Skip writing file data for certain file types (above). - if !writeBody { - return nil - } - - // Add the size since we are going to write the body. - meta.Size += info.Size() - - f, err := os.Open(path) - if err != nil { - return fmt.Errorf("Failed opening file %q for archiving: %v", path, err) - } - defer f.Close() - - if _, err = io.Copy(tarW, f); err != nil { - return fmt.Errorf("Failed copying file %q to archive: %v", path, err) - } - - return nil - }) - if err != nil { - return nil, err - } - - // Flush the tar writer - if err := tarW.Close(); err != nil { - return nil, fmt.Errorf("Failed to close the tar archive: %v", err) - } - - // Flush the gzip writer - if err := gzipW.Close(); err != nil { - return nil, fmt.Errorf("Failed to close the gzip writer: %v", err) - } - - return meta, nil -} - -// Unpack is used to read and extract the contents of a slug to -// directory dst. Returns any error. -func Unpack(r io.Reader, dst string) error { - // Decompress as we read - uncompressed, err := gzip.NewReader(r) - if err != nil { - return fmt.Errorf("Failed to uncompress slug: %v", err) - } - - // Untar as we read - untar := tar.NewReader(uncompressed) - - // Unpackage all the contents into the directory - for { - header, err := untar.Next() - if err == io.EOF { - break - } - if err != nil { - return fmt.Errorf("Failed to untar slug: %v", err) - } - - // Get rid of absolute paths - path := header.Name - if path[0] == '/' { - path = path[1:] - } - path = filepath.Join(dst, path) - - // Make the directories to the path - dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("Failed to create directory %q: %v", dir, err) - } - - // If we have a symlink, just link it. - if header.Typeflag == tar.TypeSymlink { - if err := os.Symlink(header.Linkname, path); err != nil { - return fmt.Errorf("Failed creating symlink %q => %q: %v", - path, header.Linkname, err) - } - continue - } - - // Only unpack regular files from this point on - if header.Typeflag == tar.TypeDir { - continue - } else if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeRegA { - return fmt.Errorf("Failed creating %q: unsupported type %c", path, - header.Typeflag) - } - - // Open a handle to the destination - fh, err := os.Create(path) - if err != nil { - // This mimics tar's behavior wrt the tar file containing duplicate files - // and it allowing later ones to clobber earlier ones even if the file - // has perms that don't allow overwriting - if os.IsPermission(err) { - os.Chmod(path, 0600) - fh, err = os.Create(path) - } - - if err != nil { - return fmt.Errorf("Failed creating file %q: %v", path, err) - } - } - - // Copy the contents - _, err = io.Copy(fh, untar) - fh.Close() - if err != nil { - return fmt.Errorf("Failed to copy slug file %q: %v", path, err) - } - - // Restore the file mode. We have to do this after writing the file, - // since it is possible we have a read-only mode. - mode := header.FileInfo().Mode() - if err := os.Chmod(path, mode); err != nil { - return fmt.Errorf("Failed setting permissions on %q: %v", path, err) - } - } - return nil -} - -// checkFileMode is used to examine an os.FileMode and determine if it should -// be included in the archive, and if it has a data body which needs writing. -func checkFileMode(m os.FileMode) (keep, body bool) { - switch { - case m.IsRegular(): - return true, true - - case m.IsDir(): - return true, false - - case m&os.ModeSymlink != 0: - return true, false - } - - return false, false -} diff --git a/vendor/github.com/hashicorp/go-tfe/LICENSE b/vendor/github.com/hashicorp/go-tfe/LICENSE deleted file mode 100644 index c33dcc7c9..000000000 --- a/vendor/github.com/hashicorp/go-tfe/LICENSE +++ /dev/null @@ -1,354 +0,0 @@ -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. “Contributor” - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. “Contributor Version” - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor’s Contribution. - -1.3. “Contribution” - - means Covered Software of a particular Contributor. - -1.4. “Covered Software” - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. “Incompatible With Secondary Licenses” - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of version - 1.1 or earlier of the License, but not also under the terms of a - Secondary License. - -1.6. “Executable Form” - - means any form of the work other than Source Code Form. - -1.7. “Larger Work” - - means a work that combines Covered Software with other material, in a separate - file or files, that is not Covered Software. - -1.8. “License” - - means this document. - -1.9. “Licensable” - - means having the right to grant, to the maximum extent possible, whether at the - time of the initial grant or subsequently, any and all of the rights conveyed by - this License. - -1.10. “Modifications” - - means any of the following: - - a. any file in Source Code Form that results from an addition to, deletion - from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. “Patent Claims” of a Contributor - - means any patent claim(s), including without limitation, method, process, - and apparatus claims, in any patent Licensable by such Contributor that - would be infringed, but for the grant of the License, by the making, - using, selling, offering for sale, having made, import, or transfer of - either its Contributions or its Contributor Version. - -1.12. “Secondary License” - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. “Source Code Form” - - means the form of the work preferred for making modifications. - -1.14. “You” (or “Your”) - - means an individual or a legal entity exercising rights under this - License. For legal entities, “You” includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, “control” means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or as - part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its Contributions - or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution become - effective for each Contribution on the date the Contributor first distributes - such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under this - License. No additional rights or licenses will be implied from the distribution - or licensing of Covered Software under this License. Notwithstanding Section - 2.1(b) above, no patent license is granted by a Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party’s - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of its - Contributions. - - This License does not grant any rights in the trademarks, service marks, or - logos of any Contributor (except as may be necessary to comply with the - notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this License - (see Section 10.2) or under the terms of a Secondary License (if permitted - under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its Contributions - are its original creation(s) or it has sufficient rights to grant the - rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under applicable - copyright doctrines of fair use, fair dealing, or other equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under the - terms of this License. You must inform recipients that the Source Code Form - of the Covered Software is governed by the terms of this License, and how - they can obtain a copy of this License. You may not attempt to alter or - restrict the recipients’ rights in the Source Code Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this License, - or sublicense it under different terms, provided that the license for - the Executable Form does not attempt to limit or alter the recipients’ - rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for the - Covered Software. If the Larger Work is a combination of Covered Software - with a work governed by one or more Secondary Licenses, and the Covered - Software is not Incompatible With Secondary Licenses, this License permits - You to additionally distribute such Covered Software under the terms of - such Secondary License(s), so that the recipient of the Larger Work may, at - their option, further distribute the Covered Software under the terms of - either this License or such Secondary License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices (including - copyright notices, patent notices, disclaimers of warranty, or limitations - of liability) contained within the Source Code Form of the Covered - Software, except that You may alter any license notices to the extent - required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on behalf - of any Contributor. You must make it absolutely clear that any such - warranty, support, indemnity, or liability obligation is offered by You - alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, judicial - order, or regulation then You must: (a) comply with the terms of this License - to the maximum extent possible; and (b) describe the limitations and the code - they affect. Such description must be placed in a text file included with all - distributions of the Covered Software under this License. Except to the - extent prohibited by statute or regulation, such description must be - sufficiently detailed for a recipient of ordinary skill to be able to - understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing basis, - if such Contributor fails to notify You of the non-compliance by some - reasonable means prior to 60 days after You have come back into compliance. - Moreover, Your grants from a particular Contributor are reinstated on an - ongoing basis if such Contributor notifies You of the non-compliance by - some reasonable means, this is the first time You have received notice of - non-compliance with this License from such Contributor, and You become - compliant prior to 30 days after Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, counter-claims, - and cross-claims) alleging that a Contributor Version directly or - indirectly infringes any patent, then the rights granted to You by any and - all Contributors for the Covered Software under Section 2.1 of this License - shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an “as is” basis, without - warranty of any kind, either expressed, implied, or statutory, including, - without limitation, warranties that the Covered Software is free of defects, - merchantable, fit for a particular purpose or non-infringing. The entire - risk as to the quality and performance of the Covered Software is with You. - Should any Covered Software prove defective in any respect, You (not any - Contributor) assume the cost of any necessary servicing, repair, or - correction. This disclaimer of warranty constitutes an essential part of this - License. No use of any Covered Software is authorized under this License - except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from such - party’s negligence to the extent applicable law prohibits such limitation. - Some jurisdictions do not allow the exclusion or limitation of incidental or - consequential damages, so this exclusion and limitation may not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts of - a jurisdiction where the defendant maintains its principal place of business - and such litigation shall be governed by laws of that jurisdiction, without - reference to its conflict-of-law provisions. Nothing in this Section shall - prevent a party’s ability to bring cross-claims or counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject matter - hereof. If any provision of this License is held to be unenforceable, such - provision shall be reformed only to the extent necessary to make it - enforceable. Any law or regulation which provides that the language of a - contract shall be construed against the drafter shall not be used to construe - this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version of - the License under which You originally received the Covered Software, or - under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a modified - version of this License if you rename the license and remove any - references to the name of the license steward (except to note that such - modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses - If You choose to distribute Source Code Form that is Incompatible With - Secondary Licenses under the terms of this version of the License, the - notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, then -You may include the notice in a location (such as a LICENSE file in a relevant -directory) where a recipient would be likely to look for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - “Incompatible With Secondary Licenses” Notice - - This Source Code Form is “Incompatible - With Secondary Licenses”, as defined by - the Mozilla Public License, v. 2.0. - diff --git a/vendor/github.com/hashicorp/go-tfe/README.md b/vendor/github.com/hashicorp/go-tfe/README.md deleted file mode 100644 index 05bbc78d8..000000000 --- a/vendor/github.com/hashicorp/go-tfe/README.md +++ /dev/null @@ -1,131 +0,0 @@ -Terraform Enterprise Go Client -============================== - -[![Build Status](https://travis-ci.org/hashicorp/go-tfe.svg?branch=master)](https://travis-ci.org/hashicorp/go-tfe) -[![GitHub license](https://img.shields.io/github/license/hashicorp/go-tfe.svg)](https://github.com/hashicorp/go-tfe/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/hashicorp/go-tfe?status.svg)](https://godoc.org/github.com/hashicorp/go-tfe) -[![Go Report Card](https://goreportcard.com/badge/github.com/hashicorp/go-tfe)](https://goreportcard.com/report/github.com/hashicorp/go-tfe) -[![GitHub issues](https://img.shields.io/github/issues/hashicorp/go-tfe.svg)](https://github.com/hashicorp/go-tfe/issues) - -This is an API client for [Terraform Enterprise](https://www.hashicorp.com/products/terraform). - -## NOTE - -The Terraform Enterprise API endpoints are in beta and are subject to change! -So that means this API client is also in beta and is also subject to change. We -will indicate any breaking changes by releasing new versions. Until the release -of v1.0, any minor version changes will indicate possible breaking changes. Patch -version changes will be used for both bugfixes and non-breaking changes. - -## Coverage - -Currently the following endpoints are supported: - -- [x] [Accounts](https://www.terraform.io/docs/enterprise/api/account.html) -- [x] [Configuration Versions](https://www.terraform.io/docs/enterprise/api/configuration-versions.html) -- [x] [OAuth Clients](https://www.terraform.io/docs/enterprise/api/oauth-clients.html) -- [x] [OAuth Tokens](https://www.terraform.io/docs/enterprise/api/oauth-tokens.html) -- [x] [Organizations](https://www.terraform.io/docs/enterprise/api/organizations.html) -- [x] [Organization Tokens](https://www.terraform.io/docs/enterprise/api/organization-tokens.html) -- [x] [Policies](https://www.terraform.io/docs/enterprise/api/policies.html) -- [x] [Policy Checks](https://www.terraform.io/docs/enterprise/api/policy-checks.html) -- [ ] [Registry Modules](https://www.terraform.io/docs/enterprise/api/modules.html) -- [x] [Runs](https://www.terraform.io/docs/enterprise/api/run.html) -- [x] [SSH Keys](https://www.terraform.io/docs/enterprise/api/ssh-keys.html) -- [x] [State Versions](https://www.terraform.io/docs/enterprise/api/state-versions.html) -- [x] [Team Access](https://www.terraform.io/docs/enterprise/api/team-access.html) -- [x] [Team Memberships](https://www.terraform.io/docs/enterprise/api/team-members.html) -- [x] [Team Tokens](https://www.terraform.io/docs/enterprise/api/team-tokens.html) -- [x] [Teams](https://www.terraform.io/docs/enterprise/api/teams.html) -- [x] [Variables](https://www.terraform.io/docs/enterprise/api/variables.html) -- [x] [Workspaces](https://www.terraform.io/docs/enterprise/api/workspaces.html) -- [ ] [Admin](https://www.terraform.io/docs/enterprise/api/admin/index.html) - -## Installation - -Installation can be done with a normal `go get`: - -``` -go get -u github.com/hashicorp/go-tfe -``` - -## Documentation - -For complete usage of the API client, see the full [package docs](https://godoc.org/github.com/hashicorp/go-tfe). - -## Usage - -```go -import tfe "github.com/hashicorp/go-tfe" -``` - -Construct a new TFE client, then use the various endpoints on the client to -access different parts of the Terraform Enterprise API. For example, to list -all organizations: - -```go -config := &tfe.Config{ - Token: "insert-your-token-here", -} - -client, err := tfe.NewClient(config) -if err != nil { - log.Fatal(err) -} - -orgs, err := client.Organizations.List(context.Background(), OrganizationListOptions{}) -if err != nil { - log.Fatal(err) -} -``` - -## Examples - -The [examples](https://github.com/hashicorp/go-tfe/tree/master/examples) directory -contains a couple of examples. One of which is listed here as well: - -```go -package main - -import ( - "log" - - tfe "github.com/hashicorp/go-tfe" -) - -func main() { - config := &tfe.Config{ - Token: "insert-your-token-here", - } - - client, err := tfe.NewClient(config) - if err != nil { - log.Fatal(err) - } - - // Create a context - ctx := context.Background() - - // Create a new organization - options := tfe.OrganizationCreateOptions{ - Name: tfe.String("example"), - Email: tfe.String("info@example.com"), - } - - org, err := client.Organizations.Create(ctx, options) - if err != nil { - log.Fatal(err) - } - - // Delete an organization - err = client.Organizations.Delete(ctx, org.Name) - if err != nil { - log.Fatal(err) - } -} -``` - -## Issues and Contributing - -If you find an issue with this package, please report an issue. If you'd like, -we welcome any contributions. Fork this repository and submit a pull request. diff --git a/vendor/github.com/hashicorp/go-tfe/apply.go b/vendor/github.com/hashicorp/go-tfe/apply.go deleted file mode 100644 index 753410189..000000000 --- a/vendor/github.com/hashicorp/go-tfe/apply.go +++ /dev/null @@ -1,131 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "io" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ Applies = (*applies)(nil) - -// Applies describes all the apply related methods that the Terraform -// Enterprise API supports. -// -// TFE API docs: https://www.terraform.io/docs/enterprise/api/apply.html -type Applies interface { - // Read an apply by its ID. - Read(ctx context.Context, applyID string) (*Apply, error) - - // Logs retrieves the logs of an apply. - Logs(ctx context.Context, applyID string) (io.Reader, error) -} - -// applies implements Applys. -type applies struct { - client *Client -} - -// ApplyStatus represents an apply state. -type ApplyStatus string - -//List all available apply statuses. -const ( - ApplyCanceled ApplyStatus = "canceled" - ApplyCreated ApplyStatus = "created" - ApplyErrored ApplyStatus = "errored" - ApplyFinished ApplyStatus = "finished" - ApplyMFAWaiting ApplyStatus = "mfa_waiting" - ApplyPending ApplyStatus = "pending" - ApplyQueued ApplyStatus = "queued" - ApplyRunning ApplyStatus = "running" -) - -// Apply represents a Terraform Enterprise apply. -type Apply struct { - ID string `jsonapi:"primary,applies"` - LogReadURL string `jsonapi:"attr,log-read-url"` - ResourceAdditions int `jsonapi:"attr,resource-additions"` - ResourceChanges int `jsonapi:"attr,resource-changes"` - ResourceDestructions int `jsonapi:"attr,resource-destructions"` - Status ApplyStatus `jsonapi:"attr,status"` - StatusTimestamps *ApplyStatusTimestamps `jsonapi:"attr,status-timestamps"` -} - -// ApplyStatusTimestamps holds the timestamps for individual apply statuses. -type ApplyStatusTimestamps struct { - CanceledAt time.Time `json:"canceled-at"` - ErroredAt time.Time `json:"errored-at"` - FinishedAt time.Time `json:"finished-at"` - ForceCanceledAt time.Time `json:"force-canceled-at"` - QueuedAt time.Time `json:"queued-at"` - StartedAt time.Time `json:"started-at"` -} - -// Read an apply by its ID. -func (s *applies) Read(ctx context.Context, applyID string) (*Apply, error) { - if !validStringID(&applyID) { - return nil, errors.New("Invalid value for apply ID") - } - - u := fmt.Sprintf("applies/%s", url.QueryEscape(applyID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - a := &Apply{} - err = s.client.do(ctx, req, a) - if err != nil { - return nil, err - } - - return a, nil -} - -// Logs retrieves the logs of an apply. -func (s *applies) Logs(ctx context.Context, applyID string) (io.Reader, error) { - if !validStringID(&applyID) { - return nil, errors.New("Invalid value for apply ID") - } - - // Get the apply to make sure it exists. - a, err := s.Read(ctx, applyID) - if err != nil { - return nil, err - } - - // Return an error if the log URL is empty. - if a.LogReadURL == "" { - return nil, fmt.Errorf("Apply %s does not have a log URL", applyID) - } - - u, err := url.Parse(a.LogReadURL) - if err != nil { - return nil, fmt.Errorf("Invalid log URL: %v", err) - } - - done := func() (bool, error) { - a, err := s.Read(ctx, a.ID) - if err != nil { - return false, err - } - - switch a.Status { - case ApplyCanceled, ApplyErrored, ApplyFinished: - return true, nil - default: - return false, nil - } - } - - return &LogReader{ - client: s.client, - ctx: ctx, - done: done, - logURL: u, - }, nil -} diff --git a/vendor/github.com/hashicorp/go-tfe/configuration_version.go b/vendor/github.com/hashicorp/go-tfe/configuration_version.go deleted file mode 100644 index 168c1c6dd..000000000 --- a/vendor/github.com/hashicorp/go-tfe/configuration_version.go +++ /dev/null @@ -1,199 +0,0 @@ -package tfe - -import ( - "bytes" - "context" - "errors" - "fmt" - "net/url" - "time" - - slug "github.com/hashicorp/go-slug" -) - -// Compile-time proof of interface implementation. -var _ ConfigurationVersions = (*configurationVersions)(nil) - -// ConfigurationVersions describes all the configuration version related -// methods that the Terraform Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/configuration-versions.html -type ConfigurationVersions interface { - // List returns all configuration versions of a workspace. - List(ctx context.Context, workspaceID string, options ConfigurationVersionListOptions) (*ConfigurationVersionList, error) - - // Create is used to create a new configuration version. The created - // configuration version will be usable once data is uploaded to it. - Create(ctx context.Context, workspaceID string, options ConfigurationVersionCreateOptions) (*ConfigurationVersion, error) - - // Read a configuration version by its ID. - Read(ctx context.Context, cvID string) (*ConfigurationVersion, error) - - // Upload packages and uploads Terraform configuration files. It requires - // the upload URL from a configuration version and the full path to the - // configuration files on disk. - Upload(ctx context.Context, url string, path string) error -} - -// configurationVersions implements ConfigurationVersions. -type configurationVersions struct { - client *Client -} - -// ConfigurationStatus represents a configuration version status. -type ConfigurationStatus string - -//List all available configuration version statuses. -const ( - ConfigurationErrored ConfigurationStatus = "errored" - ConfigurationPending ConfigurationStatus = "pending" - ConfigurationUploaded ConfigurationStatus = "uploaded" -) - -// ConfigurationSource represents a source of a configuration version. -type ConfigurationSource string - -// List all available configuration version sources. -const ( - ConfigurationSourceAPI ConfigurationSource = "tfe-api" - ConfigurationSourceBitbucket ConfigurationSource = "bitbucket" - ConfigurationSourceGithub ConfigurationSource = "github" - ConfigurationSourceGitlab ConfigurationSource = "gitlab" - ConfigurationSourceTerraform ConfigurationSource = "terraform" -) - -// ConfigurationVersionList represents a list of configuration versions. -type ConfigurationVersionList struct { - *Pagination - Items []*ConfigurationVersion -} - -// ConfigurationVersion is a representation of an uploaded or ingressed -// Terraform configuration in TFE. A workspace must have at least one -// configuration version before any runs may be queued on it. -type ConfigurationVersion struct { - ID string `jsonapi:"primary,configuration-versions"` - AutoQueueRuns bool `jsonapi:"attr,auto-queue-runs"` - Error string `jsonapi:"attr,error"` - ErrorMessage string `jsonapi:"attr,error-message"` - Source ConfigurationSource `jsonapi:"attr,source"` - Speculative bool `jsonapi:"attr,speculative "` - Status ConfigurationStatus `jsonapi:"attr,status"` - StatusTimestamps *CVStatusTimestamps `jsonapi:"attr,status-timestamps"` - UploadURL string `jsonapi:"attr,upload-url"` -} - -// CVStatusTimestamps holds the timestamps for individual configuration version -// statuses. -type CVStatusTimestamps struct { - FinishedAt time.Time `json:"finished-at"` - QueuedAt time.Time `json:"queued-at"` - StartedAt time.Time `json:"started-at"` -} - -// ConfigurationVersionListOptions represents the options for listing -// configuration versions. -type ConfigurationVersionListOptions struct { - ListOptions -} - -// List returns all configuration versions of a workspace. -func (s *configurationVersions) List(ctx context.Context, workspaceID string, options ConfigurationVersionListOptions) (*ConfigurationVersionList, error) { - if !validStringID(&workspaceID) { - return nil, errors.New("Invalid value for workspace ID") - } - - u := fmt.Sprintf("workspaces/%s/configuration-versions", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - cvl := &ConfigurationVersionList{} - err = s.client.do(ctx, req, cvl) - if err != nil { - return nil, err - } - - return cvl, nil -} - -// ConfigurationVersionCreateOptions represents the options for creating a -// configuration version. -type ConfigurationVersionCreateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,configuration-versions"` - - // When true, runs are queued automatically when the configuration version - // is uploaded. - AutoQueueRuns *bool `jsonapi:"attr,auto-queue-runs,omitempty"` - - // When true, this configuration version can only be used for planning. - Speculative *bool `jsonapi:"attr,speculative,omitempty"` -} - -// Create is used to create a new configuration version. The created -// configuration version will be usable once data is uploaded to it. -func (s *configurationVersions) Create(ctx context.Context, workspaceID string, options ConfigurationVersionCreateOptions) (*ConfigurationVersion, error) { - if !validStringID(&workspaceID) { - return nil, errors.New("Invalid value for workspace ID") - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("workspaces/%s/configuration-versions", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return nil, err - } - - cv := &ConfigurationVersion{} - err = s.client.do(ctx, req, cv) - if err != nil { - return nil, err - } - - return cv, nil -} - -// Read a configuration version by its ID. -func (s *configurationVersions) Read(ctx context.Context, cvID string) (*ConfigurationVersion, error) { - if !validStringID(&cvID) { - return nil, errors.New("Invalid value for configuration version ID") - } - - u := fmt.Sprintf("configuration-versions/%s", url.QueryEscape(cvID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - cv := &ConfigurationVersion{} - err = s.client.do(ctx, req, cv) - if err != nil { - return nil, err - } - - return cv, nil -} - -// Upload packages and uploads Terraform configuration files. It requires the -// upload URL from a configuration version and the path to the configuration -// files on disk. -func (s *configurationVersions) Upload(ctx context.Context, url, path string) error { - body := bytes.NewBuffer(nil) - - _, err := slug.Pack(path, body) - if err != nil { - return err - } - - req, err := s.client.newRequest("PUT", url, body) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/logreader.go b/vendor/github.com/hashicorp/go-tfe/logreader.go deleted file mode 100644 index cdc1aad9b..000000000 --- a/vendor/github.com/hashicorp/go-tfe/logreader.go +++ /dev/null @@ -1,138 +0,0 @@ -package tfe - -import ( - "context" - "fmt" - "io" - "math" - "net/http" - "net/url" - "time" -) - -// LogReader implements io.Reader for streaming logs. -type LogReader struct { - client *Client - ctx context.Context - done func() (bool, error) - logURL *url.URL - offset int64 - reads int - startOfText bool - endOfText bool -} - -// backoff will perform exponential backoff based on the iteration and -// limited by the provided min and max (in milliseconds) durations. -func backoff(min, max float64, iter int) time.Duration { - backoff := math.Pow(2, float64(iter)/5) * min - if backoff > max { - backoff = max - } - return time.Duration(backoff) * time.Millisecond -} - -func (r *LogReader) Read(l []byte) (int, error) { - if written, err := r.read(l); err != io.ErrNoProgress { - return written, err - } - - // Loop until we can any data, the context is canceled or the - // run is finsished. If we would return right away without any - // data, we could and up causing a io.ErrNoProgress error. - for r.reads = 1; ; r.reads++ { - select { - case <-r.ctx.Done(): - return 0, r.ctx.Err() - case <-time.After(backoff(500, 2000, r.reads)): - if written, err := r.read(l); err != io.ErrNoProgress { - return written, err - } - } - } -} - -func (r *LogReader) read(l []byte) (int, error) { - // Update the query string. - r.logURL.RawQuery = fmt.Sprintf("limit=%d&offset=%d", len(l), r.offset) - - // Create a new request. - req, err := http.NewRequest("GET", r.logURL.String(), nil) - if err != nil { - return 0, err - } - req = req.WithContext(r.ctx) - - // Retrieve the next chunk. - resp, err := r.client.http.Do(req) - if err != nil { - return 0, err - } - defer resp.Body.Close() - - // Basic response checking. - if err := checkResponseCode(resp); err != nil { - return 0, err - } - - // Read the retrieved chunk. - written, err := resp.Body.Read(l) - if err != nil && err != io.EOF { - // Ignore io.EOF errors returned when reading from the response - // body as this indicates the end of the chunk and not the end - // of the logfile. - return written, err - } - - if written > 0 { - // Check for an STX (Start of Text) ASCII control marker. - if !r.startOfText && l[0] == byte(2) { - r.startOfText = true - - // Remove the STX marker from the received chunk. - copy(l[:written-1], l[1:]) - l[written-1] = byte(0) - r.offset++ - written-- - - // Return early if we only received the STX marker. - if written == 0 { - return 0, io.ErrNoProgress - } - } - - // If we found an STX ASCII control character, start looking for - // the ETX (End of Text) control character. - if r.startOfText && l[written-1] == byte(3) { - r.endOfText = true - - // Remove the ETX marker from the received chunk. - l[written-1] = byte(0) - r.offset++ - written-- - } - } - - // Check if we need to continue the loop and wait 500 miliseconds - // before checking if there is a new chunk available or that the - // run is finished and we are done reading all chunks. - if written == 0 { - if (r.startOfText && r.endOfText) || // The logstream finished without issues. - (r.startOfText && r.reads%10 == 0) || // The logstream terminated unexpectedly. - (!r.startOfText && r.reads > 1) { // The logstream doesn't support STX/ETX. - done, err := r.done() - if err != nil { - return 0, err - } - if done { - return 0, io.EOF - } - } - return 0, io.ErrNoProgress - } - - // Update the offset for the next read. - r.offset += int64(written) - - return written, nil -} diff --git a/vendor/github.com/hashicorp/go-tfe/oauth_client.go b/vendor/github.com/hashicorp/go-tfe/oauth_client.go deleted file mode 100644 index be31fd8e3..000000000 --- a/vendor/github.com/hashicorp/go-tfe/oauth_client.go +++ /dev/null @@ -1,199 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ OAuthClients = (*oAuthClients)(nil) - -// OAuthClients describes all the OAuth client related methods that the -// Terraform Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/oauth-clients.html -type OAuthClients interface { - // List all the OAuth clients for a given organization. - List(ctx context.Context, organization string, options OAuthClientListOptions) (*OAuthClientList, error) - - // Create an OAuth client to connect an organization and a VCS provider. - Create(ctx context.Context, organization string, options OAuthClientCreateOptions) (*OAuthClient, error) - - // Read an OAuth client by its ID. - Read(ctx context.Context, oAuthClientID string) (*OAuthClient, error) - - // Delete an OAuth client by its ID. - Delete(ctx context.Context, oAuthClientID string) error -} - -// oAuthClients implements OAuthClients. -type oAuthClients struct { - client *Client -} - -// ServiceProviderType represents a VCS type. -type ServiceProviderType string - -// List of available VCS types. -const ( - ServiceProviderBitbucket ServiceProviderType = "bitbucket_hosted" - ServiceProviderBitbucketServer ServiceProviderType = "bitbucket_server" - ServiceProviderGithub ServiceProviderType = "github" - ServiceProviderGithubEE ServiceProviderType = "github_enterprise" - ServiceProviderGitlab ServiceProviderType = "gitlab_hosted" - ServiceProviderGitlabCE ServiceProviderType = "gitlab_community_edition" - ServiceProviderGitlabEE ServiceProviderType = "gitlab_enterprise_edition" -) - -// OAuthClientList represents a list of OAuth clients. -type OAuthClientList struct { - *Pagination - Items []*OAuthClient -} - -// OAuthClient represents a connection between an organization and a VCS -// provider. -type OAuthClient struct { - ID string `jsonapi:"primary,oauth-clients"` - APIURL string `jsonapi:"attr,api-url"` - CallbackURL string `jsonapi:"attr,callback-url"` - ConnectPath string `jsonapi:"attr,connect-path"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - HTTPURL string `jsonapi:"attr,http-url"` - Key string `jsonapi:"attr,key"` - RSAPublicKey string `jsonapi:"attr,rsa-public-key"` - ServiceProvider ServiceProviderType `jsonapi:"attr,service-provider"` - ServiceProviderName string `jsonapi:"attr,service-provider-display-name"` - - // Relations - Organization *Organization `jsonapi:"relation,organization"` - OAuthTokens []*OAuthToken `jsonapi:"relation,oauth-tokens"` -} - -// OAuthClientListOptions represents the options for listing -// OAuth clients. -type OAuthClientListOptions struct { - ListOptions -} - -// List all the OAuth clients for a given organization. -func (s *oAuthClients) List(ctx context.Context, organization string, options OAuthClientListOptions) (*OAuthClientList, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/oauth-clients", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - ocl := &OAuthClientList{} - err = s.client.do(ctx, req, ocl) - if err != nil { - return nil, err - } - - return ocl, nil -} - -// OAuthClientCreateOptions represents the options for creating an OAuth client. -type OAuthClientCreateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,oauth-clients"` - - // The base URL of your VCS provider's API. - APIURL *string `jsonapi:"attr,api-url"` - - // The homepage of your VCS provider. - HTTPURL *string `jsonapi:"attr,http-url"` - - // The token string you were given by your VCS provider. - OAuthToken *string `jsonapi:"attr,oauth-token-string"` - - // The VCS provider being connected with. - ServiceProvider *ServiceProviderType `jsonapi:"attr,service-provider"` -} - -func (o OAuthClientCreateOptions) valid() error { - if !validString(o.APIURL) { - return errors.New("APIURL is required") - } - if !validString(o.HTTPURL) { - return errors.New("HTTPURL is required") - } - if !validString(o.OAuthToken) { - return errors.New("OAuthToken is required") - } - if o.ServiceProvider == nil { - return errors.New("ServiceProvider is required") - } - return nil -} - -// Create an OAuth client to connect an organization and a VCS provider. -func (s *oAuthClients) Create(ctx context.Context, organization string, options OAuthClientCreateOptions) (*OAuthClient, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("organizations/%s/oauth-clients", url.QueryEscape(organization)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return nil, err - } - - oc := &OAuthClient{} - err = s.client.do(ctx, req, oc) - if err != nil { - return nil, err - } - - return oc, nil -} - -// Read an OAuth client by its ID. -func (s *oAuthClients) Read(ctx context.Context, oAuthClientID string) (*OAuthClient, error) { - if !validStringID(&oAuthClientID) { - return nil, errors.New("Invalid value for OAuth client ID") - } - - u := fmt.Sprintf("oauth-clients/%s", url.QueryEscape(oAuthClientID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - oc := &OAuthClient{} - err = s.client.do(ctx, req, oc) - if err != nil { - return nil, err - } - - return oc, err -} - -// Delete an OAuth client by its ID. -func (s *oAuthClients) Delete(ctx context.Context, oAuthClientID string) error { - if !validStringID(&oAuthClientID) { - return errors.New("Invalid value for OAuth client ID") - } - - u := fmt.Sprintf("oauth-clients/%s", url.QueryEscape(oAuthClientID)) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/oauth_token.go b/vendor/github.com/hashicorp/go-tfe/oauth_token.go deleted file mode 100644 index 2367a1e3b..000000000 --- a/vendor/github.com/hashicorp/go-tfe/oauth_token.go +++ /dev/null @@ -1,150 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ OAuthTokens = (*oAuthTokens)(nil) - -// OAuthTokens describes all the OAuth token related methods that the -// Terraform Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/oauth-tokens.html -type OAuthTokens interface { - // List all the OAuth tokens for a given organization. - List(ctx context.Context, organization string, options OAuthTokenListOptions) (*OAuthTokenList, error) - // Read a OAuth token by its ID. - Read(ctx context.Context, oAuthTokenID string) (*OAuthToken, error) - - // Update an existing OAuth token. - Update(ctx context.Context, oAuthTokenID string, options OAuthTokenUpdateOptions) (*OAuthToken, error) - - // Delete a OAuth token by its ID. - Delete(ctx context.Context, oAuthTokenID string) error -} - -// oAuthTokens implements OAuthTokens. -type oAuthTokens struct { - client *Client -} - -// OAuthTokenList represents a list of OAuth tokens. -type OAuthTokenList struct { - *Pagination - Items []*OAuthToken -} - -// OAuthToken represents a VCS configuration including the associated -// OAuth token -type OAuthToken struct { - ID string `jsonapi:"primary,oauth-tokens"` - UID string `jsonapi:"attr,uid"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - HasSSHKey bool `jsonapi:"attr,has-ssh-key"` - ServiceProviderUser string `jsonapi:"attr,service-provider-user"` - - // Relations - OAuthClient *OAuthClient `jsonapi:"relation,oauth-client"` -} - -// OAuthTokenListOptions represents the options for listing -// OAuth tokens. -type OAuthTokenListOptions struct { - ListOptions -} - -// List all the OAuth tokens for a given organization. -func (s *oAuthTokens) List(ctx context.Context, organization string, options OAuthTokenListOptions) (*OAuthTokenList, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/oauth-tokens", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - otl := &OAuthTokenList{} - err = s.client.do(ctx, req, otl) - if err != nil { - return nil, err - } - - return otl, nil -} - -// Read an OAuth token by its ID. -func (s *oAuthTokens) Read(ctx context.Context, oAuthTokenID string) (*OAuthToken, error) { - if !validStringID(&oAuthTokenID) { - return nil, errors.New("Invalid value for OAuth token ID") - } - - u := fmt.Sprintf("oauth-tokens/%s", url.QueryEscape(oAuthTokenID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - ot := &OAuthToken{} - err = s.client.do(ctx, req, ot) - if err != nil { - return nil, err - } - - return ot, err -} - -// OAuthTokenUpdateOptions represents the options for updating an OAuth token. -type OAuthTokenUpdateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,oauth-tokens"` - - // A private SSH key to be used for git clone operations. - PrivateSSHKey *string `jsonapi:"attr,ssh-key"` -} - -// Update an existing OAuth token. -func (s *oAuthTokens) Update(ctx context.Context, oAuthTokenID string, options OAuthTokenUpdateOptions) (*OAuthToken, error) { - if !validStringID(&oAuthTokenID) { - return nil, errors.New("Invalid value for OAuth token ID") - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("oauth-tokens/%s", url.QueryEscape(oAuthTokenID)) - req, err := s.client.newRequest("PATCH", u, &options) - if err != nil { - return nil, err - } - - ot := &OAuthToken{} - err = s.client.do(ctx, req, ot) - if err != nil { - return nil, err - } - - return ot, err -} - -// Delete an OAuth token by its ID. -func (s *oAuthTokens) Delete(ctx context.Context, oAuthTokenID string) error { - if !validStringID(&oAuthTokenID) { - return errors.New("Invalid value for OAuth token ID") - } - - u := fmt.Sprintf("oauth-tokens/%s", url.QueryEscape(oAuthTokenID)) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/organization.go b/vendor/github.com/hashicorp/go-tfe/organization.go deleted file mode 100644 index f4759a231..000000000 --- a/vendor/github.com/hashicorp/go-tfe/organization.go +++ /dev/null @@ -1,310 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ Organizations = (*organizations)(nil) - -// Organizations describes all the organization related methods that the -// Terraform Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/organizations.html -type Organizations interface { - // List all the organizations visible to the current user. - List(ctx context.Context, options OrganizationListOptions) (*OrganizationList, error) - - // Create a new organization with the given options. - Create(ctx context.Context, options OrganizationCreateOptions) (*Organization, error) - - // Read an organization by its name. - Read(ctx context.Context, organization string) (*Organization, error) - - // Update attributes of an existing organization. - Update(ctx context.Context, organization string, options OrganizationUpdateOptions) (*Organization, error) - - // Delete an organization by its name. - Delete(ctx context.Context, organization string) error - - // Capacity shows the current run capacity of an organization. - Capacity(ctx context.Context, organization string) (*Capacity, error) - - // RunQueue shows the current run queue of an organization. - RunQueue(ctx context.Context, organization string, options RunQueueOptions) (*RunQueue, error) -} - -// organizations implements Organizations. -type organizations struct { - client *Client -} - -// AuthPolicyType represents an authentication policy type. -type AuthPolicyType string - -// List of available authentication policies. -const ( - AuthPolicyPassword AuthPolicyType = "password" - AuthPolicyTwoFactor AuthPolicyType = "two_factor_mandatory" -) - -// EnterprisePlanType represents an enterprise plan type. -type EnterprisePlanType string - -// List of available enterprise plan types. -const ( - EnterprisePlanDisabled EnterprisePlanType = "disabled" - EnterprisePlanPremium EnterprisePlanType = "premium" - EnterprisePlanPro EnterprisePlanType = "pro" - EnterprisePlanTrial EnterprisePlanType = "trial" -) - -// OrganizationList represents a list of organizations. -type OrganizationList struct { - *Pagination - Items []*Organization -} - -// Organization represents a Terraform Enterprise organization. -type Organization struct { - Name string `jsonapi:"primary,organizations"` - CollaboratorAuthPolicy AuthPolicyType `jsonapi:"attr,collaborator-auth-policy"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - Email string `jsonapi:"attr,email"` - EnterprisePlan EnterprisePlanType `jsonapi:"attr,enterprise-plan"` - OwnersTeamSamlRoleID string `jsonapi:"attr,owners-team-saml-role-id"` - Permissions *OrganizationPermissions `jsonapi:"attr,permissions"` - SAMLEnabled bool `jsonapi:"attr,saml-enabled"` - SessionRemember int `jsonapi:"attr,session-remember"` - SessionTimeout int `jsonapi:"attr,session-timeout"` - TrialExpiresAt time.Time `jsonapi:"attr,trial-expires-at,iso8601"` - TwoFactorConformant bool `jsonapi:"attr,two-factor-conformant"` -} - -// Capacity represents the current run capacity of an organization. -type Capacity struct { - Organization string `jsonapi:"primary,organization-capacity"` - Pending int `jsonapi:"attr,pending"` - Running int `jsonapi:"attr,running"` -} - -// RunQueue represents the current run queue of an organization. -type RunQueue struct { - *Pagination - Items []*Run -} - -// OrganizationPermissions represents the organization permissions. -type OrganizationPermissions struct { - CanCreateTeam bool `json:"can-create-team"` - CanCreateWorkspace bool `json:"can-create-workspace"` - CanCreateWorkspaceMigration bool `json:"can-create-workspace-migration"` - CanDestroy bool `json:"can-destroy"` - CanTraverse bool `json:"can-traverse"` - CanUpdate bool `json:"can-update"` - CanUpdateAPIToken bool `json:"can-update-api-token"` - CanUpdateOAuth bool `json:"can-update-oauth"` - CanUpdateSentinel bool `json:"can-update-sentinel"` -} - -// OrganizationListOptions represents the options for listing organizations. -type OrganizationListOptions struct { - ListOptions -} - -// List all the organizations visible to the current user. -func (s *organizations) List(ctx context.Context, options OrganizationListOptions) (*OrganizationList, error) { - req, err := s.client.newRequest("GET", "organizations", &options) - if err != nil { - return nil, err - } - - orgl := &OrganizationList{} - err = s.client.do(ctx, req, orgl) - if err != nil { - return nil, err - } - - return orgl, nil -} - -// OrganizationCreateOptions represents the options for creating an organization. -type OrganizationCreateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,organizations"` - - // Name of the organization. - Name *string `jsonapi:"attr,name"` - - // Admin email address. - Email *string `jsonapi:"attr,email"` -} - -func (o OrganizationCreateOptions) valid() error { - if !validString(o.Name) { - return errors.New("Name is required") - } - if !validStringID(o.Name) { - return errors.New("Invalid value for name") - } - if !validString(o.Email) { - return errors.New("Email is required") - } - return nil -} - -// Create a new organization with the given options. -func (s *organizations) Create(ctx context.Context, options OrganizationCreateOptions) (*Organization, error) { - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - req, err := s.client.newRequest("POST", "organizations", &options) - if err != nil { - return nil, err - } - - org := &Organization{} - err = s.client.do(ctx, req, org) - if err != nil { - return nil, err - } - - return org, nil -} - -// Read an organization by its name. -func (s *organizations) Read(ctx context.Context, organization string) (*Organization, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - org := &Organization{} - err = s.client.do(ctx, req, org) - if err != nil { - return nil, err - } - - return org, nil -} - -// OrganizationUpdateOptions represents the options for updating an organization. -type OrganizationUpdateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,organizations"` - - // New name for the organization. - Name *string `jsonapi:"attr,name,omitempty"` - - // New admin email address. - Email *string `jsonapi:"attr,email,omitempty"` - - // Session expiration (minutes). - SessionRemember *int `jsonapi:"attr,session-remember,omitempty"` - - // Session timeout after inactivity (minutes). - SessionTimeout *int `jsonapi:"attr,session-timeout,omitempty"` - - // Authentication policy. - CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attr,collaborator-auth-policy,omitempty"` -} - -// Update attributes of an existing organization. -func (s *organizations) Update(ctx context.Context, organization string, options OrganizationUpdateOptions) (*Organization, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("organizations/%s", url.QueryEscape(organization)) - req, err := s.client.newRequest("PATCH", u, &options) - if err != nil { - return nil, err - } - - org := &Organization{} - err = s.client.do(ctx, req, org) - if err != nil { - return nil, err - } - - return org, nil -} - -// Delete an organization by its name. -func (s *organizations) Delete(ctx context.Context, organization string) error { - if !validStringID(&organization) { - return errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s", url.QueryEscape(organization)) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} - -// Capacity shows the currently used capacity of an organization. -func (s *organizations) Capacity(ctx context.Context, organization string) (*Capacity, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/capacity", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - c := &Capacity{} - err = s.client.do(ctx, req, c) - if err != nil { - return nil, err - } - - return c, nil -} - -// RunQueueOptions represents the options for showing the queue. -type RunQueueOptions struct { - ListOptions -} - -// RunQueue shows the current run queue of an organization. -func (s *organizations) RunQueue(ctx context.Context, organization string, options RunQueueOptions) (*RunQueue, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/runs/queue", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - rq := &RunQueue{} - err = s.client.do(ctx, req, rq) - if err != nil { - return nil, err - } - - return rq, nil -} diff --git a/vendor/github.com/hashicorp/go-tfe/organization_token.go b/vendor/github.com/hashicorp/go-tfe/organization_token.go deleted file mode 100644 index 33368da0b..000000000 --- a/vendor/github.com/hashicorp/go-tfe/organization_token.go +++ /dev/null @@ -1,99 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ OrganizationTokens = (*organizationTokens)(nil) - -// OrganizationTokens describes all the organization token related methods -// that the Terraform Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/organization-tokens.html -type OrganizationTokens interface { - // Generate a new organization token, replacing any existing token. - Generate(ctx context.Context, organization string) (*OrganizationToken, error) - - // Read an organization token. - Read(ctx context.Context, organization string) (*OrganizationToken, error) - - // Delete an organization token. - Delete(ctx context.Context, organization string) error -} - -// organizationTokens implements OrganizationTokens. -type organizationTokens struct { - client *Client -} - -// OrganizationToken represents a Terraform Enterprise organization token. -type OrganizationToken struct { - ID string `jsonapi:"primary,authentication-tokens"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - Description string `jsonapi:"attr,description"` - LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"` - Token string `jsonapi:"attr,token"` -} - -// Generate a new organization token, replacing any existing token. -func (s *organizationTokens) Generate(ctx context.Context, organization string) (*OrganizationToken, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/authentication-token", url.QueryEscape(organization)) - req, err := s.client.newRequest("POST", u, nil) - if err != nil { - return nil, err - } - - ot := &OrganizationToken{} - err = s.client.do(ctx, req, ot) - if err != nil { - return nil, err - } - - return ot, err -} - -// Read an organization token. -func (s *organizationTokens) Read(ctx context.Context, organization string) (*OrganizationToken, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/authentication-token", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - ot := &OrganizationToken{} - err = s.client.do(ctx, req, ot) - if err != nil { - return nil, err - } - - return ot, err -} - -// Delete an organization token. -func (s *organizationTokens) Delete(ctx context.Context, organization string) error { - if !validStringID(&organization) { - return errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/authentication-token", url.QueryEscape(organization)) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/plan.go b/vendor/github.com/hashicorp/go-tfe/plan.go deleted file mode 100644 index 9ac989b38..000000000 --- a/vendor/github.com/hashicorp/go-tfe/plan.go +++ /dev/null @@ -1,132 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "io" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ Plans = (*plans)(nil) - -// Plans describes all the plan related methods that the Terraform Enterprise -// API supports. -// -// TFE API docs: https://www.terraform.io/docs/enterprise/api/plan.html -type Plans interface { - // Read a plan by its ID. - Read(ctx context.Context, planID string) (*Plan, error) - - // Logs retrieves the logs of a plan. - Logs(ctx context.Context, planID string) (io.Reader, error) -} - -// plans implements Plans. -type plans struct { - client *Client -} - -// PlanStatus represents a plan state. -type PlanStatus string - -//List all available plan statuses. -const ( - PlanCanceled PlanStatus = "canceled" - PlanCreated PlanStatus = "created" - PlanErrored PlanStatus = "errored" - PlanFinished PlanStatus = "finished" - PlanMFAWaiting PlanStatus = "mfa_waiting" - PlanPending PlanStatus = "pending" - PlanQueued PlanStatus = "queued" - PlanRunning PlanStatus = "running" -) - -// Plan represents a Terraform Enterprise plan. -type Plan struct { - ID string `jsonapi:"primary,plans"` - HasChanges bool `jsonapi:"attr,has-changes"` - LogReadURL string `jsonapi:"attr,log-read-url"` - ResourceAdditions int `jsonapi:"attr,resource-additions"` - ResourceChanges int `jsonapi:"attr,resource-changes"` - ResourceDestructions int `jsonapi:"attr,resource-destructions"` - Status PlanStatus `jsonapi:"attr,status"` - StatusTimestamps *PlanStatusTimestamps `jsonapi:"attr,status-timestamps"` -} - -// PlanStatusTimestamps holds the timestamps for individual plan statuses. -type PlanStatusTimestamps struct { - CanceledAt time.Time `json:"canceled-at"` - ErroredAt time.Time `json:"errored-at"` - FinishedAt time.Time `json:"finished-at"` - ForceCanceledAt time.Time `json:"force-canceled-at"` - QueuedAt time.Time `json:"queued-at"` - StartedAt time.Time `json:"started-at"` -} - -// Read a plan by its ID. -func (s *plans) Read(ctx context.Context, planID string) (*Plan, error) { - if !validStringID(&planID) { - return nil, errors.New("Invalid value for plan ID") - } - - u := fmt.Sprintf("plans/%s", url.QueryEscape(planID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - p := &Plan{} - err = s.client.do(ctx, req, p) - if err != nil { - return nil, err - } - - return p, nil -} - -// Logs retrieves the logs of a plan. -func (s *plans) Logs(ctx context.Context, planID string) (io.Reader, error) { - if !validStringID(&planID) { - return nil, errors.New("Invalid value for plan ID") - } - - // Get the plan to make sure it exists. - p, err := s.Read(ctx, planID) - if err != nil { - return nil, err - } - - // Return an error if the log URL is empty. - if p.LogReadURL == "" { - return nil, fmt.Errorf("Plan %s does not have a log URL", planID) - } - - u, err := url.Parse(p.LogReadURL) - if err != nil { - return nil, fmt.Errorf("Invalid log URL: %v", err) - } - - done := func() (bool, error) { - p, err := s.Read(ctx, p.ID) - if err != nil { - return false, err - } - - switch p.Status { - case PlanCanceled, PlanErrored, PlanFinished: - return true, nil - default: - return false, nil - } - } - - return &LogReader{ - client: s.client, - ctx: ctx, - done: done, - logURL: u, - }, nil -} diff --git a/vendor/github.com/hashicorp/go-tfe/policy.go b/vendor/github.com/hashicorp/go-tfe/policy.go deleted file mode 100644 index 80926af37..000000000 --- a/vendor/github.com/hashicorp/go-tfe/policy.go +++ /dev/null @@ -1,282 +0,0 @@ -package tfe - -import ( - "bytes" - "context" - "errors" - "fmt" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ Policies = (*policies)(nil) - -// Policies describes all the policy related methods that the Terraform -// Enterprise API supports. -// -// TFE API docs: https://www.terraform.io/docs/enterprise/api/policies.html -type Policies interface { - // List all the policies for a given organization - List(ctx context.Context, organization string, options PolicyListOptions) (*PolicyList, error) - - // Create a policy and associate it with an organization. - Create(ctx context.Context, organization string, options PolicyCreateOptions) (*Policy, error) - - // Read a policy by its ID. - Read(ctx context.Context, policyID string) (*Policy, error) - - // Update an existing policy. - Update(ctx context.Context, policyID string, options PolicyUpdateOptions) (*Policy, error) - - // Delete a policy by its ID. - Delete(ctx context.Context, policyID string) error - - // Upload the policy content of the policy. - Upload(ctx context.Context, policyID string, content []byte) error - - // Upload the policy content of the policy. - Download(ctx context.Context, policyID string) ([]byte, error) -} - -// policies implements Policies. -type policies struct { - client *Client -} - -// EnforcementLevel represents an enforcement level. -type EnforcementLevel string - -// List the available enforcement types. -const ( - EnforcementAdvisory EnforcementLevel = "advisory" - EnforcementHard EnforcementLevel = "hard-mandatory" - EnforcementSoft EnforcementLevel = "soft-mandatory" -) - -// PolicyList represents a list of policies.. -type PolicyList struct { - *Pagination - Items []*Policy -} - -// Policy represents a Terraform Enterprise policy. -type Policy struct { - ID string `jsonapi:"primary,policies"` - Name string `jsonapi:"attr,name"` - Enforce []*Enforcement `jsonapi:"attr,enforce"` - UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"` -} - -// Enforcement describes a enforcement. -type Enforcement struct { - Path string `json:"path"` - Mode EnforcementLevel `json:"mode"` -} - -// PolicyListOptions represents the options for listing policies. -type PolicyListOptions struct { - ListOptions -} - -// List all the policies for a given organization -func (s *policies) List(ctx context.Context, organization string, options PolicyListOptions) (*PolicyList, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/policies", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - pl := &PolicyList{} - err = s.client.do(ctx, req, pl) - if err != nil { - return nil, err - } - - return pl, nil -} - -// PolicyCreateOptions represents the options for creating a new policy. -type PolicyCreateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,policies"` - - // The name of the policy. - Name *string `jsonapi:"attr,name"` - - // The enforcements of the policy. - Enforce []*EnforcementOptions `jsonapi:"attr,enforce"` -} - -// EnforcementOptions represents the enforcement options of a policy. -type EnforcementOptions struct { - Path *string `json:"path,omitempty"` - Mode *EnforcementLevel `json:"mode"` -} - -func (o PolicyCreateOptions) valid() error { - if !validString(o.Name) { - return errors.New("Name is required") - } - if !validStringID(o.Name) { - return errors.New("Invalid value for name") - } - if o.Enforce == nil { - return errors.New("Enforce is required") - } - for _, e := range o.Enforce { - if !validString(e.Path) { - return errors.New("Enforcement path is required") - } - if e.Mode == nil { - return errors.New("Enforcement mode is required") - } - } - return nil -} - -// Create a policy and associate it with an organization. -func (s *policies) Create(ctx context.Context, organization string, options PolicyCreateOptions) (*Policy, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("organizations/%s/policies", url.QueryEscape(organization)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return nil, err - } - - p := &Policy{} - err = s.client.do(ctx, req, p) - if err != nil { - return nil, err - } - - return p, err -} - -// Read a policy by its ID. -func (s *policies) Read(ctx context.Context, policyID string) (*Policy, error) { - if !validStringID(&policyID) { - return nil, errors.New("Invalid value for policy ID") - } - - u := fmt.Sprintf("policies/%s", url.QueryEscape(policyID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - p := &Policy{} - err = s.client.do(ctx, req, p) - if err != nil { - return nil, err - } - - return p, err -} - -// PolicyUpdateOptions represents the options for updating a policy. -type PolicyUpdateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,policies"` - - // The enforcements of the policy. - Enforce []*EnforcementOptions `jsonapi:"attr,enforce"` -} - -func (o PolicyUpdateOptions) valid() error { - if o.Enforce == nil { - return errors.New("Enforce is required") - } - return nil -} - -// Update an existing policy. -func (s *policies) Update(ctx context.Context, policyID string, options PolicyUpdateOptions) (*Policy, error) { - if !validStringID(&policyID) { - return nil, errors.New("Invalid value for policy ID") - } - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("policies/%s", url.QueryEscape(policyID)) - req, err := s.client.newRequest("PATCH", u, &options) - if err != nil { - return nil, err - } - - p := &Policy{} - err = s.client.do(ctx, req, p) - if err != nil { - return nil, err - } - - return p, err -} - -// Delete a policy by its ID. -func (s *policies) Delete(ctx context.Context, policyID string) error { - if !validStringID(&policyID) { - return errors.New("Invalid value for policy ID") - } - - u := fmt.Sprintf("policies/%s", url.QueryEscape(policyID)) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} - -// Upload the policy content of the policy. -func (s *policies) Upload(ctx context.Context, policyID string, content []byte) error { - if !validStringID(&policyID) { - return errors.New("Invalid value for policy ID") - } - - u := fmt.Sprintf("policies/%s/upload", url.QueryEscape(policyID)) - req, err := s.client.newRequest("PUT", u, content) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} - -// Download the policy content of the policy. -func (s *policies) Download(ctx context.Context, policyID string) ([]byte, error) { - if !validStringID(&policyID) { - return nil, errors.New("Invalid value for policy ID") - } - - u := fmt.Sprintf("policies/%s/download", url.QueryEscape(policyID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - var buf bytes.Buffer - err = s.client.do(ctx, req, &buf) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} diff --git a/vendor/github.com/hashicorp/go-tfe/policy_check.go b/vendor/github.com/hashicorp/go-tfe/policy_check.go deleted file mode 100644 index 6b0d6b2a3..000000000 --- a/vendor/github.com/hashicorp/go-tfe/policy_check.go +++ /dev/null @@ -1,219 +0,0 @@ -package tfe - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ PolicyChecks = (*policyChecks)(nil) - -// PolicyChecks describes all the policy check related methods that the -// Terraform Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/policy-checks.html -type PolicyChecks interface { - // List all policy checks of the given run. - List(ctx context.Context, runID string, options PolicyCheckListOptions) (*PolicyCheckList, error) - - // Read a policy check by its ID. - Read(ctx context.Context, policyCheckID string) (*PolicyCheck, error) - - // Override a soft-mandatory or warning policy. - Override(ctx context.Context, policyCheckID string) (*PolicyCheck, error) - - // Logs retrieves the logs of a policy check. - Logs(ctx context.Context, policyCheckID string) (io.Reader, error) -} - -// policyChecks implements PolicyChecks. -type policyChecks struct { - client *Client -} - -// PolicyScope represents a policy scope. -type PolicyScope string - -// List all available policy scopes. -const ( - PolicyScopeOrganization PolicyScope = "organization" - PolicyScopeWorkspace PolicyScope = "workspace" -) - -// PolicyStatus represents a policy check state. -type PolicyStatus string - -//List all available policy check statuses. -const ( - PolicyErrored PolicyStatus = "errored" - PolicyHardFailed PolicyStatus = "hard_failed" - PolicyOverridden PolicyStatus = "overridden" - PolicyPasses PolicyStatus = "passed" - PolicyPending PolicyStatus = "pending" - PolicyQueued PolicyStatus = "queued" - PolicySoftFailed PolicyStatus = "soft_failed" -) - -// PolicyCheckList represents a list of policy checks. -type PolicyCheckList struct { - *Pagination - Items []*PolicyCheck -} - -// PolicyCheck represents a Terraform Enterprise policy check.. -type PolicyCheck struct { - ID string `jsonapi:"primary,policy-checks"` - Actions *PolicyActions `jsonapi:"attr,actions"` - Permissions *PolicyPermissions `jsonapi:"attr,permissions"` - Result *PolicyResult `jsonapi:"attr,result"` - Scope PolicyScope `jsonapi:"attr,scope"` - Status PolicyStatus `jsonapi:"attr,status"` - StatusTimestamps *PolicyStatusTimestamps `jsonapi:"attr,status-timestamps"` -} - -// PolicyActions represents the policy check actions. -type PolicyActions struct { - IsOverridable bool `json:"is-overridable"` -} - -// PolicyPermissions represents the policy check permissions. -type PolicyPermissions struct { - CanOverride bool `json:"can-override"` -} - -// PolicyResult represents the complete policy check result, -type PolicyResult struct { - AdvisoryFailed int `json:"advisory-failed"` - Duration int `json:"duration"` - HardFailed int `json:"hard-failed"` - Passed int `json:"passed"` - Result bool `json:"result"` - // Sentinel *sentinel.EvalResult `json:"sentinel"` - SoftFailed int `json:"soft-failed"` - TotalFailed int `json:"total-failed"` -} - -// PolicyStatusTimestamps holds the timestamps for individual policy check -// statuses. -type PolicyStatusTimestamps struct { - ErroredAt time.Time `json:"errored-at"` - HardFailedAt time.Time `json:"hard-failed-at"` - PassedAt time.Time `json:"passed-at"` - QueuedAt time.Time `json:"queued-at"` - SoftFailedAt time.Time `json:"soft-failed-at"` -} - -// PolicyCheckListOptions represents the options for listing policy checks. -type PolicyCheckListOptions struct { - ListOptions -} - -// List all policy checks of the given run. -func (s *policyChecks) List(ctx context.Context, runID string, options PolicyCheckListOptions) (*PolicyCheckList, error) { - if !validStringID(&runID) { - return nil, errors.New("Invalid value for run ID") - } - - u := fmt.Sprintf("runs/%s/policy-checks", url.QueryEscape(runID)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - pcl := &PolicyCheckList{} - err = s.client.do(ctx, req, pcl) - if err != nil { - return nil, err - } - - return pcl, nil -} - -// Read a policy check by its ID. -func (s *policyChecks) Read(ctx context.Context, policyCheckID string) (*PolicyCheck, error) { - if !validStringID(&policyCheckID) { - return nil, errors.New("Invalid value for policy check ID") - } - - u := fmt.Sprintf("policy-checks/%s", url.QueryEscape(policyCheckID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - pc := &PolicyCheck{} - err = s.client.do(ctx, req, pc) - if err != nil { - return nil, err - } - - return pc, nil -} - -// Override a soft-mandatory or warning policy. -func (s *policyChecks) Override(ctx context.Context, policyCheckID string) (*PolicyCheck, error) { - if !validStringID(&policyCheckID) { - return nil, errors.New("Invalid value for policy check ID") - } - - u := fmt.Sprintf("policy-checks/%s/actions/override", url.QueryEscape(policyCheckID)) - req, err := s.client.newRequest("POST", u, nil) - if err != nil { - return nil, err - } - - pc := &PolicyCheck{} - err = s.client.do(ctx, req, pc) - if err != nil { - return nil, err - } - - return pc, nil -} - -// Logs retrieves the logs of a policy check. -func (s *policyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) { - if !validStringID(&policyCheckID) { - return nil, errors.New("Invalid value for policy check ID") - } - - // Loop until the context is canceled or the policy check is finished - // running. The policy check logs are not streamed and so only available - // once the check is finished. - for { - pc, err := s.Read(ctx, policyCheckID) - if err != nil { - return nil, err - } - - switch pc.Status { - case PolicyPending, PolicyQueued: - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-time.After(500 * time.Millisecond): - continue - } - } - - u := fmt.Sprintf("policy-checks/%s/output", url.QueryEscape(policyCheckID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - logs := bytes.NewBuffer(nil) - err = s.client.do(ctx, req, logs) - if err != nil { - return nil, err - } - - return logs, nil - } -} diff --git a/vendor/github.com/hashicorp/go-tfe/run.go b/vendor/github.com/hashicorp/go-tfe/run.go deleted file mode 100644 index c666c4939..000000000 --- a/vendor/github.com/hashicorp/go-tfe/run.go +++ /dev/null @@ -1,309 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ Runs = (*runs)(nil) - -// Runs describes all the run related methods that the Terraform Enterprise -// API supports. -// -// TFE API docs: https://www.terraform.io/docs/enterprise/api/run.html -type Runs interface { - // List all the runs of the given workspace. - List(ctx context.Context, workspaceID string, options RunListOptions) (*RunList, error) - - // Create a new run with the given options. - Create(ctx context.Context, options RunCreateOptions) (*Run, error) - - // Read a run by its ID. - Read(ctx context.Context, runID string) (*Run, error) - - // Apply a run by its ID. - Apply(ctx context.Context, runID string, options RunApplyOptions) error - - // Cancel a run by its ID. - Cancel(ctx context.Context, runID string, options RunCancelOptions) error - - // Force-cancel a run by its ID. - ForceCancel(ctx context.Context, runID string, options RunForceCancelOptions) error - - // Discard a run by its ID. - Discard(ctx context.Context, runID string, options RunDiscardOptions) error -} - -// runs implements Runs. -type runs struct { - client *Client -} - -// RunStatus represents a run state. -type RunStatus string - -//List all available run statuses. -const ( - RunApplied RunStatus = "applied" - RunApplying RunStatus = "applying" - RunCanceled RunStatus = "canceled" - RunConfirmed RunStatus = "confirmed" - RunDiscarded RunStatus = "discarded" - RunErrored RunStatus = "errored" - RunPending RunStatus = "pending" - RunPlanned RunStatus = "planned" - RunPlanning RunStatus = "planning" - RunPolicyChecked RunStatus = "policy_checked" - RunPolicyChecking RunStatus = "policy_checking" - RunPolicyOverride RunStatus = "policy_override" -) - -// RunSource represents a source type of a run. -type RunSource string - -// List all available run sources. -const ( - RunSourceAPI RunSource = "tfe-api" - RunSourceConfigurationVersion RunSource = "tfe-configuration-version" - RunSourceUI RunSource = "tfe-ui" -) - -// RunList represents a list of runs. -type RunList struct { - *Pagination - Items []*Run -} - -// Run represents a Terraform Enterprise run. -type Run struct { - ID string `jsonapi:"primary,runs"` - Actions *RunActions `jsonapi:"attr,actions"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - ForceCancelAvailableAt time.Time `jsonapi:"attr,force-cancel-available-at,iso8601"` - HasChanges bool `jsonapi:"attr,has-changes"` - IsDestroy bool `jsonapi:"attr,is-destroy"` - Message string `jsonapi:"attr,message"` - Permissions *RunPermissions `jsonapi:"attr,permissions"` - PositionInQueue int `jsonapi:"attr,position-in-queue"` - Source RunSource `jsonapi:"attr,source"` - Status RunStatus `jsonapi:"attr,status"` - StatusTimestamps *RunStatusTimestamps `jsonapi:"attr,status-timestamps"` - - // Relations - Apply *Apply `jsonapi:"relation,apply"` - ConfigurationVersion *ConfigurationVersion `jsonapi:"relation,configuration-version"` - Plan *Plan `jsonapi:"relation,plan"` - PolicyChecks []*PolicyCheck `jsonapi:"relation,policy-checks"` - Workspace *Workspace `jsonapi:"relation,workspace"` -} - -// RunActions represents the run actions. -type RunActions struct { - IsCancelable bool `json:"is-cancelable"` - IsConfirmable bool `json:"is-confirmable"` - IsDiscardable bool `json:"is-discardable"` - IsForceCancelable bool `json:"is-force-cancelable"` -} - -// RunPermissions represents the run permissions. -type RunPermissions struct { - CanApply bool `json:"can-apply"` - CanCancel bool `json:"can-cancel"` - CanDiscard bool `json:"can-discard"` - CanForceCancel bool `json:"can-force-cancel"` - CanForceExecute bool `json:"can-force-execute"` -} - -// RunStatusTimestamps holds the timestamps for individual run statuses. -type RunStatusTimestamps struct { - ErroredAt time.Time `json:"errored-at"` - FinishedAt time.Time `json:"finished-at"` - QueuedAt time.Time `json:"queued-at"` - StartedAt time.Time `json:"started-at"` -} - -// RunListOptions represents the options for listing runs. -type RunListOptions struct { - ListOptions -} - -// List all the runs of the given workspace. -func (s *runs) List(ctx context.Context, workspaceID string, options RunListOptions) (*RunList, error) { - if !validStringID(&workspaceID) { - return nil, errors.New("Invalid value for workspace ID") - } - - u := fmt.Sprintf("workspaces/%s/runs", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - rl := &RunList{} - err = s.client.do(ctx, req, rl) - if err != nil { - return nil, err - } - - return rl, nil -} - -// RunCreateOptions represents the options for creating a new run. -type RunCreateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,runs"` - - // Specifies if this plan is a destroy plan, which will destroy all - // provisioned resources. - IsDestroy *bool `jsonapi:"attr,is-destroy,omitempty"` - - // Specifies the message to be associated with this run. - Message *string `jsonapi:"attr,message,omitempty"` - - // Specifies the configuration version to use for this run. If the - // configuration version object is omitted, the run will be created using the - // workspace's latest configuration version. - ConfigurationVersion *ConfigurationVersion `jsonapi:"relation,configuration-version"` - - // Specifies the workspace where the run will be executed. - Workspace *Workspace `jsonapi:"relation,workspace"` -} - -func (o RunCreateOptions) valid() error { - if o.Workspace == nil { - return errors.New("Workspace is required") - } - return nil -} - -// Create a new run with the given options. -func (s *runs) Create(ctx context.Context, options RunCreateOptions) (*Run, error) { - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - req, err := s.client.newRequest("POST", "runs", &options) - if err != nil { - return nil, err - } - - r := &Run{} - err = s.client.do(ctx, req, r) - if err != nil { - return nil, err - } - - return r, nil -} - -// Read a run by its ID. -func (s *runs) Read(ctx context.Context, runID string) (*Run, error) { - if !validStringID(&runID) { - return nil, errors.New("Invalid value for run ID") - } - - u := fmt.Sprintf("runs/%s", url.QueryEscape(runID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - r := &Run{} - err = s.client.do(ctx, req, r) - if err != nil { - return nil, err - } - - return r, nil -} - -// RunApplyOptions represents the options for applying a run. -type RunApplyOptions struct { - // An optional comment about the run. - Comment *string `json:"comment,omitempty"` -} - -// Apply a run by its ID. -func (s *runs) Apply(ctx context.Context, runID string, options RunApplyOptions) error { - if !validStringID(&runID) { - return errors.New("Invalid value for run ID") - } - - u := fmt.Sprintf("runs/%s/actions/apply", url.QueryEscape(runID)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} - -// RunCancelOptions represents the options for canceling a run. -type RunCancelOptions struct { - // An optional explanation for why the run was canceled. - Comment *string `json:"comment,omitempty"` -} - -// Cancel a run by its ID. -func (s *runs) Cancel(ctx context.Context, runID string, options RunCancelOptions) error { - if !validStringID(&runID) { - return errors.New("Invalid value for run ID") - } - - u := fmt.Sprintf("runs/%s/actions/cancel", url.QueryEscape(runID)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} - -// RunCancelOptions represents the options for force-canceling a run. -type RunForceCancelOptions struct { - // An optional comment explaining the reason for the force-cancel. - Comment *string `json:"comment,omitempty"` -} - -// ForceCancel is used to forcefully cancel a run by its ID. -func (s *runs) ForceCancel(ctx context.Context, runID string, options RunForceCancelOptions) error { - if !validStringID(&runID) { - return errors.New("Invalid value for run ID") - } - - u := fmt.Sprintf("runs/%s/actions/force-cancel", url.QueryEscape(runID)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} - -// RunDiscardOptions represents the options for discarding a run. -type RunDiscardOptions struct { - // An optional explanation for why the run was discarded. - Comment *string `json:"comment,omitempty"` -} - -// Discard a run by its ID. -func (s *runs) Discard(ctx context.Context, runID string, options RunDiscardOptions) error { - if !validStringID(&runID) { - return errors.New("Invalid value for run ID") - } - - u := fmt.Sprintf("runs/%s/actions/discard", url.QueryEscape(runID)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/ssh_key.go b/vendor/github.com/hashicorp/go-tfe/ssh_key.go deleted file mode 100644 index b76a170db..000000000 --- a/vendor/github.com/hashicorp/go-tfe/ssh_key.go +++ /dev/null @@ -1,198 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" -) - -// Compile-time proof of interface implementation. -var _ SSHKeys = (*sshKeys)(nil) - -// SSHKeys describes all the SSH key related methods that the Terraform -// Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/ssh-keys.html -type SSHKeys interface { - // List all the SSH keys for a given organization - List(ctx context.Context, organization string, options SSHKeyListOptions) (*SSHKeyList, error) - - // Create an SSH key and associate it with an organization. - Create(ctx context.Context, organization string, options SSHKeyCreateOptions) (*SSHKey, error) - - // Read an SSH key by its ID. - Read(ctx context.Context, sshKeyID string) (*SSHKey, error) - - // Update an SSH key by its ID. - Update(ctx context.Context, sshKeyID string, options SSHKeyUpdateOptions) (*SSHKey, error) - - // Delete an SSH key by its ID. - Delete(ctx context.Context, sshKeyID string) error -} - -// sshKeys implements SSHKeys. -type sshKeys struct { - client *Client -} - -// SSHKeyList represents a list of SSH keys. -type SSHKeyList struct { - *Pagination - Items []*SSHKey -} - -// SSHKey represents a SSH key. -type SSHKey struct { - ID string `jsonapi:"primary,ssh-keys"` - Name string `jsonapi:"attr,name"` -} - -// SSHKeyListOptions represents the options for listing SSH keys. -type SSHKeyListOptions struct { - ListOptions -} - -// List all the SSH keys for a given organization -func (s *sshKeys) List(ctx context.Context, organization string, options SSHKeyListOptions) (*SSHKeyList, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/ssh-keys", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - kl := &SSHKeyList{} - err = s.client.do(ctx, req, kl) - if err != nil { - return nil, err - } - - return kl, nil -} - -// SSHKeyCreateOptions represents the options for creating an SSH key. -type SSHKeyCreateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,ssh-keys"` - - // A name to identify the SSH key. - Name *string `jsonapi:"attr,name"` - - // The content of the SSH private key. - Value *string `jsonapi:"attr,value"` -} - -func (o SSHKeyCreateOptions) valid() error { - if !validString(o.Name) { - return errors.New("Name is required") - } - if !validString(o.Value) { - return errors.New("Value is required") - } - return nil -} - -// Create an SSH key and associate it with an organization. -func (s *sshKeys) Create(ctx context.Context, organization string, options SSHKeyCreateOptions) (*SSHKey, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("organizations/%s/ssh-keys", url.QueryEscape(organization)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return nil, err - } - - k := &SSHKey{} - err = s.client.do(ctx, req, k) - if err != nil { - return nil, err - } - - return k, nil -} - -// Read an SSH key by its ID. -func (s *sshKeys) Read(ctx context.Context, sshKeyID string) (*SSHKey, error) { - if !validStringID(&sshKeyID) { - return nil, errors.New("Invalid value for SSH key ID") - } - - u := fmt.Sprintf("ssh-keys/%s", url.QueryEscape(sshKeyID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - k := &SSHKey{} - err = s.client.do(ctx, req, k) - if err != nil { - return nil, err - } - - return k, nil -} - -// SSHKeyUpdateOptions represents the options for updating an SSH key. -type SSHKeyUpdateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,ssh-keys"` - - // A new name to identify the SSH key. - Name *string `jsonapi:"attr,name,omitempty"` - - // Updated content of the SSH private key. - Value *string `jsonapi:"attr,value,omitempty"` -} - -// Update an SSH key by its ID. -func (s *sshKeys) Update(ctx context.Context, sshKeyID string, options SSHKeyUpdateOptions) (*SSHKey, error) { - if !validStringID(&sshKeyID) { - return nil, errors.New("Invalid value for SSH key ID") - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("ssh-keys/%s", url.QueryEscape(sshKeyID)) - req, err := s.client.newRequest("PATCH", u, &options) - if err != nil { - return nil, err - } - - k := &SSHKey{} - err = s.client.do(ctx, req, k) - if err != nil { - return nil, err - } - - return k, nil -} - -// Delete an SSH key by its ID. -func (s *sshKeys) Delete(ctx context.Context, sshKeyID string) error { - if !validStringID(&sshKeyID) { - return errors.New("Invalid value for SSH key ID") - } - - u := fmt.Sprintf("ssh-keys/%s", url.QueryEscape(sshKeyID)) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/state_version.go b/vendor/github.com/hashicorp/go-tfe/state_version.go deleted file mode 100644 index 89bcfda1c..000000000 --- a/vendor/github.com/hashicorp/go-tfe/state_version.go +++ /dev/null @@ -1,216 +0,0 @@ -package tfe - -import ( - "bytes" - "context" - "errors" - "fmt" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ StateVersions = (*stateVersions)(nil) - -// StateVersions describes all the state version related methods that -// the Terraform Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/state-versions.html -type StateVersions interface { - // List all the state versions for a given workspace. - List(ctx context.Context, options StateVersionListOptions) (*StateVersionList, error) - - // Create a new state version for the given workspace. - Create(ctx context.Context, workspaceID string, options StateVersionCreateOptions) (*StateVersion, error) - - // Read a state version by its ID. - Read(ctx context.Context, svID string) (*StateVersion, error) - - // Current reads the latest available state from the given workspace. - Current(ctx context.Context, workspaceID string) (*StateVersion, error) - - // Download retrieves the actual stored state of a state version - Download(ctx context.Context, url string) ([]byte, error) -} - -// stateVersions implements StateVersions. -type stateVersions struct { - client *Client -} - -// StateVersionList represents a list of state versions. -type StateVersionList struct { - *Pagination - Items []*StateVersion -} - -// StateVersion represents a Terraform Enterprise state version. -type StateVersion struct { - ID string `jsonapi:"primary,state-versions"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - DownloadURL string `jsonapi:"attr,hosted-state-download-url"` - Serial int64 `jsonapi:"attr,serial"` - VCSCommitSHA string `jsonapi:"attr,vcs-commit-sha"` - VCSCommitURL string `jsonapi:"attr,vcs-commit-url"` - - // Relations - Run *Run `jsonapi:"relation,run"` -} - -// StateVersionListOptions represents the options for listing state versions. -type StateVersionListOptions struct { - ListOptions - Organization *string `url:"filter[organization][name]"` - Workspace *string `url:"filter[workspace][name]"` -} - -func (o StateVersionListOptions) valid() error { - if !validString(o.Organization) { - return errors.New("Organization is required") - } - if !validString(o.Workspace) { - return errors.New("Workspace is required") - } - return nil -} - -// List all the state versions for a given workspace. -func (s *stateVersions) List(ctx context.Context, options StateVersionListOptions) (*StateVersionList, error) { - if err := options.valid(); err != nil { - return nil, err - } - - req, err := s.client.newRequest("GET", "state-versions", &options) - if err != nil { - return nil, err - } - - svl := &StateVersionList{} - err = s.client.do(ctx, req, svl) - if err != nil { - return nil, err - } - - return svl, nil -} - -// StateVersionCreateOptions represents the options for creating a state version. -type StateVersionCreateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,state-versions"` - - // The lineage of the state. - Lineage *string `jsonapi:"attr,lineage,omitempty"` - - // The MD5 hash of the state version. - MD5 *string `jsonapi:"attr,md5"` - - // The serial of the state. - Serial *int64 `jsonapi:"attr,serial"` - - // The base64 encoded state. - State *string `jsonapi:"attr,state"` - - // Specifies the run to associate the state with. - Run *Run `jsonapi:"relation,run,omitempty"` -} - -func (o StateVersionCreateOptions) valid() error { - if !validString(o.MD5) { - return errors.New("MD5 is required") - } - if o.Serial == nil { - return errors.New("Serial is required") - } - if !validString(o.State) { - return errors.New("State is required") - } - return nil -} - -// Create a new state version for the given workspace. -func (s *stateVersions) Create(ctx context.Context, workspaceID string, options StateVersionCreateOptions) (*StateVersion, error) { - if !validStringID(&workspaceID) { - return nil, errors.New("Invalid value for workspace ID") - } - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("workspaces/%s/state-versions", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return nil, err - } - - sv := &StateVersion{} - err = s.client.do(ctx, req, sv) - if err != nil { - return nil, err - } - - return sv, nil -} - -// Read a state version by its ID. -func (s *stateVersions) Read(ctx context.Context, svID string) (*StateVersion, error) { - if !validStringID(&svID) { - return nil, errors.New("Invalid value for state version ID") - } - - u := fmt.Sprintf("state-versions/%s", url.QueryEscape(svID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - sv := &StateVersion{} - err = s.client.do(ctx, req, sv) - if err != nil { - return nil, err - } - - return sv, nil -} - -// Current reads the latest available state from the given workspace. -func (s *stateVersions) Current(ctx context.Context, workspaceID string) (*StateVersion, error) { - if !validStringID(&workspaceID) { - return nil, errors.New("Invalid value for workspace ID") - } - - u := fmt.Sprintf("workspaces/%s/current-state-version", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - sv := &StateVersion{} - err = s.client.do(ctx, req, sv) - if err != nil { - return nil, err - } - - return sv, nil -} - -// Download retrieves the actual stored state of a state version -func (s *stateVersions) Download(ctx context.Context, url string) ([]byte, error) { - req, err := s.client.newRequest("GET", url, nil) - if err != nil { - return nil, err - } - req.Header.Set("Accept", "application/json") - - var buf bytes.Buffer - err = s.client.do(ctx, req, &buf) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} diff --git a/vendor/github.com/hashicorp/go-tfe/team.go b/vendor/github.com/hashicorp/go-tfe/team.go deleted file mode 100644 index e6a69c3d2..000000000 --- a/vendor/github.com/hashicorp/go-tfe/team.go +++ /dev/null @@ -1,165 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" -) - -// Compile-time proof of interface implementation. -var _ Teams = (*teams)(nil) - -// Teams describes all the team related methods that the Terraform -// Enterprise API supports. -// -// TFE API docs: https://www.terraform.io/docs/enterprise/api/teams.html -type Teams interface { - // List all the teams of the given organization. - List(ctx context.Context, organization string, options TeamListOptions) (*TeamList, error) - - // Create a new team with the given options. - Create(ctx context.Context, organization string, options TeamCreateOptions) (*Team, error) - - // Read a team by its ID. - Read(ctx context.Context, teamID string) (*Team, error) - - // Delete a team by its ID. - Delete(ctx context.Context, teamID string) error -} - -// teams implements Teams. -type teams struct { - client *Client -} - -// TeamList represents a list of teams. -type TeamList struct { - *Pagination - Items []*Team -} - -// Team represents a Terraform Enterprise team. -type Team struct { - ID string `jsonapi:"primary,teams"` - Name string `jsonapi:"attr,name"` - Permissions *TeamPermissions `jsonapi:"attr,permissions"` - UserCount int `jsonapi:"attr,users-count"` - - // Relations - Users []*User `jsonapi:"relation,users"` -} - -// TeamPermissions represents the team permissions. -type TeamPermissions struct { - CanDestroy bool `json:"can-destroy"` - CanUpdateMembership bool `json:"can-update-membership"` -} - -// TeamListOptions represents the options for listing teams. -type TeamListOptions struct { - ListOptions -} - -// List all the teams of the given organization. -func (s *teams) List(ctx context.Context, organization string, options TeamListOptions) (*TeamList, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/teams", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - tl := &TeamList{} - err = s.client.do(ctx, req, tl) - if err != nil { - return nil, err - } - - return tl, nil -} - -// TeamCreateOptions represents the options for creating a team. -type TeamCreateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,teams"` - - // Name of the team. - Name *string `jsonapi:"attr,name"` -} - -func (o TeamCreateOptions) valid() error { - if !validString(o.Name) { - return errors.New("Name is required") - } - if !validStringID(o.Name) { - return errors.New("Invalid value for name") - } - return nil -} - -// Create a new team with the given options. -func (s *teams) Create(ctx context.Context, organization string, options TeamCreateOptions) (*Team, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("organizations/%s/teams", url.QueryEscape(organization)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return nil, err - } - - t := &Team{} - err = s.client.do(ctx, req, t) - if err != nil { - return nil, err - } - - return t, nil -} - -// Read a single team by its ID. -func (s *teams) Read(ctx context.Context, teamID string) (*Team, error) { - if !validStringID(&teamID) { - return nil, errors.New("Invalid value for team ID") - } - - u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - t := &Team{} - err = s.client.do(ctx, req, t) - if err != nil { - return nil, err - } - - return t, nil -} - -// Delete a team by its ID. -func (s *teams) Delete(ctx context.Context, teamID string) error { - if !validStringID(&teamID) { - return errors.New("Invalid value for team ID") - } - - u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID)) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/team_access.go b/vendor/github.com/hashicorp/go-tfe/team_access.go deleted file mode 100644 index 33abc3225..000000000 --- a/vendor/github.com/hashicorp/go-tfe/team_access.go +++ /dev/null @@ -1,184 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" -) - -// Compile-time proof of interface implementation. -var _ TeamAccesses = (*teamAccesses)(nil) - -// TeamAccesses describes all the team access related methods that the -// Terraform Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/team-access.html -type TeamAccesses interface { - // List all the team accesses for a given workspace. - List(ctx context.Context, options TeamAccessListOptions) (*TeamAccessList, error) - - // Add team access for a workspace. - Add(ctx context.Context, options TeamAccessAddOptions) (*TeamAccess, error) - - // Read a team access by its ID. - Read(ctx context.Context, teamAccessID string) (*TeamAccess, error) - - // Remove team access from a workspace. - Remove(ctx context.Context, teamAccessID string) error -} - -// teamAccesses implements TeamAccesses. -type teamAccesses struct { - client *Client -} - -// AccessType represents a team access type. -type AccessType string - -// List all available team access types. -const ( - AccessAdmin AccessType = "admin" - AccessRead AccessType = "read" - AccessWrite AccessType = "write" -) - -// TeamAccessList represents a list of team accesses. -type TeamAccessList struct { - *Pagination - Items []*TeamAccess -} - -// TeamAccess represents the workspace access for a team. -type TeamAccess struct { - ID string `jsonapi:"primary,team-workspaces"` - Access AccessType `jsonapi:"attr,access"` - - // Relations - Team *Team `jsonapi:"relation,team"` - Workspace *Workspace `jsonapi:"relation,workspace"` -} - -// TeamAccessListOptions represents the options for listing team accesses. -type TeamAccessListOptions struct { - ListOptions - WorkspaceID *string `url:"filter[workspace][id],omitempty"` -} - -func (o TeamAccessListOptions) valid() error { - if !validString(o.WorkspaceID) { - return errors.New("Workspace ID is required") - } - if !validStringID(o.WorkspaceID) { - return errors.New("Invalid value for workspace ID") - } - return nil -} - -// List all the team accesses for a given workspace. -func (s *teamAccesses) List(ctx context.Context, options TeamAccessListOptions) (*TeamAccessList, error) { - if err := options.valid(); err != nil { - return nil, err - } - - req, err := s.client.newRequest("GET", "team-workspaces", &options) - if err != nil { - return nil, err - } - - tal := &TeamAccessList{} - err = s.client.do(ctx, req, tal) - if err != nil { - return nil, err - } - - return tal, nil -} - -// TeamAccessAddOptions represents the options for adding team access. -type TeamAccessAddOptions struct { - // For internal use only! - ID string `jsonapi:"primary,team-workspaces"` - - // The type of access to grant. - Access *AccessType `jsonapi:"attr,access"` - - // The team to add to the workspace - Team *Team `jsonapi:"relation,team"` - - // The workspace to which the team is to be added. - Workspace *Workspace `jsonapi:"relation,workspace"` -} - -func (o TeamAccessAddOptions) valid() error { - if o.Access == nil { - return errors.New("Access is required") - } - if o.Team == nil { - return errors.New("Team is required") - } - if o.Workspace == nil { - return errors.New("Workspace is required") - } - return nil -} - -// Add team access for a workspace. -func (s *teamAccesses) Add(ctx context.Context, options TeamAccessAddOptions) (*TeamAccess, error) { - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - req, err := s.client.newRequest("POST", "team-workspaces", &options) - if err != nil { - return nil, err - } - - ta := &TeamAccess{} - err = s.client.do(ctx, req, ta) - if err != nil { - return nil, err - } - - return ta, nil -} - -// Read a team access by its ID. -func (s *teamAccesses) Read(ctx context.Context, teamAccessID string) (*TeamAccess, error) { - if !validStringID(&teamAccessID) { - return nil, errors.New("Invalid value for team access ID") - } - - u := fmt.Sprintf("team-workspaces/%s", url.QueryEscape(teamAccessID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - ta := &TeamAccess{} - err = s.client.do(ctx, req, ta) - if err != nil { - return nil, err - } - - return ta, nil -} - -// Remove team access from a workspace. -func (s *teamAccesses) Remove(ctx context.Context, teamAccessID string) error { - if !validStringID(&teamAccessID) { - return errors.New("Invalid value for team access ID") - } - - u := fmt.Sprintf("team-workspaces/%s", url.QueryEscape(teamAccessID)) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/team_member.go b/vendor/github.com/hashicorp/go-tfe/team_member.go deleted file mode 100644 index 297d58a6b..000000000 --- a/vendor/github.com/hashicorp/go-tfe/team_member.go +++ /dev/null @@ -1,139 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" -) - -// Compile-time proof of interface implementation. -var _ TeamMembers = (*teamMembers)(nil) - -// TeamMembers describes all the team member related methods that the -// Terraform Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/team-members.html -type TeamMembers interface { - // List all members of a team. - List(ctx context.Context, teamID string) ([]*User, error) - - // Add multiple users to a team. - Add(ctx context.Context, teamID string, options TeamMemberAddOptions) error - - // Remove multiple users from a team. - Remove(ctx context.Context, teamID string, options TeamMemberRemoveOptions) error -} - -// teamMembers implements TeamMembers. -type teamMembers struct { - client *Client -} - -type teamMember struct { - Username string `jsonapi:"primary,users"` -} - -// List all members of a team. -func (s *teamMembers) List(ctx context.Context, teamID string) ([]*User, error) { - if !validStringID(&teamID) { - return nil, errors.New("Invalid value for team ID") - } - - options := struct { - Include string `url:"include"` - }{ - Include: "users", - } - - u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID)) - req, err := s.client.newRequest("GET", u, options) - if err != nil { - return nil, err - } - - t := &Team{} - err = s.client.do(ctx, req, t) - if err != nil { - return nil, err - } - - return t.Users, nil -} - -// TeamMemberAddOptions represents the options for adding team members. -type TeamMemberAddOptions struct { - Usernames []string -} - -func (o *TeamMemberAddOptions) valid() error { - if o.Usernames == nil { - return errors.New("Usernames is required") - } - if len(o.Usernames) == 0 { - return errors.New("Invalid value for usernames") - } - return nil -} - -// Add multiple users to a team. -func (s *teamMembers) Add(ctx context.Context, teamID string, options TeamMemberAddOptions) error { - if !validStringID(&teamID) { - return errors.New("Invalid value for team ID") - } - if err := options.valid(); err != nil { - return err - } - - var tms []*teamMember - for _, name := range options.Usernames { - tms = append(tms, &teamMember{Username: name}) - } - - u := fmt.Sprintf("teams/%s/relationships/users", url.QueryEscape(teamID)) - req, err := s.client.newRequest("POST", u, tms) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} - -// TeamMemberRemoveOptions represents the options for deleting team members. -type TeamMemberRemoveOptions struct { - Usernames []string -} - -func (o *TeamMemberRemoveOptions) valid() error { - if o.Usernames == nil { - return errors.New("Usernames is required") - } - if len(o.Usernames) == 0 { - return errors.New("Invalid value for usernames") - } - return nil -} - -// Remove multiple users from a team. -func (s *teamMembers) Remove(ctx context.Context, teamID string, options TeamMemberRemoveOptions) error { - if !validStringID(&teamID) { - return errors.New("Invalid value for team ID") - } - if err := options.valid(); err != nil { - return err - } - - var tms []*teamMember - for _, name := range options.Usernames { - tms = append(tms, &teamMember{Username: name}) - } - - u := fmt.Sprintf("teams/%s/relationships/users", url.QueryEscape(teamID)) - req, err := s.client.newRequest("DELETE", u, tms) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/team_token.go b/vendor/github.com/hashicorp/go-tfe/team_token.go deleted file mode 100644 index baaf75789..000000000 --- a/vendor/github.com/hashicorp/go-tfe/team_token.go +++ /dev/null @@ -1,99 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ TeamTokens = (*teamTokens)(nil) - -// TeamTokens describes all the team token related methods that the -// Terraform Enterprise API supports. -// -// TFE API docs: -// https://www.terraform.io/docs/enterprise/api/team-tokens.html -type TeamTokens interface { - // Generate a new team token, replacing any existing token. - Generate(ctx context.Context, teamID string) (*TeamToken, error) - - // Read a team token by its ID. - Read(ctx context.Context, teamID string) (*TeamToken, error) - - // Delete a team token by its ID. - Delete(ctx context.Context, teamID string) error -} - -// teamTokens implements TeamTokens. -type teamTokens struct { - client *Client -} - -// TeamToken represents a Terraform Enterprise team token. -type TeamToken struct { - ID string `jsonapi:"primary,authentication-tokens"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - Description string `jsonapi:"attr,description"` - LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"` - Token string `jsonapi:"attr,token"` -} - -// Generate a new team token, replacing any existing token. -func (s *teamTokens) Generate(ctx context.Context, teamID string) (*TeamToken, error) { - if !validStringID(&teamID) { - return nil, errors.New("Invalid value for team ID") - } - - u := fmt.Sprintf("teams/%s/authentication-token", url.QueryEscape(teamID)) - req, err := s.client.newRequest("POST", u, nil) - if err != nil { - return nil, err - } - - tt := &TeamToken{} - err = s.client.do(ctx, req, tt) - if err != nil { - return nil, err - } - - return tt, err -} - -// Read a team token by its ID. -func (s *teamTokens) Read(ctx context.Context, teamID string) (*TeamToken, error) { - if !validStringID(&teamID) { - return nil, errors.New("Invalid value for team ID") - } - - u := fmt.Sprintf("teams/%s/authentication-token", url.QueryEscape(teamID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - tt := &TeamToken{} - err = s.client.do(ctx, req, tt) - if err != nil { - return nil, err - } - - return tt, err -} - -// Delete a team token by its ID. -func (s *teamTokens) Delete(ctx context.Context, teamID string) error { - if !validStringID(&teamID) { - return errors.New("Invalid value for team ID") - } - - u := fmt.Sprintf("teams/%s/authentication-token", url.QueryEscape(teamID)) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/tfe.go b/vendor/github.com/hashicorp/go-tfe/tfe.go deleted file mode 100644 index 127d94a93..000000000 --- a/vendor/github.com/hashicorp/go-tfe/tfe.go +++ /dev/null @@ -1,420 +0,0 @@ -package tfe - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "reflect" - "strings" - - "github.com/google/go-querystring/query" - "github.com/hashicorp/go-cleanhttp" - "github.com/svanharmelen/jsonapi" -) - -const ( - // DefaultAddress of Terraform Enterprise. - DefaultAddress = "https://app.terraform.io" - // DefaultBasePath on which the API is served. - DefaultBasePath = "/api/v2/" -) - -const ( - userAgent = "go-tfe" -) - -var ( - // ErrUnauthorized is returned when a receiving a 401. - ErrUnauthorized = errors.New("unauthorized") - // ErrResourceNotFound is returned when a receiving a 404. - ErrResourceNotFound = errors.New("resource not found") -) - -// Config provides configuration details to the API client. -type Config struct { - // The address of the Terraform Enterprise API. - Address string - - // The base path on which the API is served. - BasePath string - - // API token used to access the Terraform Enterprise API. - Token string - - // Headers that will be added to every request. - Headers http.Header - - // A custom HTTP client to use. - HTTPClient *http.Client -} - -// DefaultConfig returns a default config structure. -func DefaultConfig() *Config { - config := &Config{ - Address: os.Getenv("TFE_ADDRESS"), - BasePath: DefaultBasePath, - Token: os.Getenv("TFE_TOKEN"), - Headers: make(http.Header), - HTTPClient: cleanhttp.DefaultPooledClient(), - } - - // Set the default address if none is given. - if config.Address == "" { - config.Address = DefaultAddress - } - - // Set the default user agent. - config.Headers.Set("User-Agent", userAgent) - - return config -} - -// Client is the Terraform Enterprise API client. It provides the basic -// connectivity and configuration for accessing the TFE API. -type Client struct { - baseURL *url.URL - token string - headers http.Header - http *http.Client - - Applies Applies - ConfigurationVersions ConfigurationVersions - OAuthClients OAuthClients - OAuthTokens OAuthTokens - Organizations Organizations - OrganizationTokens OrganizationTokens - Plans Plans - Policies Policies - PolicyChecks PolicyChecks - Runs Runs - SSHKeys SSHKeys - StateVersions StateVersions - Teams Teams - TeamAccess TeamAccesses - TeamMembers TeamMembers - TeamTokens TeamTokens - Users Users - Variables Variables - Workspaces Workspaces -} - -// NewClient creates a new Terraform Enterprise API client. -func NewClient(cfg *Config) (*Client, error) { - config := DefaultConfig() - - // Layer in the provided config for any non-blank values. - if cfg != nil { - if cfg.Address != "" { - config.Address = cfg.Address - } - if cfg.BasePath != "" { - config.BasePath = cfg.BasePath - } - if cfg.Token != "" { - config.Token = cfg.Token - } - for k, v := range cfg.Headers { - config.Headers[k] = v - } - if cfg.HTTPClient != nil { - config.HTTPClient = cfg.HTTPClient - } - } - - // Parse the address to make sure its a valid URL. - baseURL, err := url.Parse(config.Address) - if err != nil { - return nil, fmt.Errorf("Invalid address: %v", err) - } - - baseURL.Path = config.BasePath - if !strings.HasSuffix(baseURL.Path, "/") { - baseURL.Path += "/" - } - - // This value must be provided by the user. - if config.Token == "" { - return nil, fmt.Errorf("Missing API token") - } - - // Create the client. - client := &Client{ - baseURL: baseURL, - token: config.Token, - headers: config.Headers, - http: config.HTTPClient, - } - - // Create the services. - client.Applies = &applies{client: client} - client.ConfigurationVersions = &configurationVersions{client: client} - client.OAuthClients = &oAuthClients{client: client} - client.OAuthTokens = &oAuthTokens{client: client} - client.Organizations = &organizations{client: client} - client.OrganizationTokens = &organizationTokens{client: client} - client.Plans = &plans{client: client} - client.Policies = &policies{client: client} - client.PolicyChecks = &policyChecks{client: client} - client.Runs = &runs{client: client} - client.SSHKeys = &sshKeys{client: client} - client.StateVersions = &stateVersions{client: client} - client.Teams = &teams{client: client} - client.TeamAccess = &teamAccesses{client: client} - client.TeamMembers = &teamMembers{client: client} - client.TeamTokens = &teamTokens{client: client} - client.Users = &users{client: client} - client.Variables = &variables{client: client} - client.Workspaces = &workspaces{client: client} - - return client, nil -} - -// ListOptions is used to specify pagination options when making API requests. -// Pagination allows breaking up large result sets into chunks, or "pages". -type ListOptions struct { - // The page number to request. The results vary based on the PageSize. - PageNumber int `url:"page[number],omitempty"` - - // The number of elements returned in a single page. - PageSize int `url:"page[size],omitempty"` -} - -// Pagination is used to return the pagination details of an API request. -type Pagination struct { - CurrentPage int `json:"current-page"` - PreviousPage int `json:"prev-page"` - NextPage int `json:"next-page"` - TotalPages int `json:"total-pages"` - TotalCount int `json:"total-count"` -} - -// newRequest creates an API request. A relative URL path can be provided in -// path, in which case it is resolved relative to the apiVersionPath of the -// Client. Relative URL paths should always be specified without a preceding -// slash. -// If v is supplied, the value will be JSONAPI encoded and included as the -// request body. If the method is GET, the value will be parsed and added as -// query parameters. -func (c *Client) newRequest(method, path string, v interface{}) (*http.Request, error) { - u, err := c.baseURL.Parse(path) - if err != nil { - return nil, err - } - - req := &http.Request{ - Method: method, - URL: u, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Header: make(http.Header), - Host: u.Host, - } - - // Set default headers. - for k, v := range c.headers { - req.Header[k] = v - } - - switch method { - case "GET": - req.Header.Set("Accept", "application/vnd.api+json") - - if v != nil { - q, err := query.Values(v) - if err != nil { - return nil, err - } - u.RawQuery = q.Encode() - } - case "DELETE", "PATCH", "POST": - req.Header.Set("Accept", "application/vnd.api+json") - req.Header.Set("Content-Type", "application/vnd.api+json") - - if v != nil { - var body bytes.Buffer - if err := jsonapi.MarshalPayloadWithoutIncluded(&body, v); err != nil { - return nil, err - } - req.Body = ioutil.NopCloser(&body) - req.ContentLength = int64(body.Len()) - } - case "PUT": - req.Header.Set("Accept", "application/json") - req.Header.Set("Content-Type", "application/octet-stream") - - if v != nil { - switch v := v.(type) { - case *bytes.Buffer: - req.Body = ioutil.NopCloser(v) - req.ContentLength = int64(v.Len()) - case []byte: - req.Body = ioutil.NopCloser(bytes.NewReader(v)) - req.ContentLength = int64(len(v)) - default: - return nil, fmt.Errorf("Unexpected type: %T", v) - } - } - } - - // Set the authorization header. - req.Header.Set("Authorization", "Bearer "+c.token) - - return req, nil -} - -// do sends an API request and returns the API response. The API response -// is JSONAPI decoded and the document's primary data is stored in the value -// pointed to by v, or returned as an error if an API error has occurred. - -// If v implements the io.Writer interface, the raw response body will be -// written to v, without attempting to first decode it. -// -// The provided ctx must be non-nil. If it is canceled or times out, ctx.Err() -// will be returned. -func (c *Client) do(ctx context.Context, req *http.Request, v interface{}) error { - // Add the context to the request. - req = req.WithContext(ctx) - - // Execute the request and check the response. - resp, err := c.http.Do(req) - if err != nil { - // If we got an error, and the context has been canceled, - // the context's error is probably more useful. - select { - case <-ctx.Done(): - return ctx.Err() - default: - return err - } - } - defer resp.Body.Close() - - // Basic response checking. - if err := checkResponseCode(resp); err != nil { - return err - } - - // Return here if decoding the response isn't needed. - if v == nil { - return nil - } - - // If v implements io.Writer, write the raw response body. - if w, ok := v.(io.Writer); ok { - _, err = io.Copy(w, resp.Body) - return err - } - - // Get the value of v so we can test if it's a struct. - dst := reflect.Indirect(reflect.ValueOf(v)) - - // Return an error if v is not a struct or an io.Writer. - if dst.Kind() != reflect.Struct { - return fmt.Errorf("v must be a struct or an io.Writer") - } - - // Try to get the Items and Pagination struct fields. - items := dst.FieldByName("Items") - pagination := dst.FieldByName("Pagination") - - // Unmarshal a single value if v does not contain the - // Items and Pagination struct fields. - if !items.IsValid() || !pagination.IsValid() { - return jsonapi.UnmarshalPayload(resp.Body, v) - } - - // Return an error if v.Items is not a slice. - if items.Type().Kind() != reflect.Slice { - return fmt.Errorf("v.Items must be a slice") - } - - // Create a temporary buffer and copy all the read data into it. - body := bytes.NewBuffer(nil) - reader := io.TeeReader(resp.Body, body) - - // Unmarshal as a list of values as v.Items is a slice. - raw, err := jsonapi.UnmarshalManyPayload(reader, items.Type().Elem()) - if err != nil { - return err - } - - // Make a new slice to hold the results. - sliceType := reflect.SliceOf(items.Type().Elem()) - result := reflect.MakeSlice(sliceType, 0, len(raw)) - - // Add all of the results to the new slice. - for _, v := range raw { - result = reflect.Append(result, reflect.ValueOf(v)) - } - - // Pointer-swap the result. - items.Set(result) - - // As we are getting a list of values, we need to decode - // the pagination details out of the response body. - p, err := parsePagination(body) - if err != nil { - return err - } - - // Pointer-swap the decoded pagination details. - pagination.Set(reflect.ValueOf(p)) - - return nil -} - -func parsePagination(body io.Reader) (*Pagination, error) { - var raw struct { - Meta struct { - Pagination Pagination `json:"pagination"` - } `json:"meta"` - } - - // JSON decode the raw response. - if err := json.NewDecoder(body).Decode(&raw); err != nil { - return &Pagination{}, err - } - - return &raw.Meta.Pagination, nil -} - -// checkResponseCode can be used to check the status code of an HTTP request. -func checkResponseCode(r *http.Response) error { - if r.StatusCode >= 200 && r.StatusCode <= 299 { - return nil - } - - switch r.StatusCode { - case 401: - return ErrUnauthorized - case 404: - return ErrResourceNotFound - } - - // Decode the error payload. - errPayload := &jsonapi.ErrorsPayload{} - err := json.NewDecoder(r.Body).Decode(errPayload) - if err != nil || len(errPayload.Errors) == 0 { - return fmt.Errorf(r.Status) - } - - // Parse and format the errors. - var errs []string - for _, e := range errPayload.Errors { - if e.Detail == "" { - errs = append(errs, e.Title) - } else { - errs = append(errs, fmt.Sprintf("%s %s", e.Title, e.Detail)) - } - } - - return fmt.Errorf(strings.Join(errs, "\n")) -} diff --git a/vendor/github.com/hashicorp/go-tfe/type_helpers.go b/vendor/github.com/hashicorp/go-tfe/type_helpers.go deleted file mode 100644 index 30df01e49..000000000 --- a/vendor/github.com/hashicorp/go-tfe/type_helpers.go +++ /dev/null @@ -1,46 +0,0 @@ -package tfe - -// Access returns a pointer to the given team access type. -func Access(v AccessType) *AccessType { - return &v -} - -// AuthPolicy returns a pointer to the given authentication poliy. -func AuthPolicy(v AuthPolicyType) *AuthPolicyType { - return &v -} - -// Bool returns a pointer to the given bool -func Bool(v bool) *bool { - return &v -} - -// Category returns a pointer to the given category type. -func Category(v CategoryType) *CategoryType { - return &v -} - -// EnforcementMode returns a pointer to the given enforcement level. -func EnforcementMode(v EnforcementLevel) *EnforcementLevel { - return &v -} - -// Int returns a pointer to the given int. -func Int(v int) *int { - return &v -} - -// Int64 returns a pointer to the given int64. -func Int64(v int64) *int64 { - return &v -} - -// ServiceProvider returns a pointer to the given service provider type. -func ServiceProvider(v ServiceProviderType) *ServiceProviderType { - return &v -} - -// String returns a pointer to the given string. -func String(v string) *string { - return &v -} diff --git a/vendor/github.com/hashicorp/go-tfe/user.go b/vendor/github.com/hashicorp/go-tfe/user.go deleted file mode 100644 index f0ca28ee3..000000000 --- a/vendor/github.com/hashicorp/go-tfe/user.go +++ /dev/null @@ -1,93 +0,0 @@ -package tfe - -import ( - "context" -) - -// Compile-time proof of interface implementation. -var _ Users = (*users)(nil) - -// Users describes all the user related methods that the Terraform -// Enterprise API supports. -// -// TFE API docs: https://www.terraform.io/docs/enterprise/api/user.html -type Users interface { - // ReadCurrent reads the details of the currently authenticated user. - ReadCurrent(ctx context.Context) (*User, error) - - // Update attributes of the currently authenticated user. - Update(ctx context.Context, options UserUpdateOptions) (*User, error) -} - -// users implements Users. -type users struct { - client *Client -} - -// User represents a Terraform Enterprise user. -type User struct { - ID string `jsonapi:"primary,users"` - AvatarURL string `jsonapi:"attr,avatar-url"` - Email string `jsonapi:"attr,email"` - IsServiceAccount bool `jsonapi:"attr,is-service-account"` - TwoFactor *TwoFactor `jsonapi:"attr,two-factor"` - UnconfirmedEmail string `jsonapi:"attr,unconfirmed-email"` - Username string `jsonapi:"attr,username"` - V2Only bool `jsonapi:"attr,v2-only"` - - // Relations - // AuthenticationTokens *AuthenticationTokens `jsonapi:"relation,authentication-tokens"` -} - -// TwoFactor represents the organization permissions. -type TwoFactor struct { - Enabled bool `json:"enabled"` - Verified bool `json:"verified"` -} - -// ReadCurrent reads the details of the currently authenticated user. -func (s *users) ReadCurrent(ctx context.Context) (*User, error) { - req, err := s.client.newRequest("GET", "account/details", nil) - if err != nil { - return nil, err - } - - u := &User{} - err = s.client.do(ctx, req, u) - if err != nil { - return nil, err - } - - return u, nil -} - -// UserUpdateOptions represents the options for updating a user. -type UserUpdateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,users"` - - // New username. - Username *string `jsonapi:"attr,username,omitempty"` - - // New email address (must be consumed afterwards to take effect). - Email *string `jsonapi:"attr,email,omitempty"` -} - -// Update attributes of the currently authenticated user. -func (s *users) Update(ctx context.Context, options UserUpdateOptions) (*User, error) { - // Make sure we don't send a user provided ID. - options.ID = "" - - req, err := s.client.newRequest("PATCH", "account/update", &options) - if err != nil { - return nil, err - } - - u := &User{} - err = s.client.do(ctx, req, u) - if err != nil { - return nil, err - } - - return u, nil -} diff --git a/vendor/github.com/hashicorp/go-tfe/validations.go b/vendor/github.com/hashicorp/go-tfe/validations.go deleted file mode 100644 index 38d95a681..000000000 --- a/vendor/github.com/hashicorp/go-tfe/validations.go +++ /dev/null @@ -1,19 +0,0 @@ -package tfe - -import ( - "regexp" -) - -// A regular expression used to validate common string ID patterns. -var reStringID = regexp.MustCompile(`^[a-zA-Z0-9\-\._]+$`) - -// validString checks if the given input is present and non-empty. -func validString(v *string) bool { - return v != nil && *v != "" -} - -// validStringID checks if the given string pointer is non-nil and -// contains a typical string identifier. -func validStringID(v *string) bool { - return v != nil && reStringID.MatchString(*v) -} diff --git a/vendor/github.com/hashicorp/go-tfe/variable.go b/vendor/github.com/hashicorp/go-tfe/variable.go deleted file mode 100644 index ba28404c9..000000000 --- a/vendor/github.com/hashicorp/go-tfe/variable.go +++ /dev/null @@ -1,243 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" -) - -// Compile-time proof of interface implementation. -var _ Variables = (*variables)(nil) - -// Variables describes all the variable related methods that the Terraform -// Enterprise API supports. -// -// TFE API docs: https://www.terraform.io/docs/enterprise/api/variables.html -type Variables interface { - // List all the variables associated with the given workspace. - List(ctx context.Context, options VariableListOptions) (*VariableList, error) - - // Create is used to create a new variable. - Create(ctx context.Context, options VariableCreateOptions) (*Variable, error) - - // Read a variable by its ID. - Read(ctx context.Context, variableID string) (*Variable, error) - - // Update values of an existing variable. - Update(ctx context.Context, variableID string, options VariableUpdateOptions) (*Variable, error) - - // Delete a variable by its ID. - Delete(ctx context.Context, variableID string) error -} - -// variables implements Variables. -type variables struct { - client *Client -} - -// CategoryType represents a category type. -type CategoryType string - -//List all available categories. -const ( - CategoryEnv CategoryType = "env" - CategoryTerraform CategoryType = "terraform" -) - -// VariableList represents a list of variables. -type VariableList struct { - *Pagination - Items []*Variable -} - -// Variable represents a Terraform Enterprise variable. -type Variable struct { - ID string `jsonapi:"primary,vars"` - Key string `jsonapi:"attr,key"` - Value string `jsonapi:"attr,value"` - Category CategoryType `jsonapi:"attr,category"` - HCL bool `jsonapi:"attr,hcl"` - Sensitive bool `jsonapi:"attr,sensitive"` - - // Relations - Workspace *Workspace `jsonapi:"relation,workspace"` -} - -// VariableListOptions represents the options for listing variables. -type VariableListOptions struct { - ListOptions - Organization *string `url:"filter[organization][name]"` - Workspace *string `url:"filter[workspace][name]"` -} - -func (o VariableListOptions) valid() error { - if !validString(o.Organization) { - return errors.New("Organization is required") - } - if !validString(o.Workspace) { - return errors.New("Workspace is required") - } - return nil -} - -// List all the variables associated with the given workspace. -func (s *variables) List(ctx context.Context, options VariableListOptions) (*VariableList, error) { - if err := options.valid(); err != nil { - return nil, err - } - - req, err := s.client.newRequest("GET", "vars", &options) - if err != nil { - return nil, err - } - - vl := &VariableList{} - err = s.client.do(ctx, req, vl) - if err != nil { - return nil, err - } - - return vl, nil -} - -// VariableCreateOptions represents the options for creating a new variable. -type VariableCreateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,vars"` - - // The name of the variable. - Key *string `jsonapi:"attr,key"` - - // The value of the variable. - Value *string `jsonapi:"attr,value"` - - // Whether this is a Terraform or environment variable. - Category *CategoryType `jsonapi:"attr,category"` - - // Whether to evaluate the value of the variable as a string of HCL code. - HCL *bool `jsonapi:"attr,hcl,omitempty"` - - // Whether the value is sensitive. - Sensitive *bool `jsonapi:"attr,sensitive,omitempty"` - - // The workspace that owns the variable. - Workspace *Workspace `jsonapi:"relation,workspace"` -} - -func (o VariableCreateOptions) valid() error { - if !validString(o.Key) { - return errors.New("Key is required") - } - if !validString(o.Value) { - return errors.New("Value is required") - } - if o.Category == nil { - return errors.New("Category is required") - } - if o.Workspace == nil { - return errors.New("Workspace is required") - } - return nil -} - -// Create is used to create a new variable. -func (s *variables) Create(ctx context.Context, options VariableCreateOptions) (*Variable, error) { - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - req, err := s.client.newRequest("POST", "vars", &options) - if err != nil { - return nil, err - } - - v := &Variable{} - err = s.client.do(ctx, req, v) - if err != nil { - return nil, err - } - - return v, nil -} - -// Read a variable by its ID. -func (s *variables) Read(ctx context.Context, variableID string) (*Variable, error) { - if !validStringID(&variableID) { - return nil, errors.New("Invalid value for variable ID") - } - - u := fmt.Sprintf("vars/%s", url.QueryEscape(variableID)) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - v := &Variable{} - err = s.client.do(ctx, req, v) - if err != nil { - return nil, err - } - - return v, err -} - -// VariableUpdateOptions represents the options for updating a variable. -type VariableUpdateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,vars"` - - // The name of the variable. - Key *string `jsonapi:"attr,key,omitempty"` - - // The value of the variable. - Value *string `jsonapi:"attr,value,omitempty"` - - // Whether to evaluate the value of the variable as a string of HCL code. - HCL *bool `jsonapi:"attr,hcl,omitempty"` - - // Whether the value is sensitive. - Sensitive *bool `jsonapi:"attr,sensitive,omitempty"` -} - -// Update values of an existing variable. -func (s *variables) Update(ctx context.Context, variableID string, options VariableUpdateOptions) (*Variable, error) { - if !validStringID(&variableID) { - return nil, errors.New("Invalid value for variable ID") - } - - // Make sure we don't send a user provided ID. - options.ID = variableID - - u := fmt.Sprintf("vars/%s", url.QueryEscape(variableID)) - req, err := s.client.newRequest("PATCH", u, &options) - if err != nil { - return nil, err - } - - v := &Variable{} - err = s.client.do(ctx, req, v) - if err != nil { - return nil, err - } - - return v, nil -} - -// Delete a variable by its ID. -func (s *variables) Delete(ctx context.Context, variableID string) error { - if !validStringID(&variableID) { - return errors.New("Invalid value for variable ID") - } - - u := fmt.Sprintf("vars/%s", url.QueryEscape(variableID)) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} diff --git a/vendor/github.com/hashicorp/go-tfe/workspace.go b/vendor/github.com/hashicorp/go-tfe/workspace.go deleted file mode 100644 index 4d78a75d8..000000000 --- a/vendor/github.com/hashicorp/go-tfe/workspace.go +++ /dev/null @@ -1,447 +0,0 @@ -package tfe - -import ( - "context" - "errors" - "fmt" - "net/url" - "time" -) - -// Compile-time proof of interface implementation. -var _ Workspaces = (*workspaces)(nil) - -// Workspaces describes all the workspace related methods that the Terraform -// Enterprise API supports. -// -// TFE API docs: https://www.terraform.io/docs/enterprise/api/workspaces.html -type Workspaces interface { - // List all the workspaces within an organization. - List(ctx context.Context, organization string, options WorkspaceListOptions) (*WorkspaceList, error) - - // Create is used to create a new workspace. - Create(ctx context.Context, organization string, options WorkspaceCreateOptions) (*Workspace, error) - - // Read a workspace by its name. - Read(ctx context.Context, organization string, workspace string) (*Workspace, error) - - // Update settings of an existing workspace. - Update(ctx context.Context, organization string, workspace string, options WorkspaceUpdateOptions) (*Workspace, error) - - // Delete a workspace by its name. - Delete(ctx context.Context, organization string, workspace string) error - - // Lock a workspace by its ID. - Lock(ctx context.Context, workspaceID string, options WorkspaceLockOptions) (*Workspace, error) - - // Unlock a workspace by its ID. - Unlock(ctx context.Context, workspaceID string) (*Workspace, error) - - // AssignSSHKey to a workspace. - AssignSSHKey(ctx context.Context, workspaceID string, options WorkspaceAssignSSHKeyOptions) (*Workspace, error) - - // UnassignSSHKey from a workspace. - UnassignSSHKey(ctx context.Context, workspaceID string) (*Workspace, error) -} - -// workspaces implements Workspaces. -type workspaces struct { - client *Client -} - -// WorkspaceList represents a list of workspaces. -type WorkspaceList struct { - *Pagination - Items []*Workspace -} - -// Workspace represents a Terraform Enterprise workspace. -type Workspace struct { - ID string `jsonapi:"primary,workspaces"` - Actions *WorkspaceActions `jsonapi:"attr,actions"` - AutoApply bool `jsonapi:"attr,auto-apply"` - CanQueueDestroyPlan bool `jsonapi:"attr,can-queue-destroy-plan"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - Environment string `jsonapi:"attr,environment"` - Locked bool `jsonapi:"attr,locked"` - MigrationEnvironment string `jsonapi:"attr,migration-environment"` - Name string `jsonapi:"attr,name"` - Permissions *WorkspacePermissions `jsonapi:"attr,permissions"` - TerraformVersion string `jsonapi:"attr,terraform-version"` - VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"` - WorkingDirectory string `jsonapi:"attr,working-directory"` - - // Relations - CurrentRun *Run `jsonapi:"relation,current-run"` - Organization *Organization `jsonapi:"relation,organization"` - SSHKey *SSHKey `jsonapi:"relation,ssh-key"` -} - -// VCSRepo contains the configuration of a VCS integration. -type VCSRepo struct { - Branch string `json:"branch"` - Identifier string `json:"identifier"` - IngressSubmodules bool `json:"ingress-submodules"` - OAuthTokenID string `json:"oauth-token-id"` -} - -// WorkspaceActions represents the workspace actions. -type WorkspaceActions struct { - IsDestroyable bool `json:"is-destroyable"` -} - -// WorkspacePermissions represents the workspace permissions. -type WorkspacePermissions struct { - CanDestroy bool `json:"can-destroy"` - CanLock bool `json:"can-lock"` - CanQueueDestroy bool `json:"can-queue-destroy"` - CanQueueRun bool `json:"can-queue-run"` - CanReadSettings bool `json:"can-read-settings"` - CanUpdate bool `json:"can-update"` - CanUpdateVariable bool `json:"can-update-variable"` -} - -// WorkspaceListOptions represents the options for listing workspaces. -type WorkspaceListOptions struct { - ListOptions - - // A search string (partial workspace name) used to filter the results. - Search *string `url:"search[name],omitempty"` -} - -// List all the workspaces within an organization. -func (s *workspaces) List(ctx context.Context, organization string, options WorkspaceListOptions) (*WorkspaceList, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - - u := fmt.Sprintf("organizations/%s/workspaces", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - wl := &WorkspaceList{} - err = s.client.do(ctx, req, wl) - if err != nil { - return nil, err - } - - return wl, nil -} - -// WorkspaceCreateOptions represents the options for creating a new workspace. -type WorkspaceCreateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,workspaces"` - - // Whether to automatically apply changes when a Terraform plan is successful. - AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"` - - // The legacy TFE environment to use as the source of the migration, in the - // form organization/environment. Omit this unless you are migrating a legacy - // environment. - MigrationEnvironment *string `jsonapi:"attr,migration-environment,omitempty"` - - // The name of the workspace, which can only include letters, numbers, -, - // and _. This will be used as an identifier and must be unique in the - // organization. - Name *string `jsonapi:"attr,name"` - - // The version of Terraform to use for this workspace. Upon creating a - // workspace, the latest version is selected unless otherwise specified. - TerraformVersion *string `jsonapi:"attr,terraform-version,omitempty"` - - // Settings for the workspace's VCS repository. If omitted, the workspace is - // created without a VCS repo. If included, you must specify at least the - // oauth-token-id and identifier keys below. - VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"` - - // A relative path that Terraform will execute within. This defaults to the - // root of your repository and is typically set to a subdirectory matching the - // environment when multiple environments exist within the same repository. - WorkingDirectory *string `jsonapi:"attr,working-directory,omitempty"` -} - -// VCSRepoOptions represents the configuration options of a VCS integration. -type VCSRepoOptions struct { - Branch *string `json:"branch,omitempty"` - Identifier *string `json:"identifier,omitempty"` - IngressSubmodules *bool `json:"ingress-submodules,omitempty"` - OAuthTokenID *string `json:"oauth-token-id,omitempty"` -} - -func (o WorkspaceCreateOptions) valid() error { - if !validString(o.Name) { - return errors.New("Name is required") - } - if !validStringID(o.Name) { - return errors.New("Invalid value for name") - } - return nil -} - -// Create is used to create a new workspace. -func (s *workspaces) Create(ctx context.Context, organization string, options WorkspaceCreateOptions) (*Workspace, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("organizations/%s/workspaces", url.QueryEscape(organization)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return nil, err - } - - w := &Workspace{} - err = s.client.do(ctx, req, w) - if err != nil { - return nil, err - } - - return w, nil -} - -// Read a workspace by its name. -func (s *workspaces) Read(ctx context.Context, organization, workspace string) (*Workspace, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - if !validStringID(&workspace) { - return nil, errors.New("Invalid value for workspace") - } - - u := fmt.Sprintf( - "organizations/%s/workspaces/%s", - url.QueryEscape(organization), - url.QueryEscape(workspace), - ) - req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - w := &Workspace{} - err = s.client.do(ctx, req, w) - if err != nil { - return nil, err - } - - return w, nil -} - -// WorkspaceUpdateOptions represents the options for updating a workspace. -type WorkspaceUpdateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,workspaces"` - - // Whether to automatically apply changes when a Terraform plan is successful. - AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"` - - // A new name for the workspace, which can only include letters, numbers, -, - // and _. This will be used as an identifier and must be unique in the - // organization. Warning: Changing a workspace's name changes its URL in the - // API and UI. - Name *string `jsonapi:"attr,name,omitempty"` - - // The version of Terraform to use for this workspace. - TerraformVersion *string `jsonapi:"attr,terraform-version,omitempty"` - - // To delete a workspace's existing VCS repo, specify null instead of an - // object. To modify a workspace's existing VCS repo, include whichever of - // the keys below you wish to modify. To add a new VCS repo to a workspace - // that didn't previously have one, include at least the oauth-token-id and - // identifier keys. VCSRepo *VCSRepo `jsonapi:"relation,vcs-repo,om-tempty"` - VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"` - - // A relative path that Terraform will execute within. This defaults to the - // root of your repository and is typically set to a subdirectory matching - // the environment when multiple environments exist within the same - // repository. - WorkingDirectory *string `jsonapi:"attr,working-directory,omitempty"` -} - -// Update settings of an existing workspace. -func (s *workspaces) Update(ctx context.Context, organization, workspace string, options WorkspaceUpdateOptions) (*Workspace, error) { - if !validStringID(&organization) { - return nil, errors.New("Invalid value for organization") - } - if !validStringID(&workspace) { - return nil, errors.New("Invalid value for workspace") - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf( - "organizations/%s/workspaces/%s", - url.QueryEscape(organization), - url.QueryEscape(workspace), - ) - req, err := s.client.newRequest("PATCH", u, &options) - if err != nil { - return nil, err - } - - w := &Workspace{} - err = s.client.do(ctx, req, w) - if err != nil { - return nil, err - } - - return w, nil -} - -// Delete a workspace by its name. -func (s *workspaces) Delete(ctx context.Context, organization, workspace string) error { - if !validStringID(&organization) { - return errors.New("Invalid value for organization") - } - if !validStringID(&workspace) { - return errors.New("Invalid value for workspace") - } - - u := fmt.Sprintf( - "organizations/%s/workspaces/%s", - url.QueryEscape(organization), - url.QueryEscape(workspace), - ) - req, err := s.client.newRequest("DELETE", u, nil) - if err != nil { - return err - } - - return s.client.do(ctx, req, nil) -} - -// WorkspaceLockOptions represents the options for locking a workspace. -type WorkspaceLockOptions struct { - // Specifies the reason for locking the workspace. - Reason *string `json:"reason,omitempty"` -} - -// Lock a workspace by its ID. -func (s *workspaces) Lock(ctx context.Context, workspaceID string, options WorkspaceLockOptions) (*Workspace, error) { - if !validStringID(&workspaceID) { - return nil, errors.New("Invalid value for workspace ID") - } - - u := fmt.Sprintf("workspaces/%s/actions/lock", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("POST", u, &options) - if err != nil { - return nil, err - } - - w := &Workspace{} - err = s.client.do(ctx, req, w) - if err != nil { - return nil, err - } - - return w, nil -} - -// Unlock a workspace by its ID. -func (s *workspaces) Unlock(ctx context.Context, workspaceID string) (*Workspace, error) { - if !validStringID(&workspaceID) { - return nil, errors.New("Invalid value for workspace ID") - } - - u := fmt.Sprintf("workspaces/%s/actions/unlock", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("POST", u, nil) - if err != nil { - return nil, err - } - - w := &Workspace{} - err = s.client.do(ctx, req, w) - if err != nil { - return nil, err - } - - return w, nil -} - -// WorkspaceAssignSSHKeyOptions represents the options to assign an SSH key to -// a workspace. -type WorkspaceAssignSSHKeyOptions struct { - // For internal use only! - ID string `jsonapi:"primary,workspaces"` - - // The SSH key ID to assign. - SSHKeyID *string `jsonapi:"attr,id"` -} - -func (o WorkspaceAssignSSHKeyOptions) valid() error { - if !validString(o.SSHKeyID) { - return errors.New("SSH key ID is required") - } - if !validStringID(o.SSHKeyID) { - return errors.New("Invalid value for SSH key ID") - } - return nil -} - -// AssignSSHKey to a workspace. -func (s *workspaces) AssignSSHKey(ctx context.Context, workspaceID string, options WorkspaceAssignSSHKeyOptions) (*Workspace, error) { - if !validStringID(&workspaceID) { - return nil, errors.New("Invalid value for workspace ID") - } - if err := options.valid(); err != nil { - return nil, err - } - - // Make sure we don't send a user provided ID. - options.ID = "" - - u := fmt.Sprintf("workspaces/%s/relationships/ssh-key", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("PATCH", u, &options) - if err != nil { - return nil, err - } - - w := &Workspace{} - err = s.client.do(ctx, req, w) - if err != nil { - return nil, err - } - - return w, nil -} - -// workspaceUnassignSSHKeyOptions represents the options to unassign an SSH key -// to a workspace. -type workspaceUnassignSSHKeyOptions struct { - // For internal use only! - ID string `jsonapi:"primary,workspaces"` - - // Must be nil to unset the currently assigned SSH key. - SSHKeyID *string `jsonapi:"attr,id"` -} - -// UnassignSSHKey from a workspace. -func (s *workspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*Workspace, error) { - if !validStringID(&workspaceID) { - return nil, errors.New("Invalid value for workspace ID") - } - - u := fmt.Sprintf("workspaces/%s/relationships/ssh-key", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("PATCH", u, &workspaceUnassignSSHKeyOptions{}) - if err != nil { - return nil, err - } - - w := &Workspace{} - err = s.client.do(ctx, req, w) - if err != nil { - return nil, err - } - - return w, nil -} diff --git a/vendor/github.com/svanharmelen/jsonapi/LICENSE b/vendor/github.com/svanharmelen/jsonapi/LICENSE deleted file mode 100644 index c97912cef..000000000 --- a/vendor/github.com/svanharmelen/jsonapi/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 Google Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/svanharmelen/jsonapi/README.md b/vendor/github.com/svanharmelen/jsonapi/README.md deleted file mode 100644 index 44b054181..000000000 --- a/vendor/github.com/svanharmelen/jsonapi/README.md +++ /dev/null @@ -1,457 +0,0 @@ -# jsonapi - -[![Build Status](https://travis-ci.org/google/jsonapi.svg?branch=master)](https://travis-ci.org/google/jsonapi) -[![Go Report Card](https://goreportcard.com/badge/github.com/google/jsonapi)](https://goreportcard.com/report/github.com/google/jsonapi) -[![GoDoc](https://godoc.org/github.com/google/jsonapi?status.svg)](http://godoc.org/github.com/google/jsonapi) - -A serializer/deserializer for JSON payloads that comply to the -[JSON API - jsonapi.org](http://jsonapi.org) spec in go. - -## Installation - -``` -go get -u github.com/google/jsonapi -``` - -Or, see [Alternative Installation](#alternative-installation). - -## Background - -You are working in your Go web application and you have a struct that is -organized similarly to your database schema. You need to send and -receive json payloads that adhere to the JSON API spec. Once you realize that -your json needed to take on this special form, you go down the path of -creating more structs to be able to serialize and deserialize JSON API -payloads. Then there are more models required with this additional -structure. Ugh! With JSON API, you can keep your model structs as is and -use [StructTags](http://golang.org/pkg/reflect/#StructTag) to indicate -to JSON API how you want your response built or your request -deserialized. What about your relationships? JSON API supports -relationships out of the box and will even put them in your response -into an `included` side-loaded slice--that contains associated records. - -## Introduction - -JSON API uses [StructField](http://golang.org/pkg/reflect/#StructField) -tags to annotate the structs fields that you already have and use in -your app and then reads and writes [JSON API](http://jsonapi.org) -output based on the instructions you give the library in your JSON API -tags. Let's take an example. In your app, you most likely have structs -that look similar to these: - - -```go -type Blog struct { - ID int `json:"id"` - Title string `json:"title"` - Posts []*Post `json:"posts"` - CurrentPost *Post `json:"current_post"` - CurrentPostId int `json:"current_post_id"` - CreatedAt time.Time `json:"created_at"` - ViewCount int `json:"view_count"` -} - -type Post struct { - ID int `json:"id"` - BlogID int `json:"blog_id"` - Title string `json:"title"` - Body string `json:"body"` - Comments []*Comment `json:"comments"` -} - -type Comment struct { - Id int `json:"id"` - PostID int `json:"post_id"` - Body string `json:"body"` - Likes uint `json:"likes_count,omitempty"` -} -``` - -These structs may or may not resemble the layout of your database. But -these are the ones that you want to use right? You wouldn't want to use -structs like those that JSON API sends because it is difficult to get at -all of your data easily. - -## Example App - -[examples/app.go](https://github.com/google/jsonapi/blob/master/examples/app.go) - -This program demonstrates the implementation of a create, a show, -and a list [http.Handler](http://golang.org/pkg/net/http#Handler). It -outputs some example requests and responses as well as serialized -examples of the source/target structs to json. That is to say, I show -you that the library has successfully taken your JSON API request and -turned it into your struct types. - -To run, - -* Make sure you have [Go installed](https://golang.org/doc/install) -* Create the following directories or similar: `~/go` -* Set `GOPATH` to `PWD` in your shell session, `export GOPATH=$PWD` -* `go get github.com/google/jsonapi`. (Append `-u` after `get` if you - are updating.) -* `cd $GOPATH/src/github.com/google/jsonapi/examples` -* `go build && ./examples` - -## `jsonapi` Tag Reference - -### Example - -The `jsonapi` [StructTags](http://golang.org/pkg/reflect/#StructTag) -tells this library how to marshal and unmarshal your structs into -JSON API payloads and your JSON API payloads to structs, respectively. -Then Use JSON API's Marshal and Unmarshal methods to construct and read -your responses and replies. Here's an example of the structs above -using JSON API tags: - -```go -type Blog struct { - ID int `jsonapi:"primary,blogs"` - Title string `jsonapi:"attr,title"` - Posts []*Post `jsonapi:"relation,posts"` - CurrentPost *Post `jsonapi:"relation,current_post"` - CurrentPostID int `jsonapi:"attr,current_post_id"` - CreatedAt time.Time `jsonapi:"attr,created_at"` - ViewCount int `jsonapi:"attr,view_count"` -} - -type Post struct { - ID int `jsonapi:"primary,posts"` - BlogID int `jsonapi:"attr,blog_id"` - Title string `jsonapi:"attr,title"` - Body string `jsonapi:"attr,body"` - Comments []*Comment `jsonapi:"relation,comments"` -} - -type Comment struct { - ID int `jsonapi:"primary,comments"` - PostID int `jsonapi:"attr,post_id"` - Body string `jsonapi:"attr,body"` - Likes uint `jsonapi:"attr,likes-count,omitempty"` -} -``` - -### Permitted Tag Values - -#### `primary` - -``` -`jsonapi:"primary,"` -``` - -This indicates this is the primary key field for this struct type. -Tag value arguments are comma separated. The first argument must be, -`primary`, and the second must be the name that should appear in the -`type`\* field for all data objects that represent this type of model. - -\* According the [JSON API](http://jsonapi.org) spec, the plural record -types are shown in the examples, but not required. - -#### `attr` - -``` -`jsonapi:"attr,,"` -``` - -These fields' values will end up in the `attributes`hash for a record. -The first argument must be, `attr`, and the second should be the name -for the key to display in the `attributes` hash for that record. The optional -third argument is `omitempty` - if it is present the field will not be present -in the `"attributes"` if the field's value is equivalent to the field types -empty value (ie if the `count` field is of type `int`, `omitempty` will omit the -field when `count` has a value of `0`). Lastly, the spec indicates that -`attributes` key names should be dasherized for multiple word field names. - -#### `relation` - -``` -`jsonapi:"relation,,"` -``` - -Relations are struct fields that represent a one-to-one or one-to-many -relationship with other structs. JSON API will traverse the graph of -relationships and marshal or unmarshal records. The first argument must -be, `relation`, and the second should be the name of the relationship, -used as the key in the `relationships` hash for the record. The optional -third argument is `omitempty` - if present will prevent non existent to-one and -to-many from being serialized. - -## Methods Reference - -**All `Marshal` and `Unmarshal` methods expect pointers to struct -instance or slices of the same contained with the `interface{}`s** - -Now you have your structs prepared to be seralized or materialized, What -about the rest? - -### Create Record Example - -You can Unmarshal a JSON API payload using -[jsonapi.UnmarshalPayload](http://godoc.org/github.com/google/jsonapi#UnmarshalPayload). -It reads from an [io.Reader](https://golang.org/pkg/io/#Reader) -containing a JSON API payload for one record (but can have related -records). Then, it materializes a struct that you created and passed in -(using new or &). Again, the method supports single records only, at -the top level, in request payloads at the moment. Bulk creates and -updates are not supported yet. - -After saving your record, you can use, -[MarshalOnePayload](http://godoc.org/github.com/google/jsonapi#MarshalOnePayload), -to write the JSON API response to an -[io.Writer](https://golang.org/pkg/io/#Writer). - -#### `UnmarshalPayload` - -```go -UnmarshalPayload(in io.Reader, model interface{}) -``` - -Visit [godoc](http://godoc.org/github.com/google/jsonapi#UnmarshalPayload) - -#### `MarshalPayload` - -```go -MarshalPayload(w io.Writer, models interface{}) error -``` - -Visit [godoc](http://godoc.org/github.com/google/jsonapi#MarshalPayload) - -Writes a JSON API response, with related records sideloaded, into an -`included` array. This method encodes a response for either a single record or -many records. - -##### Handler Example Code - -```go -func CreateBlog(w http.ResponseWriter, r *http.Request) { - blog := new(Blog) - - if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // ...save your blog... - - w.Header().Set("Content-Type", jsonapi.MediaType) - w.WriteHeader(http.StatusCreated) - - if err := jsonapi.MarshalPayload(w, blog); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} -``` - -### Create Records Example - -#### `UnmarshalManyPayload` - -```go -UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error) -``` - -Visit [godoc](http://godoc.org/github.com/google/jsonapi#UnmarshalManyPayload) - -Takes an `io.Reader` and a `reflect.Type` representing the uniform type -contained within the `"data"` JSON API member. - -##### Handler Example Code - -```go -func CreateBlogs(w http.ResponseWriter, r *http.Request) { - // ...create many blogs at once - - blogs, err := UnmarshalManyPayload(r.Body, reflect.TypeOf(new(Blog))) - if err != nil { - t.Fatal(err) - } - - for _, blog := range blogs { - b, ok := blog.(*Blog) - // ...save each of your blogs - } - - w.Header().Set("Content-Type", jsonapi.MediaType) - w.WriteHeader(http.StatusCreated) - - if err := jsonapi.MarshalPayload(w, blogs); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} -``` - - -### Links - -If you need to include [link objects](http://jsonapi.org/format/#document-links) along with response data, implement the `Linkable` interface for document-links, and `RelationshipLinkable` for relationship links: - -```go -func (post Post) JSONAPILinks() *Links { - return &Links{ - "self": "href": fmt.Sprintf("https://example.com/posts/%d", post.ID), - "comments": Link{ - Href: fmt.Sprintf("https://example.com/api/blogs/%d/comments", post.ID), - Meta: map[string]interface{}{ - "counts": map[string]uint{ - "likes": 4, - }, - }, - }, - } -} - -// Invoked for each relationship defined on the Post struct when marshaled -func (post Post) JSONAPIRelationshipLinks(relation string) *Links { - if relation == "comments" { - return &Links{ - "related": fmt.Sprintf("https://example.com/posts/%d/comments", post.ID), - } - } - return nil -} -``` - -### Meta - - If you need to include [meta objects](http://jsonapi.org/format/#document-meta) along with response data, implement the `Metable` interface for document-meta, and `RelationshipMetable` for relationship meta: - - ```go -func (post Post) JSONAPIMeta() *Meta { - return &Meta{ - "details": "sample details here", - } -} - -// Invoked for each relationship defined on the Post struct when marshaled -func (post Post) JSONAPIRelationshipMeta(relation string) *Meta { - if relation == "comments" { - return &Meta{ - "this": map[string]interface{}{ - "can": map[string]interface{}{ - "go": []interface{}{ - "as", - "deep", - map[string]interface{}{ - "as": "required", - }, - }, - }, - }, - } - } - return nil -} -``` - -### Errors -This package also implements support for JSON API compatible `errors` payloads using the following types. - -#### `MarshalErrors` -```go -MarshalErrors(w io.Writer, errs []*ErrorObject) error -``` - -Writes a JSON API response using the given `[]error`. - -#### `ErrorsPayload` -```go -type ErrorsPayload struct { - Errors []*ErrorObject `json:"errors"` -} -``` - -ErrorsPayload is a serializer struct for representing a valid JSON API errors payload. - -#### `ErrorObject` -```go -type ErrorObject struct { ... } - -// Error implements the `Error` interface. -func (e *ErrorObject) Error() string { - return fmt.Sprintf("Error: %s %s\n", e.Title, e.Detail) -} -``` - -ErrorObject is an `Error` implementation as well as an implementation of the JSON API error object. - -The main idea behind this struct is that you can use it directly in your code as an error type and pass it directly to `MarshalErrors` to get a valid JSON API errors payload. - -##### Errors Example Code -```go -// An error has come up in your code, so set an appropriate status, and serialize the error. -if err := validate(&myStructToValidate); err != nil { - context.SetStatusCode(http.StatusBadRequest) // Or however you need to set a status. - jsonapi.MarshalErrors(w, []*ErrorObject{{ - Title: "Validation Error", - Detail: "Given request body was invalid.", - Status: "400", - Meta: map[string]interface{}{"field": "some_field", "error": "bad type", "expected": "string", "received": "float64"}, - }}) - return -} -``` - -## Testing - -### `MarshalOnePayloadEmbedded` - -```go -MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error -``` - -Visit [godoc](http://godoc.org/github.com/google/jsonapi#MarshalOnePayloadEmbedded) - -This method is not strictly meant to for use in implementation code, -although feel free. It was mainly created for use in tests; in most cases, -your request payloads for create will be embedded rather than sideloaded -for related records. This method will serialize a single struct pointer -into an embedded json response. In other words, there will be no, -`included`, array in the json; all relationships will be serialized -inline with the data. - -However, in tests, you may want to construct payloads to post to create -methods that are embedded to most closely model the payloads that will -be produced by the client. This method aims to enable that. - -### Example - -```go -out := bytes.NewBuffer(nil) - -// testModel returns a pointer to a Blog -jsonapi.MarshalOnePayloadEmbedded(out, testModel()) - -h := new(BlogsHandler) - -w := httptest.NewRecorder() -r, _ := http.NewRequest(http.MethodPost, "/blogs", out) - -h.CreateBlog(w, r) - -blog := new(Blog) -jsonapi.UnmarshalPayload(w.Body, blog) - -// ... assert stuff about blog here ... -``` - -## Alternative Installation -I use git subtrees to manage dependencies rather than `go get` so that -the src is committed to my repo. - -``` -git subtree add --squash --prefix=src/github.com/google/jsonapi https://github.com/google/jsonapi.git master -``` - -To update, - -``` -git subtree pull --squash --prefix=src/github.com/google/jsonapi https://github.com/google/jsonapi.git master -``` - -This assumes that I have my repo structured with a `src` dir containing -a collection of packages and `GOPATH` is set to the root -folder--containing `src`. - -## Contributing - -Fork, Change, Pull Request *with tests*. diff --git a/vendor/github.com/svanharmelen/jsonapi/constants.go b/vendor/github.com/svanharmelen/jsonapi/constants.go deleted file mode 100644 index 23288d311..000000000 --- a/vendor/github.com/svanharmelen/jsonapi/constants.go +++ /dev/null @@ -1,55 +0,0 @@ -package jsonapi - -const ( - // StructTag annotation strings - annotationJSONAPI = "jsonapi" - annotationPrimary = "primary" - annotationClientID = "client-id" - annotationAttribute = "attr" - annotationRelation = "relation" - annotationOmitEmpty = "omitempty" - annotationISO8601 = "iso8601" - annotationSeperator = "," - - iso8601TimeFormat = "2006-01-02T15:04:05Z" - - // MediaType is the identifier for the JSON API media type - // - // see http://jsonapi.org/format/#document-structure - MediaType = "application/vnd.api+json" - - // Pagination Constants - // - // http://jsonapi.org/format/#fetching-pagination - - // KeyFirstPage is the key to the links object whose value contains a link to - // the first page of data - KeyFirstPage = "first" - // KeyLastPage is the key to the links object whose value contains a link to - // the last page of data - KeyLastPage = "last" - // KeyPreviousPage is the key to the links object whose value contains a link - // to the previous page of data - KeyPreviousPage = "prev" - // KeyNextPage is the key to the links object whose value contains a link to - // the next page of data - KeyNextPage = "next" - - // QueryParamPageNumber is a JSON API query parameter used in a page based - // pagination strategy in conjunction with QueryParamPageSize - QueryParamPageNumber = "page[number]" - // QueryParamPageSize is a JSON API query parameter used in a page based - // pagination strategy in conjunction with QueryParamPageNumber - QueryParamPageSize = "page[size]" - - // QueryParamPageOffset is a JSON API query parameter used in an offset based - // pagination strategy in conjunction with QueryParamPageLimit - QueryParamPageOffset = "page[offset]" - // QueryParamPageLimit is a JSON API query parameter used in an offset based - // pagination strategy in conjunction with QueryParamPageOffset - QueryParamPageLimit = "page[limit]" - - // QueryParamPageCursor is a JSON API query parameter used with a cursor-based - // strategy - QueryParamPageCursor = "page[cursor]" -) diff --git a/vendor/github.com/svanharmelen/jsonapi/doc.go b/vendor/github.com/svanharmelen/jsonapi/doc.go deleted file mode 100644 index 29d7a14ba..000000000 --- a/vendor/github.com/svanharmelen/jsonapi/doc.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Package jsonapi provides a serializer and deserializer for jsonapi.org spec payloads. - -You can keep your model structs as is and use struct field tags to indicate to jsonapi -how you want your response built or your request deserialzied. What about my relationships? -jsonapi supports relationships out of the box and will even side load them in your response -into an "included" array--that contains associated objects. - -jsonapi uses StructField tags to annotate the structs fields that you already have and use -in your app and then reads and writes jsonapi.org output based on the instructions you give -the library in your jsonapi tags. - -Example structs using a Blog > Post > Comment structure, - - type Blog struct { - ID int `jsonapi:"primary,blogs"` - Title string `jsonapi:"attr,title"` - Posts []*Post `jsonapi:"relation,posts"` - CurrentPost *Post `jsonapi:"relation,current_post"` - CurrentPostID int `jsonapi:"attr,current_post_id"` - CreatedAt time.Time `jsonapi:"attr,created_at"` - ViewCount int `jsonapi:"attr,view_count"` - } - - type Post struct { - ID int `jsonapi:"primary,posts"` - BlogID int `jsonapi:"attr,blog_id"` - Title string `jsonapi:"attr,title"` - Body string `jsonapi:"attr,body"` - Comments []*Comment `jsonapi:"relation,comments"` - } - - type Comment struct { - ID int `jsonapi:"primary,comments"` - PostID int `jsonapi:"attr,post_id"` - Body string `jsonapi:"attr,body"` - } - -jsonapi Tag Reference - -Value, primary: "primary," - -This indicates that this is the primary key field for this struct type. Tag -value arguments are comma separated. The first argument must be, "primary", and -the second must be the name that should appear in the "type" field for all data -objects that represent this type of model. - -Value, attr: "attr,[,]" - -These fields' values should end up in the "attribute" hash for a record. The first -argument must be, "attr', and the second should be the name for the key to display in -the the "attributes" hash for that record. - -The following extra arguments are also supported: - -"omitempty": excludes the fields value from the "attribute" hash. -"iso8601": uses the ISO8601 timestamp format when serialising or deserialising the time.Time value. - -Value, relation: "relation," - -Relations are struct fields that represent a one-to-one or one-to-many to other structs. -jsonapi will traverse the graph of relationships and marshal or unmarshal records. The first -argument must be, "relation", and the second should be the name of the relationship, used as -the key in the "relationships" hash for the record. - -Use the methods below to Marshal and Unmarshal jsonapi.org json payloads. - -Visit the readme at https://github.com/google/jsonapi -*/ -package jsonapi diff --git a/vendor/github.com/svanharmelen/jsonapi/errors.go b/vendor/github.com/svanharmelen/jsonapi/errors.go deleted file mode 100644 index ed7fa9f75..000000000 --- a/vendor/github.com/svanharmelen/jsonapi/errors.go +++ /dev/null @@ -1,55 +0,0 @@ -package jsonapi - -import ( - "encoding/json" - "fmt" - "io" -) - -// MarshalErrors writes a JSON API response using the given `[]error`. -// -// For more information on JSON API error payloads, see the spec here: -// http://jsonapi.org/format/#document-top-level -// and here: http://jsonapi.org/format/#error-objects. -func MarshalErrors(w io.Writer, errorObjects []*ErrorObject) error { - if err := json.NewEncoder(w).Encode(&ErrorsPayload{Errors: errorObjects}); err != nil { - return err - } - return nil -} - -// ErrorsPayload is a serializer struct for representing a valid JSON API errors payload. -type ErrorsPayload struct { - Errors []*ErrorObject `json:"errors"` -} - -// ErrorObject is an `Error` implementation as well as an implementation of the JSON API error object. -// -// The main idea behind this struct is that you can use it directly in your code as an error type -// and pass it directly to `MarshalErrors` to get a valid JSON API errors payload. -// For more information on Golang errors, see: https://golang.org/pkg/errors/ -// For more information on the JSON API spec's error objects, see: http://jsonapi.org/format/#error-objects -type ErrorObject struct { - // ID is a unique identifier for this particular occurrence of a problem. - ID string `json:"id,omitempty"` - - // Title is a short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization. - Title string `json:"title,omitempty"` - - // Detail is a human-readable explanation specific to this occurrence of the problem. Like title, this field’s value can be localized. - Detail string `json:"detail,omitempty"` - - // Status is the HTTP status code applicable to this problem, expressed as a string value. - Status string `json:"status,omitempty"` - - // Code is an application-specific error code, expressed as a string value. - Code string `json:"code,omitempty"` - - // Meta is an object containing non-standard meta-information about the error. - Meta *map[string]interface{} `json:"meta,omitempty"` -} - -// Error implements the `Error` interface. -func (e *ErrorObject) Error() string { - return fmt.Sprintf("Error: %s %s\n", e.Title, e.Detail) -} diff --git a/vendor/github.com/svanharmelen/jsonapi/node.go b/vendor/github.com/svanharmelen/jsonapi/node.go deleted file mode 100644 index a58488c82..000000000 --- a/vendor/github.com/svanharmelen/jsonapi/node.go +++ /dev/null @@ -1,121 +0,0 @@ -package jsonapi - -import "fmt" - -// Payloader is used to encapsulate the One and Many payload types -type Payloader interface { - clearIncluded() -} - -// OnePayload is used to represent a generic JSON API payload where a single -// resource (Node) was included as an {} in the "data" key -type OnePayload struct { - Data *Node `json:"data"` - Included []*Node `json:"included,omitempty"` - Links *Links `json:"links,omitempty"` - Meta *Meta `json:"meta,omitempty"` -} - -func (p *OnePayload) clearIncluded() { - p.Included = []*Node{} -} - -// ManyPayload is used to represent a generic JSON API payload where many -// resources (Nodes) were included in an [] in the "data" key -type ManyPayload struct { - Data []*Node `json:"data"` - Included []*Node `json:"included,omitempty"` - Links *Links `json:"links,omitempty"` - Meta *Meta `json:"meta,omitempty"` -} - -func (p *ManyPayload) clearIncluded() { - p.Included = []*Node{} -} - -// Node is used to represent a generic JSON API Resource -type Node struct { - Type string `json:"type"` - ID string `json:"id,omitempty"` - ClientID string `json:"client-id,omitempty"` - Attributes map[string]interface{} `json:"attributes,omitempty"` - Relationships map[string]interface{} `json:"relationships,omitempty"` - Links *Links `json:"links,omitempty"` - Meta *Meta `json:"meta,omitempty"` -} - -// RelationshipOneNode is used to represent a generic has one JSON API relation -type RelationshipOneNode struct { - Data *Node `json:"data"` - Links *Links `json:"links,omitempty"` - Meta *Meta `json:"meta,omitempty"` -} - -// RelationshipManyNode is used to represent a generic has many JSON API -// relation -type RelationshipManyNode struct { - Data []*Node `json:"data"` - Links *Links `json:"links,omitempty"` - Meta *Meta `json:"meta,omitempty"` -} - -// Links is used to represent a `links` object. -// http://jsonapi.org/format/#document-links -type Links map[string]interface{} - -func (l *Links) validate() (err error) { - // Each member of a links object is a “link”. A link MUST be represented as - // either: - // - a string containing the link’s URL. - // - an object (“link object”) which can contain the following members: - // - href: a string containing the link’s URL. - // - meta: a meta object containing non-standard meta-information about the - // link. - for k, v := range *l { - _, isString := v.(string) - _, isLink := v.(Link) - - if !(isString || isLink) { - return fmt.Errorf( - "The %s member of the links object was not a string or link object", - k, - ) - } - } - return -} - -// Link is used to represent a member of the `links` object. -type Link struct { - Href string `json:"href"` - Meta Meta `json:"meta,omitempty"` -} - -// Linkable is used to include document links in response data -// e.g. {"self": "http://example.com/posts/1"} -type Linkable interface { - JSONAPILinks() *Links -} - -// RelationshipLinkable is used to include relationship links in response data -// e.g. {"related": "http://example.com/posts/1/comments"} -type RelationshipLinkable interface { - // JSONAPIRelationshipLinks will be invoked for each relationship with the corresponding relation name (e.g. `comments`) - JSONAPIRelationshipLinks(relation string) *Links -} - -// Meta is used to represent a `meta` object. -// http://jsonapi.org/format/#document-meta -type Meta map[string]interface{} - -// Metable is used to include document meta in response data -// e.g. {"foo": "bar"} -type Metable interface { - JSONAPIMeta() *Meta -} - -// RelationshipMetable is used to include relationship meta in response data -type RelationshipMetable interface { - // JSONRelationshipMeta will be invoked for each relationship with the corresponding relation name (e.g. `comments`) - JSONAPIRelationshipMeta(relation string) *Meta -} diff --git a/vendor/github.com/svanharmelen/jsonapi/request.go b/vendor/github.com/svanharmelen/jsonapi/request.go deleted file mode 100644 index e3543428a..000000000 --- a/vendor/github.com/svanharmelen/jsonapi/request.go +++ /dev/null @@ -1,680 +0,0 @@ -package jsonapi - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "reflect" - "strconv" - "strings" - "time" -) - -const ( - unsuportedStructTagMsg = "Unsupported jsonapi tag annotation, %s" -) - -var ( - // ErrInvalidTime is returned when a struct has a time.Time type field, but - // the JSON value was not a unix timestamp integer. - ErrInvalidTime = errors.New("Only numbers can be parsed as dates, unix timestamps") - // ErrInvalidISO8601 is returned when a struct has a time.Time type field and includes - // "iso8601" in the tag spec, but the JSON value was not an ISO8601 timestamp string. - ErrInvalidISO8601 = errors.New("Only strings can be parsed as dates, ISO8601 timestamps") - // ErrUnknownFieldNumberType is returned when the JSON value was a float - // (numeric) but the Struct field was a non numeric type (i.e. not int, uint, - // float, etc) - ErrUnknownFieldNumberType = errors.New("The struct field was not of a known number type") - // ErrInvalidType is returned when the given type is incompatible with the expected type. - ErrInvalidType = errors.New("Invalid type provided") // I wish we used punctuation. - -) - -// ErrUnsupportedPtrType is returned when the Struct field was a pointer but -// the JSON value was of a different type -type ErrUnsupportedPtrType struct { - rf reflect.Value - t reflect.Type - structField reflect.StructField -} - -func (eupt ErrUnsupportedPtrType) Error() string { - typeName := eupt.t.Elem().Name() - kind := eupt.t.Elem().Kind() - if kind.String() != "" && kind.String() != typeName { - typeName = fmt.Sprintf("%s (%s)", typeName, kind.String()) - } - return fmt.Sprintf( - "jsonapi: Can't unmarshal %+v (%s) to struct field `%s`, which is a pointer to `%s`", - eupt.rf, eupt.rf.Type().Kind(), eupt.structField.Name, typeName, - ) -} - -func newErrUnsupportedPtrType(rf reflect.Value, t reflect.Type, structField reflect.StructField) error { - return ErrUnsupportedPtrType{rf, t, structField} -} - -// UnmarshalPayload converts an io into a struct instance using jsonapi tags on -// struct fields. This method supports single request payloads only, at the -// moment. Bulk creates and updates are not supported yet. -// -// Will Unmarshal embedded and sideloaded payloads. The latter is only possible if the -// object graph is complete. That is, in the "relationships" data there are type and id, -// keys that correspond to records in the "included" array. -// -// For example you could pass it, in, req.Body and, model, a BlogPost -// struct instance to populate in an http handler, -// -// func CreateBlog(w http.ResponseWriter, r *http.Request) { -// blog := new(Blog) -// -// if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil { -// http.Error(w, err.Error(), 500) -// return -// } -// -// // ...do stuff with your blog... -// -// w.Header().Set("Content-Type", jsonapi.MediaType) -// w.WriteHeader(201) -// -// if err := jsonapi.MarshalPayload(w, blog); err != nil { -// http.Error(w, err.Error(), 500) -// } -// } -// -// -// Visit https://github.com/google/jsonapi#create for more info. -// -// model interface{} should be a pointer to a struct. -func UnmarshalPayload(in io.Reader, model interface{}) error { - payload := new(OnePayload) - - if err := json.NewDecoder(in).Decode(payload); err != nil { - return err - } - - if payload.Included != nil { - includedMap := make(map[string]*Node) - for _, included := range payload.Included { - key := fmt.Sprintf("%s,%s", included.Type, included.ID) - includedMap[key] = included - } - - return unmarshalNode(payload.Data, reflect.ValueOf(model), &includedMap) - } - return unmarshalNode(payload.Data, reflect.ValueOf(model), nil) -} - -// UnmarshalManyPayload converts an io into a set of struct instances using -// jsonapi tags on the type's struct fields. -func UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error) { - payload := new(ManyPayload) - - if err := json.NewDecoder(in).Decode(payload); err != nil { - return nil, err - } - - models := []interface{}{} // will be populated from the "data" - includedMap := map[string]*Node{} // will be populate from the "included" - - if payload.Included != nil { - for _, included := range payload.Included { - key := fmt.Sprintf("%s,%s", included.Type, included.ID) - includedMap[key] = included - } - } - - for _, data := range payload.Data { - model := reflect.New(t.Elem()) - err := unmarshalNode(data, model, &includedMap) - if err != nil { - return nil, err - } - models = append(models, model.Interface()) - } - - return models, nil -} - -func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) (err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("data is not a jsonapi representation of '%v'", model.Type()) - } - }() - - modelValue := model.Elem() - modelType := model.Type().Elem() - - var er error - - for i := 0; i < modelValue.NumField(); i++ { - fieldType := modelType.Field(i) - tag := fieldType.Tag.Get("jsonapi") - if tag == "" { - continue - } - - fieldValue := modelValue.Field(i) - - args := strings.Split(tag, ",") - if len(args) < 1 { - er = ErrBadJSONAPIStructTag - break - } - - annotation := args[0] - - if (annotation == annotationClientID && len(args) != 1) || - (annotation != annotationClientID && len(args) < 2) { - er = ErrBadJSONAPIStructTag - break - } - - if annotation == annotationPrimary { - if data.ID == "" { - continue - } - - // Check the JSON API Type - if data.Type != args[1] { - er = fmt.Errorf( - "Trying to Unmarshal an object of type %#v, but %#v does not match", - data.Type, - args[1], - ) - break - } - - // ID will have to be transmitted as astring per the JSON API spec - v := reflect.ValueOf(data.ID) - - // Deal with PTRS - var kind reflect.Kind - if fieldValue.Kind() == reflect.Ptr { - kind = fieldType.Type.Elem().Kind() - } else { - kind = fieldType.Type.Kind() - } - - // Handle String case - if kind == reflect.String { - assign(fieldValue, v) - continue - } - - // Value was not a string... only other supported type was a numeric, - // which would have been sent as a float value. - floatValue, err := strconv.ParseFloat(data.ID, 64) - if err != nil { - // Could not convert the value in the "id" attr to a float - er = ErrBadJSONAPIID - break - } - - // Convert the numeric float to one of the supported ID numeric types - // (int[8,16,32,64] or uint[8,16,32,64]) - var idValue reflect.Value - switch kind { - case reflect.Int: - n := int(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int8: - n := int8(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int16: - n := int16(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int32: - n := int32(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int64: - n := int64(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint: - n := uint(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint8: - n := uint8(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint16: - n := uint16(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint32: - n := uint32(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint64: - n := uint64(floatValue) - idValue = reflect.ValueOf(&n) - default: - // We had a JSON float (numeric), but our field was not one of the - // allowed numeric types - er = ErrBadJSONAPIID - break - } - - assign(fieldValue, idValue) - } else if annotation == annotationClientID { - if data.ClientID == "" { - continue - } - - fieldValue.Set(reflect.ValueOf(data.ClientID)) - } else if annotation == annotationAttribute { - attributes := data.Attributes - - if attributes == nil || len(data.Attributes) == 0 { - continue - } - - attribute := attributes[args[1]] - - // continue if the attribute was not included in the request - if attribute == nil { - continue - } - - structField := fieldType - value, err := unmarshalAttribute(attribute, args, structField, fieldValue) - if err != nil { - er = err - break - } - - assign(fieldValue, value) - continue - - } else if annotation == annotationRelation { - isSlice := fieldValue.Type().Kind() == reflect.Slice - - if data.Relationships == nil || data.Relationships[args[1]] == nil { - continue - } - - if isSlice { - // to-many relationship - relationship := new(RelationshipManyNode) - - buf := bytes.NewBuffer(nil) - - json.NewEncoder(buf).Encode(data.Relationships[args[1]]) - json.NewDecoder(buf).Decode(relationship) - - data := relationship.Data - models := reflect.New(fieldValue.Type()).Elem() - - for _, n := range data { - m := reflect.New(fieldValue.Type().Elem().Elem()) - - if err := unmarshalNode( - fullNode(n, included), - m, - included, - ); err != nil { - er = err - break - } - - models = reflect.Append(models, m) - } - - fieldValue.Set(models) - } else { - // to-one relationships - relationship := new(RelationshipOneNode) - - buf := bytes.NewBuffer(nil) - - json.NewEncoder(buf).Encode( - data.Relationships[args[1]], - ) - json.NewDecoder(buf).Decode(relationship) - - /* - http://jsonapi.org/format/#document-resource-object-relationships - http://jsonapi.org/format/#document-resource-object-linkage - relationship can have a data node set to null (e.g. to disassociate the relationship) - so unmarshal and set fieldValue only if data obj is not null - */ - if relationship.Data == nil { - continue - } - - m := reflect.New(fieldValue.Type().Elem()) - if err := unmarshalNode( - fullNode(relationship.Data, included), - m, - included, - ); err != nil { - er = err - break - } - - fieldValue.Set(m) - - } - - } else { - er = fmt.Errorf(unsuportedStructTagMsg, annotation) - } - } - - return er -} - -func fullNode(n *Node, included *map[string]*Node) *Node { - includedKey := fmt.Sprintf("%s,%s", n.Type, n.ID) - - if included != nil && (*included)[includedKey] != nil { - return (*included)[includedKey] - } - - return n -} - -// assign will take the value specified and assign it to the field; if -// field is expecting a ptr assign will assign a ptr. -func assign(field, value reflect.Value) { - value = reflect.Indirect(value) - - if field.Kind() == reflect.Ptr { - // initialize pointer so it's value - // can be set by assignValue - field.Set(reflect.New(field.Type().Elem())) - assignValue(field.Elem(), value) - } else { - assignValue(field, value) - } -} - -// assign assigns the specified value to the field, -// expecting both values not to be pointer types. -func assignValue(field, value reflect.Value) { - switch field.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, - reflect.Int32, reflect.Int64: - field.SetInt(value.Int()) - case reflect.Uint, reflect.Uint8, reflect.Uint16, - reflect.Uint32, reflect.Uint64, reflect.Uintptr: - field.SetUint(value.Uint()) - case reflect.Float32, reflect.Float64: - field.SetFloat(value.Float()) - case reflect.String: - field.SetString(value.String()) - case reflect.Bool: - field.SetBool(value.Bool()) - default: - field.Set(value) - } -} - -func unmarshalAttribute( - attribute interface{}, - args []string, - structField reflect.StructField, - fieldValue reflect.Value) (value reflect.Value, err error) { - value = reflect.ValueOf(attribute) - fieldType := structField.Type - - // Handle field of type []string - if fieldValue.Type() == reflect.TypeOf([]string{}) { - value, err = handleStringSlice(attribute, args, fieldType, fieldValue) - return - } - - // Handle field of type time.Time - if fieldValue.Type() == reflect.TypeOf(time.Time{}) || - fieldValue.Type() == reflect.TypeOf(new(time.Time)) { - value, err = handleTime(attribute, args, fieldType, fieldValue) - return - } - - // Handle field of type struct - if fieldValue.Type().Kind() == reflect.Struct { - value, err = handleStruct(attribute, args, fieldType, fieldValue) - return - } - - // Handle field containing slice of structs - if fieldValue.Type().Kind() == reflect.Slice { - elem := reflect.TypeOf(fieldValue.Interface()).Elem() - if elem.Kind() == reflect.Ptr { - elem = elem.Elem() - } - - if elem.Kind() == reflect.Struct { - value, err = handleStructSlice(attribute, args, fieldType, fieldValue) - return - } - } - - // JSON value was a float (numeric) - if value.Kind() == reflect.Float64 { - value, err = handleNumeric(attribute, args, fieldType, fieldValue) - return - } - - // Field was a Pointer type - if fieldValue.Kind() == reflect.Ptr { - value, err = handlePointer(attribute, args, fieldType, fieldValue, structField) - return - } - - // As a final catch-all, ensure types line up to avoid a runtime panic. - if fieldValue.Kind() != value.Kind() { - err = ErrInvalidType - return - } - - return -} - -func handleStringSlice( - attribute interface{}, - args []string, - fieldType reflect.Type, - fieldValue reflect.Value) (reflect.Value, error) { - v := reflect.ValueOf(attribute) - values := make([]string, v.Len()) - for i := 0; i < v.Len(); i++ { - values[i] = v.Index(i).Interface().(string) - } - - return reflect.ValueOf(values), nil -} - -func handleTime( - attribute interface{}, - args []string, - fieldType reflect.Type, - fieldValue reflect.Value) (reflect.Value, error) { - var isIso8601 bool - v := reflect.ValueOf(attribute) - - if len(args) > 2 { - for _, arg := range args[2:] { - if arg == annotationISO8601 { - isIso8601 = true - } - } - } - - if isIso8601 { - var tm string - if v.Kind() == reflect.String { - tm = v.Interface().(string) - } else { - return reflect.ValueOf(time.Now()), ErrInvalidISO8601 - } - - t, err := time.Parse(iso8601TimeFormat, tm) - if err != nil { - return reflect.ValueOf(time.Now()), ErrInvalidISO8601 - } - - if fieldValue.Kind() == reflect.Ptr { - return reflect.ValueOf(&t), nil - } - - return reflect.ValueOf(t), nil - } - - var at int64 - - if v.Kind() == reflect.Float64 { - at = int64(v.Interface().(float64)) - } else if v.Kind() == reflect.Int { - at = v.Int() - } else { - return reflect.ValueOf(time.Now()), ErrInvalidTime - } - - t := time.Unix(at, 0) - - return reflect.ValueOf(t), nil -} - -func handleNumeric( - attribute interface{}, - args []string, - fieldType reflect.Type, - fieldValue reflect.Value) (reflect.Value, error) { - v := reflect.ValueOf(attribute) - floatValue := v.Interface().(float64) - - var kind reflect.Kind - if fieldValue.Kind() == reflect.Ptr { - kind = fieldType.Elem().Kind() - } else { - kind = fieldType.Kind() - } - - var numericValue reflect.Value - - switch kind { - case reflect.Int: - n := int(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int8: - n := int8(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int16: - n := int16(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int32: - n := int32(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int64: - n := int64(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint: - n := uint(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint8: - n := uint8(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint16: - n := uint16(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint32: - n := uint32(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint64: - n := uint64(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Float32: - n := float32(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Float64: - n := floatValue - numericValue = reflect.ValueOf(&n) - default: - return reflect.Value{}, ErrUnknownFieldNumberType - } - - return numericValue, nil -} - -func handlePointer( - attribute interface{}, - args []string, - fieldType reflect.Type, - fieldValue reflect.Value, - structField reflect.StructField) (reflect.Value, error) { - t := fieldValue.Type() - var concreteVal reflect.Value - - switch cVal := attribute.(type) { - case string: - concreteVal = reflect.ValueOf(&cVal) - case bool: - concreteVal = reflect.ValueOf(&cVal) - case complex64, complex128, uintptr: - concreteVal = reflect.ValueOf(&cVal) - case map[string]interface{}: - var err error - concreteVal, err = handleStruct(attribute, args, fieldType, fieldValue) - if err != nil { - return reflect.Value{}, newErrUnsupportedPtrType( - reflect.ValueOf(attribute), fieldType, structField) - } - return concreteVal.Elem(), err - default: - return reflect.Value{}, newErrUnsupportedPtrType( - reflect.ValueOf(attribute), fieldType, structField) - } - - if t != concreteVal.Type() { - return reflect.Value{}, newErrUnsupportedPtrType( - reflect.ValueOf(attribute), fieldType, structField) - } - - return concreteVal, nil -} - -func handleStruct( - attribute interface{}, - args []string, - fieldType reflect.Type, - fieldValue reflect.Value) (reflect.Value, error) { - model := reflect.New(fieldValue.Type()) - - data, err := json.Marshal(attribute) - if err != nil { - return model, err - } - - err = json.Unmarshal(data, model.Interface()) - - if err != nil { - return model, err - } - - return model, err -} - -func handleStructSlice( - attribute interface{}, - args []string, - fieldType reflect.Type, - fieldValue reflect.Value) (reflect.Value, error) { - models := reflect.New(fieldValue.Type()).Elem() - dataMap := reflect.ValueOf(attribute).Interface().([]interface{}) - for _, data := range dataMap { - model := reflect.New(fieldValue.Type().Elem()).Elem() - modelType := model.Type() - - value, err := handleStruct(data, []string{}, modelType, model) - - if err != nil { - continue - } - - models = reflect.Append(models, reflect.Indirect(value)) - } - - return models, nil -} diff --git a/vendor/github.com/svanharmelen/jsonapi/response.go b/vendor/github.com/svanharmelen/jsonapi/response.go deleted file mode 100644 index e8e85fa42..000000000 --- a/vendor/github.com/svanharmelen/jsonapi/response.go +++ /dev/null @@ -1,539 +0,0 @@ -package jsonapi - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "reflect" - "strconv" - "strings" - "time" -) - -var ( - // ErrBadJSONAPIStructTag is returned when the Struct field's JSON API - // annotation is invalid. - ErrBadJSONAPIStructTag = errors.New("Bad jsonapi struct tag format") - // ErrBadJSONAPIID is returned when the Struct JSON API annotated "id" field - // was not a valid numeric type. - ErrBadJSONAPIID = errors.New( - "id should be either string, int(8,16,32,64) or uint(8,16,32,64)") - // ErrExpectedSlice is returned when a variable or argument was expected to - // be a slice of *Structs; MarshalMany will return this error when its - // interface{} argument is invalid. - ErrExpectedSlice = errors.New("models should be a slice of struct pointers") - // ErrUnexpectedType is returned when marshalling an interface; the interface - // had to be a pointer or a slice; otherwise this error is returned. - ErrUnexpectedType = errors.New("models should be a struct pointer or slice of struct pointers") -) - -// MarshalPayload writes a jsonapi response for one or many records. The -// related records are sideloaded into the "included" array. If this method is -// given a struct pointer as an argument it will serialize in the form -// "data": {...}. If this method is given a slice of pointers, this method will -// serialize in the form "data": [...] -// -// One Example: you could pass it, w, your http.ResponseWriter, and, models, a -// ptr to a Blog to be written to the response body: -// -// func ShowBlog(w http.ResponseWriter, r *http.Request) { -// blog := &Blog{} -// -// w.Header().Set("Content-Type", jsonapi.MediaType) -// w.WriteHeader(http.StatusOK) -// -// if err := jsonapi.MarshalPayload(w, blog); err != nil { -// http.Error(w, err.Error(), http.StatusInternalServerError) -// } -// } -// -// Many Example: you could pass it, w, your http.ResponseWriter, and, models, a -// slice of Blog struct instance pointers to be written to the response body: -// -// func ListBlogs(w http.ResponseWriter, r *http.Request) { -// blogs := []*Blog{} -// -// w.Header().Set("Content-Type", jsonapi.MediaType) -// w.WriteHeader(http.StatusOK) -// -// if err := jsonapi.MarshalPayload(w, blogs); err != nil { -// http.Error(w, err.Error(), http.StatusInternalServerError) -// } -// } -// -func MarshalPayload(w io.Writer, models interface{}) error { - payload, err := Marshal(models) - if err != nil { - return err - } - - if err := json.NewEncoder(w).Encode(payload); err != nil { - return err - } - return nil -} - -// Marshal does the same as MarshalPayload except it just returns the payload -// and doesn't write out results. Useful if you use your own JSON rendering -// library. -func Marshal(models interface{}) (Payloader, error) { - switch vals := reflect.ValueOf(models); vals.Kind() { - case reflect.Slice: - m, err := convertToSliceInterface(&models) - if err != nil { - return nil, err - } - - payload, err := marshalMany(m) - if err != nil { - return nil, err - } - - if linkableModels, isLinkable := models.(Linkable); isLinkable { - jl := linkableModels.JSONAPILinks() - if er := jl.validate(); er != nil { - return nil, er - } - payload.Links = linkableModels.JSONAPILinks() - } - - if metableModels, ok := models.(Metable); ok { - payload.Meta = metableModels.JSONAPIMeta() - } - - return payload, nil - case reflect.Ptr: - // Check that the pointer was to a struct - if reflect.Indirect(vals).Kind() != reflect.Struct { - return nil, ErrUnexpectedType - } - return marshalOne(models) - default: - return nil, ErrUnexpectedType - } -} - -// MarshalPayloadWithoutIncluded writes a jsonapi response with one or many -// records, without the related records sideloaded into "included" array. -// If you want to serialize the relations into the "included" array see -// MarshalPayload. -// -// models interface{} should be either a struct pointer or a slice of struct -// pointers. -func MarshalPayloadWithoutIncluded(w io.Writer, model interface{}) error { - payload, err := Marshal(model) - if err != nil { - return err - } - payload.clearIncluded() - - if err := json.NewEncoder(w).Encode(payload); err != nil { - return err - } - return nil -} - -// marshalOne does the same as MarshalOnePayload except it just returns the -// payload and doesn't write out results. Useful is you use your JSON rendering -// library. -func marshalOne(model interface{}) (*OnePayload, error) { - included := make(map[string]*Node) - - rootNode, err := visitModelNode(model, &included, true) - if err != nil { - return nil, err - } - payload := &OnePayload{Data: rootNode} - - payload.Included = nodeMapValues(&included) - - return payload, nil -} - -// marshalMany does the same as MarshalManyPayload except it just returns the -// payload and doesn't write out results. Useful is you use your JSON rendering -// library. -func marshalMany(models []interface{}) (*ManyPayload, error) { - payload := &ManyPayload{ - Data: []*Node{}, - } - included := map[string]*Node{} - - for _, model := range models { - node, err := visitModelNode(model, &included, true) - if err != nil { - return nil, err - } - payload.Data = append(payload.Data, node) - } - payload.Included = nodeMapValues(&included) - - return payload, nil -} - -// MarshalOnePayloadEmbedded - This method not meant to for use in -// implementation code, although feel free. The purpose of this -// method is for use in tests. In most cases, your request -// payloads for create will be embedded rather than sideloaded for -// related records. This method will serialize a single struct -// pointer into an embedded json response. In other words, there -// will be no, "included", array in the json all relationships will -// be serailized inline in the data. -// -// However, in tests, you may want to construct payloads to post -// to create methods that are embedded to most closely resemble -// the payloads that will be produced by the client. This is what -// this method is intended for. -// -// model interface{} should be a pointer to a struct. -func MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error { - rootNode, err := visitModelNode(model, nil, false) - if err != nil { - return err - } - - payload := &OnePayload{Data: rootNode} - - if err := json.NewEncoder(w).Encode(payload); err != nil { - return err - } - - return nil -} - -func visitModelNode(model interface{}, included *map[string]*Node, - sideload bool) (*Node, error) { - node := new(Node) - - var er error - value := reflect.ValueOf(model) - if value.IsNil() { - return nil, nil - } - - modelValue := value.Elem() - modelType := value.Type().Elem() - - for i := 0; i < modelValue.NumField(); i++ { - structField := modelValue.Type().Field(i) - tag := structField.Tag.Get(annotationJSONAPI) - if tag == "" { - continue - } - - fieldValue := modelValue.Field(i) - fieldType := modelType.Field(i) - - args := strings.Split(tag, annotationSeperator) - - if len(args) < 1 { - er = ErrBadJSONAPIStructTag - break - } - - annotation := args[0] - - if (annotation == annotationClientID && len(args) != 1) || - (annotation != annotationClientID && len(args) < 2) { - er = ErrBadJSONAPIStructTag - break - } - - if annotation == annotationPrimary { - v := fieldValue - - // Deal with PTRS - var kind reflect.Kind - if fieldValue.Kind() == reflect.Ptr { - kind = fieldType.Type.Elem().Kind() - v = reflect.Indirect(fieldValue) - } else { - kind = fieldType.Type.Kind() - } - - // Handle allowed types - switch kind { - case reflect.String: - node.ID = v.Interface().(string) - case reflect.Int: - node.ID = strconv.FormatInt(int64(v.Interface().(int)), 10) - case reflect.Int8: - node.ID = strconv.FormatInt(int64(v.Interface().(int8)), 10) - case reflect.Int16: - node.ID = strconv.FormatInt(int64(v.Interface().(int16)), 10) - case reflect.Int32: - node.ID = strconv.FormatInt(int64(v.Interface().(int32)), 10) - case reflect.Int64: - node.ID = strconv.FormatInt(v.Interface().(int64), 10) - case reflect.Uint: - node.ID = strconv.FormatUint(uint64(v.Interface().(uint)), 10) - case reflect.Uint8: - node.ID = strconv.FormatUint(uint64(v.Interface().(uint8)), 10) - case reflect.Uint16: - node.ID = strconv.FormatUint(uint64(v.Interface().(uint16)), 10) - case reflect.Uint32: - node.ID = strconv.FormatUint(uint64(v.Interface().(uint32)), 10) - case reflect.Uint64: - node.ID = strconv.FormatUint(v.Interface().(uint64), 10) - default: - // We had a JSON float (numeric), but our field was not one of the - // allowed numeric types - er = ErrBadJSONAPIID - break - } - - node.Type = args[1] - } else if annotation == annotationClientID { - clientID := fieldValue.String() - if clientID != "" { - node.ClientID = clientID - } - } else if annotation == annotationAttribute { - var omitEmpty, iso8601 bool - - if len(args) > 2 { - for _, arg := range args[2:] { - switch arg { - case annotationOmitEmpty: - omitEmpty = true - case annotationISO8601: - iso8601 = true - } - } - } - - if node.Attributes == nil { - node.Attributes = make(map[string]interface{}) - } - - if fieldValue.Type() == reflect.TypeOf(time.Time{}) { - t := fieldValue.Interface().(time.Time) - - if t.IsZero() { - continue - } - - if iso8601 { - node.Attributes[args[1]] = t.UTC().Format(iso8601TimeFormat) - } else { - node.Attributes[args[1]] = t.Unix() - } - } else if fieldValue.Type() == reflect.TypeOf(new(time.Time)) { - // A time pointer may be nil - if fieldValue.IsNil() { - if omitEmpty { - continue - } - - node.Attributes[args[1]] = nil - } else { - tm := fieldValue.Interface().(*time.Time) - - if tm.IsZero() && omitEmpty { - continue - } - - if iso8601 { - node.Attributes[args[1]] = tm.UTC().Format(iso8601TimeFormat) - } else { - node.Attributes[args[1]] = tm.Unix() - } - } - } else { - // Dealing with a fieldValue that is not a time - emptyValue := reflect.Zero(fieldValue.Type()) - - // See if we need to omit this field - if omitEmpty && reflect.DeepEqual(fieldValue.Interface(), emptyValue.Interface()) { - continue - } - - strAttr, ok := fieldValue.Interface().(string) - if ok { - node.Attributes[args[1]] = strAttr - } else { - node.Attributes[args[1]] = fieldValue.Interface() - } - } - } else if annotation == annotationRelation { - var omitEmpty bool - - //add support for 'omitempty' struct tag for marshaling as absent - if len(args) > 2 { - omitEmpty = args[2] == annotationOmitEmpty - } - - isSlice := fieldValue.Type().Kind() == reflect.Slice - if omitEmpty && - (isSlice && fieldValue.Len() < 1 || - (!isSlice && fieldValue.IsNil())) { - continue - } - - if node.Relationships == nil { - node.Relationships = make(map[string]interface{}) - } - - var relLinks *Links - if linkableModel, ok := model.(RelationshipLinkable); ok { - relLinks = linkableModel.JSONAPIRelationshipLinks(args[1]) - } - - var relMeta *Meta - if metableModel, ok := model.(RelationshipMetable); ok { - relMeta = metableModel.JSONAPIRelationshipMeta(args[1]) - } - - if isSlice { - // to-many relationship - relationship, err := visitModelNodeRelationships( - fieldValue, - included, - sideload, - ) - if err != nil { - er = err - break - } - relationship.Links = relLinks - relationship.Meta = relMeta - - if sideload { - shallowNodes := []*Node{} - for _, n := range relationship.Data { - appendIncluded(included, n) - shallowNodes = append(shallowNodes, toShallowNode(n)) - } - - node.Relationships[args[1]] = &RelationshipManyNode{ - Data: shallowNodes, - Links: relationship.Links, - Meta: relationship.Meta, - } - } else { - node.Relationships[args[1]] = relationship - } - } else { - // to-one relationships - - // Handle null relationship case - if fieldValue.IsNil() { - node.Relationships[args[1]] = &RelationshipOneNode{Data: nil} - continue - } - - relationship, err := visitModelNode( - fieldValue.Interface(), - included, - sideload, - ) - if err != nil { - er = err - break - } - - if sideload { - appendIncluded(included, relationship) - node.Relationships[args[1]] = &RelationshipOneNode{ - Data: toShallowNode(relationship), - Links: relLinks, - Meta: relMeta, - } - } else { - node.Relationships[args[1]] = &RelationshipOneNode{ - Data: relationship, - Links: relLinks, - Meta: relMeta, - } - } - } - - } else { - er = ErrBadJSONAPIStructTag - break - } - } - - if er != nil { - return nil, er - } - - if linkableModel, isLinkable := model.(Linkable); isLinkable { - jl := linkableModel.JSONAPILinks() - if er := jl.validate(); er != nil { - return nil, er - } - node.Links = linkableModel.JSONAPILinks() - } - - if metableModel, ok := model.(Metable); ok { - node.Meta = metableModel.JSONAPIMeta() - } - - return node, nil -} - -func toShallowNode(node *Node) *Node { - return &Node{ - ID: node.ID, - Type: node.Type, - } -} - -func visitModelNodeRelationships(models reflect.Value, included *map[string]*Node, - sideload bool) (*RelationshipManyNode, error) { - nodes := []*Node{} - - for i := 0; i < models.Len(); i++ { - n := models.Index(i).Interface() - - node, err := visitModelNode(n, included, sideload) - if err != nil { - return nil, err - } - - nodes = append(nodes, node) - } - - return &RelationshipManyNode{Data: nodes}, nil -} - -func appendIncluded(m *map[string]*Node, nodes ...*Node) { - included := *m - - for _, n := range nodes { - k := fmt.Sprintf("%s,%s", n.Type, n.ID) - - if _, hasNode := included[k]; hasNode { - continue - } - - included[k] = n - } -} - -func nodeMapValues(m *map[string]*Node) []*Node { - mp := *m - nodes := make([]*Node, len(mp)) - - i := 0 - for _, n := range mp { - nodes[i] = n - i++ - } - - return nodes -} - -func convertToSliceInterface(i *interface{}) ([]interface{}, error) { - vals := reflect.ValueOf(*i) - if vals.Kind() != reflect.Slice { - return nil, ErrExpectedSlice - } - var response []interface{} - for x := 0; x < vals.Len(); x++ { - response = append(response, vals.Index(x).Interface()) - } - return response, nil -} diff --git a/vendor/github.com/svanharmelen/jsonapi/runtime.go b/vendor/github.com/svanharmelen/jsonapi/runtime.go deleted file mode 100644 index 7dc658155..000000000 --- a/vendor/github.com/svanharmelen/jsonapi/runtime.go +++ /dev/null @@ -1,103 +0,0 @@ -package jsonapi - -import ( - "crypto/rand" - "fmt" - "io" - "reflect" - "time" -) - -type Event int - -const ( - UnmarshalStart Event = iota - UnmarshalStop - MarshalStart - MarshalStop -) - -type Runtime struct { - ctx map[string]interface{} -} - -type Events func(*Runtime, Event, string, time.Duration) - -var Instrumentation Events - -func NewRuntime() *Runtime { return &Runtime{make(map[string]interface{})} } - -func (r *Runtime) WithValue(key string, value interface{}) *Runtime { - r.ctx[key] = value - - return r -} - -func (r *Runtime) Value(key string) interface{} { - return r.ctx[key] -} - -func (r *Runtime) Instrument(key string) *Runtime { - return r.WithValue("instrument", key) -} - -func (r *Runtime) shouldInstrument() bool { - return Instrumentation != nil -} - -func (r *Runtime) UnmarshalPayload(reader io.Reader, model interface{}) error { - return r.instrumentCall(UnmarshalStart, UnmarshalStop, func() error { - return UnmarshalPayload(reader, model) - }) -} - -func (r *Runtime) UnmarshalManyPayload(reader io.Reader, kind reflect.Type) (elems []interface{}, err error) { - r.instrumentCall(UnmarshalStart, UnmarshalStop, func() error { - elems, err = UnmarshalManyPayload(reader, kind) - return err - }) - - return -} - -func (r *Runtime) MarshalPayload(w io.Writer, model interface{}) error { - return r.instrumentCall(MarshalStart, MarshalStop, func() error { - return MarshalPayload(w, model) - }) -} - -func (r *Runtime) instrumentCall(start Event, stop Event, c func() error) error { - if !r.shouldInstrument() { - return c() - } - - instrumentationGUID, err := newUUID() - if err != nil { - return err - } - - begin := time.Now() - Instrumentation(r, start, instrumentationGUID, time.Duration(0)) - - if err := c(); err != nil { - return err - } - - diff := time.Duration(time.Now().UnixNano() - begin.UnixNano()) - Instrumentation(r, stop, instrumentationGUID, diff) - - return nil -} - -// citation: http://play.golang.org/p/4FkNSiUDMg -func newUUID() (string, error) { - uuid := make([]byte, 16) - if _, err := io.ReadFull(rand.Reader, uuid); err != nil { - return "", err - } - // variant bits; see section 4.1.1 - uuid[8] = uuid[8]&^0xc0 | 0x80 - // version 4 (pseudo-random); see section 4.1.3 - uuid[6] = uuid[6]&^0xf0 | 0x40 - return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil -} diff --git a/vendor/github.com/xanzy/ssh-agent/pageant_windows.go b/vendor/github.com/xanzy/ssh-agent/pageant_windows.go index 629560796..3507b0228 100644 --- a/vendor/github.com/xanzy/ssh-agent/pageant_windows.go +++ b/vendor/github.com/xanzy/ssh-agent/pageant_windows.go @@ -29,8 +29,8 @@ import ( "errors" "fmt" "sync" - "syscall" - "unsafe" + . "syscall" + . "unsafe" ) // Maximum size of message can be sent to pageant @@ -53,7 +53,7 @@ const ( type copyData struct { dwData uintptr cbData uint32 - lpData unsafe.Pointer + lpData Pointer } var ( @@ -65,7 +65,7 @@ var ( ) func winAPI(dllName, funcName string) func(...uintptr) (uintptr, uintptr, error) { - proc := syscall.MustLoadDLL(dllName).MustFindProc(funcName) + proc := MustLoadDLL(dllName).MustFindProc(funcName) return func(a ...uintptr) (uintptr, uintptr, error) { return proc.Call(a...) } } @@ -96,21 +96,21 @@ func query(msg []byte) ([]byte, error) { thID, _, _ := winGetCurrentThreadID() mapName := fmt.Sprintf("PageantRequest%08x", thID) - pMapName, _ := syscall.UTF16PtrFromString(mapName) + pMapName, _ := UTF16PtrFromString(mapName) - mmap, err := syscall.CreateFileMapping(syscall.InvalidHandle, nil, syscall.PAGE_READWRITE, 0, MaxMessageLen+4, pMapName) + mmap, err := CreateFileMapping(InvalidHandle, nil, PAGE_READWRITE, 0, MaxMessageLen+4, pMapName) if err != nil { return nil, err } - defer syscall.CloseHandle(mmap) + defer CloseHandle(mmap) - ptr, err := syscall.MapViewOfFile(mmap, syscall.FILE_MAP_WRITE, 0, 0, 0) + ptr, err := MapViewOfFile(mmap, FILE_MAP_WRITE, 0, 0, 0) if err != nil { return nil, err } - defer syscall.UnmapViewOfFile(ptr) + defer UnmapViewOfFile(ptr) - mmSlice := (*(*[MaxMessageLen]byte)(unsafe.Pointer(ptr)))[:] + mmSlice := (*(*[MaxMessageLen]byte)(Pointer(ptr)))[:] copy(mmSlice, msg) @@ -119,10 +119,10 @@ func query(msg []byte) ([]byte, error) { cds := copyData{ dwData: agentCopydataID, cbData: uint32(len(mapNameBytesZ)), - lpData: unsafe.Pointer(&(mapNameBytesZ[0])), + lpData: Pointer(&(mapNameBytesZ[0])), } - resp, _, _ := winSendMessage(paWin, wmCopydata, 0, uintptr(unsafe.Pointer(&cds))) + resp, _, _ := winSendMessage(paWin, wmCopydata, 0, uintptr(Pointer(&cds))) if resp == 0 { return nil, ErrSendMessage @@ -140,7 +140,7 @@ func query(msg []byte) ([]byte, error) { } func pageantWindow() uintptr { - nameP, _ := syscall.UTF16PtrFromString("Pageant") - h, _, _ := winFindWindow(uintptr(unsafe.Pointer(nameP)), uintptr(unsafe.Pointer(nameP))) + nameP, _ := UTF16PtrFromString("Pageant") + h, _, _ := winFindWindow(uintptr(Pointer(nameP)), uintptr(Pointer(nameP))) return h }