backend/remote: implement the Local interface
This commit is contained in:
parent
e68377b1f8
commit
35d9ce3f92
|
@ -66,7 +66,7 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, *configload.
|
|||
// Load the latest state. If we enter contextFromPlanFile below then the
|
||||
// state snapshot in the plan file must match this, or else it'll return
|
||||
// error diagnostics.
|
||||
log.Printf("[TRACE] backend/local: retrieving the local state snapshot for workspace %q", op.Workspace)
|
||||
log.Printf("[TRACE] backend/local: retrieving local state snapshot for workspace %q", op.Workspace)
|
||||
opts.State = s.State()
|
||||
|
||||
var tfCtx *terraform.Context
|
||||
|
|
|
@ -328,6 +328,84 @@ func (b *Remote) token(hostname string) (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// Workspaces implements backend.Enhanced.
|
||||
func (b *Remote) Workspaces() ([]string, error) {
|
||||
if b.prefix == "" {
|
||||
return nil, backend.ErrWorkspacesNotSupported
|
||||
}
|
||||
return b.workspaces()
|
||||
}
|
||||
|
||||
// workspaces returns a filtered list of remote workspace names.
|
||||
func (b *Remote) workspaces() ([]string, error) {
|
||||
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
|
||||
}
|
||||
|
||||
// DeleteWorkspace implements backend.Enhanced.
|
||||
func (b *Remote) DeleteWorkspace(name string) error {
|
||||
if b.workspace == "" && name == backend.DefaultStateName {
|
||||
return backend.ErrDefaultWorkspaceNotSupported
|
||||
}
|
||||
if b.prefix == "" && name != backend.DefaultStateName {
|
||||
return backend.ErrWorkspacesNotSupported
|
||||
}
|
||||
|
||||
// Configure the remote workspace name.
|
||||
switch {
|
||||
case name == backend.DefaultStateName:
|
||||
name = b.workspace
|
||||
case b.prefix != "" && !strings.HasPrefix(name, b.prefix):
|
||||
name = b.prefix + name
|
||||
}
|
||||
|
||||
client := &remoteClient{
|
||||
client: b.client,
|
||||
organization: b.organization,
|
||||
workspace: name,
|
||||
}
|
||||
|
||||
return client.Delete()
|
||||
}
|
||||
|
||||
// StateMgr implements backend.Enhanced.
|
||||
func (b *Remote) StateMgr(name string) (state.State, error) {
|
||||
if b.workspace == "" && name == backend.DefaultStateName {
|
||||
|
@ -387,84 +465,6 @@ func (b *Remote) StateMgr(name string) (state.State, error) {
|
|||
return &remote.State{Client: client}, nil
|
||||
}
|
||||
|
||||
// DeleteWorkspace implements backend.Enhanced.
|
||||
func (b *Remote) DeleteWorkspace(name string) error {
|
||||
if b.workspace == "" && name == backend.DefaultStateName {
|
||||
return backend.ErrDefaultWorkspaceNotSupported
|
||||
}
|
||||
if b.prefix == "" && name != backend.DefaultStateName {
|
||||
return backend.ErrWorkspacesNotSupported
|
||||
}
|
||||
|
||||
// Configure the remote workspace name.
|
||||
switch {
|
||||
case name == backend.DefaultStateName:
|
||||
name = b.workspace
|
||||
case b.prefix != "" && !strings.HasPrefix(name, b.prefix):
|
||||
name = b.prefix + name
|
||||
}
|
||||
|
||||
client := &remoteClient{
|
||||
client: b.client,
|
||||
organization: b.organization,
|
||||
workspace: name,
|
||||
}
|
||||
|
||||
return client.Delete()
|
||||
}
|
||||
|
||||
// Workspaces implements backend.Enhanced.
|
||||
func (b *Remote) Workspaces() ([]string, error) {
|
||||
if b.prefix == "" {
|
||||
return nil, backend.ErrWorkspacesNotSupported
|
||||
}
|
||||
return b.workspaces()
|
||||
}
|
||||
|
||||
// workspaces returns a filtered list of remote workspace names.
|
||||
func (b *Remote) workspaces() ([]string, error) {
|
||||
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) {
|
||||
// Get the remote workspace name.
|
||||
|
@ -645,16 +645,18 @@ func (b *Remote) ReportResult(op *backend.RunningOperation, err error) {
|
|||
// 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
|
||||
// }
|
||||
//
|
||||
// TODO SvH: Rename this back to Colorize as soon as we can pass -no-color.
|
||||
func (b *Remote) cliColorize() *colorstring.Colorize {
|
||||
if b.CLIColor != nil {
|
||||
return b.CLIColor
|
||||
}
|
||||
|
||||
// return &colorstring.Colorize{
|
||||
// Colors: colorstring.DefaultColors,
|
||||
// Disable: true,
|
||||
// }
|
||||
// }
|
||||
return &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Disable: true,
|
||||
}
|
||||
}
|
||||
|
||||
func generalError(msg string, err error) error {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package remote
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/command/clistate"
|
||||
"github.com/hashicorp/terraform/states/statemgr"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
// Context implements backend.Enhanced.
|
||||
func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Full, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
if op.LockState {
|
||||
op.StateLocker = clistate.NewLocker(context.Background(), op.StateLockTimeout, b.CLI, b.cliColorize())
|
||||
} else {
|
||||
op.StateLocker = clistate.NewNoopLocker()
|
||||
}
|
||||
|
||||
// Get the latest state.
|
||||
log.Printf("[TRACE] backend/remote: requesting state manager for workspace %q", op.Workspace)
|
||||
stateMgr, err := b.StateMgr(op.Workspace)
|
||||
if err != nil {
|
||||
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] backend/remote: requesting state lock for workspace %q", op.Workspace)
|
||||
if err := op.StateLocker.Lock(stateMgr, op.Type.String()); err != nil {
|
||||
diags = diags.Append(errwrap.Wrapf("Error locking state: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] backend/remote: reading remote state for workspace %q", op.Workspace)
|
||||
if err := stateMgr.RefreshState(); err != nil {
|
||||
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
// Initialize our context options
|
||||
var opts terraform.ContextOpts
|
||||
if v := b.ContextOpts; v != nil {
|
||||
opts = *v
|
||||
}
|
||||
|
||||
// Copy set options from the operation
|
||||
opts.Destroy = op.Destroy
|
||||
opts.Targets = op.Targets
|
||||
opts.UIInput = op.UIIn
|
||||
|
||||
// Load the latest state. If we enter contextFromPlanFile below then the
|
||||
// state snapshot in the plan file must match this, or else it'll return
|
||||
// error diagnostics.
|
||||
log.Printf("[TRACE] backend/remote: retrieving remote state snapshot for workspace %q", op.Workspace)
|
||||
opts.State = stateMgr.State()
|
||||
|
||||
log.Printf("[TRACE] backend/remote: loading configuration for the current working directory")
|
||||
config, configDiags := op.ConfigLoader.LoadConfig(op.ConfigDir)
|
||||
diags = diags.Append(configDiags)
|
||||
if configDiags.HasErrors() {
|
||||
return nil, nil, diags
|
||||
}
|
||||
opts.Config = config
|
||||
|
||||
log.Printf("[TRACE] backend/remote: retrieving variables from workspace %q", op.Workspace)
|
||||
tfeVariables, err := b.client.Variables.List(context.Background(), tfe.VariableListOptions{
|
||||
Organization: tfe.String(b.organization),
|
||||
Workspace: tfe.String(op.Workspace),
|
||||
})
|
||||
if err != nil && err != tfe.ErrResourceNotFound {
|
||||
diags = diags.Append(errwrap.Wrapf("Error loading variables: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
if tfeVariables != nil {
|
||||
for _, v := range tfeVariables.Items {
|
||||
if v.Sensitive {
|
||||
v.Value = "<sensitive>"
|
||||
}
|
||||
op.Variables[v.Key] = &unparsedVariableValue{
|
||||
value: v.Value,
|
||||
source: terraform.ValueFromCLIArg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variables, varDiags := backend.ParseVariableValues(op.Variables, config.Module.Variables)
|
||||
diags = diags.Append(varDiags)
|
||||
if diags.HasErrors() {
|
||||
return nil, nil, diags
|
||||
}
|
||||
if op.Variables != nil {
|
||||
opts.Variables = variables
|
||||
}
|
||||
|
||||
tfCtx, ctxDiags := terraform.NewContext(&opts)
|
||||
diags = diags.Append(ctxDiags)
|
||||
|
||||
log.Printf("[TRACE] backend/remote: finished building terraform.Context")
|
||||
|
||||
return tfCtx, stateMgr, diags
|
||||
}
|
|
@ -6,6 +6,9 @@ import (
|
|||
"github.com/mitchellh/colorstring"
|
||||
)
|
||||
|
||||
// TODO SvH: This file should be deleted and the type cliColorize should be
|
||||
// renamed back to Colorize as soon as we can pass -no-color to the backend.
|
||||
|
||||
// colorsRe is used to find ANSI escaped color codes.
|
||||
var colorsRe = regexp.MustCompile("\033\\[\\d{1,3}m")
|
||||
|
||||
|
|
Loading…
Reference in New Issue