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 }