Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the remote enhanced backend #19299

Merged
merged 1 commit into from
Nov 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions backend/atlas/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ type Backend struct {

var _ backend.Backend = (*Backend)(nil)

// New returns a new initialized Atlas backend.
func New() *Backend {
return &Backend{}
}

func (b *Backend) ConfigSchema() *configschema.Block {
return &configschema.Block{
Attributes: map[string]*configschema.Attribute{
Expand Down Expand Up @@ -163,16 +168,16 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
}

func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrNamedStatesNotSupported
return nil, backend.ErrWorkspacesNotSupported
}

func (b *Backend) DeleteWorkspace(name string) error {
return backend.ErrNamedStatesNotSupported
return backend.ErrWorkspacesNotSupported
}

func (b *Backend) StateMgr(name string) (state.State, error) {
if name != backend.DefaultStateName {
return nil, backend.ErrNamedStatesNotSupported
return nil, backend.ErrWorkspacesNotSupported
}

return &remote.State{Client: b.stateClient}, nil
Expand Down
4 changes: 2 additions & 2 deletions backend/atlas/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestConfigure_envAddr(t *testing.T) {
defer os.Setenv("ATLAS_ADDRESS", os.Getenv("ATLAS_ADDRESS"))
os.Setenv("ATLAS_ADDRESS", "http://foo.com")

b := &Backend{}
b := New()
diags := b.Configure(cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("foo/bar"),
"address": cty.NullVal(cty.String),
Expand All @@ -37,7 +37,7 @@ func TestConfigure_envToken(t *testing.T) {
defer os.Setenv("ATLAS_TOKEN", os.Getenv("ATLAS_TOKEN"))
os.Setenv("ATLAS_TOKEN", "foo")

b := &Backend{}
b := New()
diags := b.Configure(cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("foo/bar"),
"address": cty.NullVal(cty.String),
Expand Down
2 changes: 1 addition & 1 deletion backend/atlas/state_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func testStateClient(t *testing.T, c map[string]string) remote.Client {
}
synthBody := configs.SynthBody("<test>", vals)

b := backend.TestBackendConfig(t, &Backend{}, synthBody)
b := backend.TestBackendConfig(t, New(), synthBody)
raw, err := b.StateMgr(backend.DefaultStateName)
if err != nil {
t.Fatalf("err: %s", err)
Expand Down
44 changes: 25 additions & 19 deletions backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"errors"
"time"

"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs"
Expand All @@ -22,24 +20,35 @@ import (
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
)

// DefaultStateName is the name of the default, initial state that every
// backend must have. This state cannot be deleted.
const DefaultStateName = "default"

// ErrWorkspacesNotSupported is an error returned when a caller attempts
// to perform an operation on a workspace other than "default" for a
// backend that doesn't support multiple workspaces.
//
// The caller can detect this to do special fallback behavior or produce
// a specific, helpful error message.
var ErrWorkspacesNotSupported = errors.New("workspaces not supported")
var (
// ErrDefaultWorkspaceNotSupported is returned when an operation does not
// support using the default workspace, but requires a named workspace to
// be selected.
ErrDefaultWorkspaceNotSupported = errors.New("default workspace not supported\n" +
"You can create a new workspace with the \"workspace new\" command.")

// ErrNamedStatesNotSupported is an older name for ErrWorkspacesNotSupported.
//
// Deprecated: Use ErrWorkspacesNotSupported instead.
var ErrNamedStatesNotSupported = ErrWorkspacesNotSupported
// ErrOperationNotSupported is returned when an unsupported operation
// is detected by the configured backend.
ErrOperationNotSupported = errors.New("operation not supported")

// ErrWorkspacesNotSupported is an error returned when a caller attempts
// to perform an operation on a workspace other than "default" for a
// backend that doesn't support multiple workspaces.
//
// The caller can detect this to do special fallback behavior or produce
// a specific, helpful error message.
ErrWorkspacesNotSupported = errors.New("workspaces not supported")
)

// InitFn is used to initialize a new backend.
type InitFn func() Backend

// Backend is the minimal interface that must be implemented to enable Terraform.
type Backend interface {
Expand Down Expand Up @@ -179,11 +188,12 @@ type Operation struct {

// The options below are more self-explanatory and affect the runtime
// behavior of the operation.
AutoApprove bool
Destroy bool
DestroyForce bool
Parallelism int
Targets []addrs.Targetable
Variables map[string]UnparsedVariableValue
AutoApprove bool
DestroyForce bool

// Input/output/control options.
UIIn terraform.UIInput
Expand Down Expand Up @@ -244,10 +254,6 @@ type RunningOperation struct {
// operation has completed.
Result OperationResult

// ExitCode can be used to set a custom exit code. This enables enhanced
// backends to set specific exit codes that miror any remote exit codes.
ExitCode int

// PlanEmpty is populated after a Plan operation completes without error
// to note whether a plan is empty or has changes.
PlanEmpty bool
Expand Down
66 changes: 40 additions & 26 deletions backend/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,28 @@
package init

import (
"os"
"sync"

"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/svchost/disco"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"

backendatlas "github.com/hashicorp/terraform/backend/atlas"
backendlocal "github.com/hashicorp/terraform/backend/local"
backendartifactory "github.com/hashicorp/terraform/backend/remote-state/artifactory"
backendAtlas "github.com/hashicorp/terraform/backend/atlas"
backendLocal "github.com/hashicorp/terraform/backend/local"
backendRemote "github.com/hashicorp/terraform/backend/remote"
backendArtifactory "github.com/hashicorp/terraform/backend/remote-state/artifactory"
backendAzure "github.com/hashicorp/terraform/backend/remote-state/azure"
backendconsul "github.com/hashicorp/terraform/backend/remote-state/consul"
backendetcdv2 "github.com/hashicorp/terraform/backend/remote-state/etcdv2"
backendetcdv3 "github.com/hashicorp/terraform/backend/remote-state/etcdv3"
backendConsul "github.com/hashicorp/terraform/backend/remote-state/consul"
backendEtcdv2 "github.com/hashicorp/terraform/backend/remote-state/etcdv2"
backendEtcdv3 "github.com/hashicorp/terraform/backend/remote-state/etcdv3"
backendGCS "github.com/hashicorp/terraform/backend/remote-state/gcs"
backendhttp "github.com/hashicorp/terraform/backend/remote-state/http"
backendinmem "github.com/hashicorp/terraform/backend/remote-state/inmem"
backendHTTP "github.com/hashicorp/terraform/backend/remote-state/http"
backendInmem "github.com/hashicorp/terraform/backend/remote-state/inmem"
backendManta "github.com/hashicorp/terraform/backend/remote-state/manta"
backendS3 "github.com/hashicorp/terraform/backend/remote-state/s3"
backendSwift "github.com/hashicorp/terraform/backend/remote-state/swift"

"github.com/zclconf/go-cty/cty"
)

// backends is the list of available backends. This is a global variable
Expand All @@ -37,27 +38,40 @@ import (
// complex structures and supporting that over the plugin system is currently
// prohibitively difficult. For those wanting to implement a custom backend,
// they can do so with recompilation.
var backends map[string]func() backend.Backend
var backends map[string]backend.InitFn
var backendsLock sync.Mutex

// Init initializes the backends map with all our hardcoded backends.
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{
"artifactory": func() backend.Backend { return backendartifactory.New() },
"atlas": func() backend.Backend { return &backendatlas.Backend{} },
"http": func() backend.Backend { return backendhttp.New() },
"local": func() backend.Backend { return &backendlocal.Local{} },
"consul": func() backend.Backend { return backendconsul.New() },
"inmem": func() backend.Backend { return backendinmem.New() },
"swift": func() backend.Backend { return backendSwift.New() },
"s3": func() backend.Backend { return backendS3.New() },
backendsLock.Lock()
defer backendsLock.Unlock()

backends = map[string]backend.InitFn{
// Enhanced backends.
"local": func() backend.Backend { return backendLocal.New() },
"remote": func() backend.Backend {
b := backendRemote.New(services)
if os.Getenv("TF_FORCE_LOCAL_BACKEND") != "" {
return backendLocal.NewWithBackend(b)
}
return b
},

// Remote State backends.
"artifactory": func() backend.Backend { return backendArtifactory.New() },
"atlas": func() backend.Backend { return backendAtlas.New() },
"azurerm": func() backend.Backend { return backendAzure.New() },
"etcd": func() backend.Backend { return backendetcdv2.New() },
"etcdv3": func() backend.Backend { return backendetcdv3.New() },
"consul": func() backend.Backend { return backendConsul.New() },
"etcd": func() backend.Backend { return backendEtcdv2.New() },
"etcdv3": func() backend.Backend { return backendEtcdv3.New() },
"gcs": func() backend.Backend { return backendGCS.New() },
"http": func() backend.Backend { return backendHTTP.New() },
"inmem": func() backend.Backend { return backendInmem.New() },
"manta": func() backend.Backend { return backendManta.New() },
"s3": func() backend.Backend { return backendS3.New() },
"swift": func() backend.Backend { return backendSwift.New() },

// Deprecated backends.
"azure": func() backend.Backend {
return deprecateBackend(
backendAzure.New(),
Expand All @@ -69,7 +83,7 @@ func Init(services *disco.Disco) {

// Backend returns the initialization factory for the given backend, or
// nil if none exists.
func Backend(name string) func() backend.Backend {
func Backend(name string) backend.InitFn {
backendsLock.Lock()
defer backendsLock.Unlock()
return backends[name]
Expand All @@ -82,7 +96,7 @@ func Backend(name string) func() backend.Backend {
// This method sets this backend globally and care should be taken to do
// this only before Terraform is executing to prevent odd behavior of backends
// changing mid-execution.
func Set(name string, f func() backend.Backend) {
func Set(name string, f backend.InitFn) {
backendsLock.Lock()
defer backendsLock.Unlock()

Expand Down
2 changes: 2 additions & 0 deletions backend/init/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func TestInit_backend(t *testing.T) {
Type string
}{
{"local", "*local.Local"},
{"remote", "*remote.Remote"},
{"atlas", "*atlas.Backend"},
{"azurerm", "*azure.Backend"},
{"consul", "*consul.Backend"},
Expand Down Expand Up @@ -53,6 +54,7 @@ func TestInit_forceLocalBackend(t *testing.T) {
Type string
}{
{"local", "nil"},
{"remote", "*remote.Remote"},
}

// Set the TF_FORCE_LOCAL_BACKEND flag so all enhanced backends will
Expand Down
27 changes: 20 additions & 7 deletions backend/local/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ import (
"strings"
"sync"

"github.com/hashicorp/terraform/tfdiags"

"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
"github.com/mitchellh/colorstring"
"github.com/zclconf/go-cty/cty"
Expand Down Expand Up @@ -96,12 +95,25 @@ type Local struct {
// exact commands that are being run.
RunningInAutomation bool

// opLock locks operations
opLock sync.Mutex
once sync.Once
}

var _ backend.Backend = (*Local)(nil)

// New returns a new initialized local backend.
func New() *Local {
return NewWithBackend(nil)
}

// NewWithBackend returns a new local backend initialized with a
// dedicated backend for non-enhanced behavior.
func NewWithBackend(backend backend.Backend) *Local {
return &Local{
Backend: backend,
}
}

func (b *Local) ConfigSchema() *configschema.Block {
if b.Backend != nil {
return b.Backend.ConfigSchema()
Expand All @@ -116,8 +128,6 @@ func (b *Local) ConfigSchema() *configschema.Block {
Type: cty.String,
Optional: true,
},
// environment_dir was previously a deprecated alias for
// workspace_dir, but now removed.
},
}
}
Expand Down Expand Up @@ -342,7 +352,7 @@ func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.
return runningOp, nil
}

// opWait wats for the operation to complete, and a stop signal or a
// opWait waits for the operation to complete, and a stop signal or a
// cancelation signal.
func (b *Local) opWait(
doneCh <-chan struct{},
Expand Down Expand Up @@ -416,7 +426,10 @@ func (b *Local) ReportResult(op *backend.RunningOperation, diags tfdiags.Diagnos
// Shouldn't generally happen, but if it does then we'll at least
// make some noise in the logs to help us spot it.
if len(diags) != 0 {
log.Printf("[ERROR] Local backend needs to report diagnostics but ShowDiagnostics callback is not set: %s", diags.ErrWithWarnings())
log.Printf(
"[ERROR] Local backend needs to report diagnostics but ShowDiagnostics is not set:\n%s",
diags.ErrWithWarnings(),
)
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions backend/local/backend_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"log"

"github.com/hashicorp/errwrap"

"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statefile"
Expand All @@ -25,15 +24,16 @@ func (b *Local) opApply(
log.Printf("[INFO] backend/local: starting Apply operation")

var diags tfdiags.Diagnostics
var err error

// If we have a nil module at this point, then set it to an empty tree
// to avoid any potential crashes.
if op.PlanFile == nil && !op.Destroy && !op.HasConfig() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No configuration files",
"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, run 'terraform destroy' instead.",
"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, run 'terraform destroy' instead.",
))
b.ReportResult(runningOp, diags)
return
Expand Down Expand Up @@ -155,7 +155,7 @@ func (b *Local) opApply(

// Store the final state
runningOp.State = applyState
err = statemgr.WriteAndPersist(opState, applyState)
err := statemgr.WriteAndPersist(opState, applyState)
if err != nil {
diags = diags.Append(b.backupStateForError(applyState, err))
b.ReportResult(runningOp, diags)
Expand Down
Loading