backend: Update interface and implementations for new config loader
The new config loader requires some steps to happen in a different order, particularly in regard to knowing the schema in order to decode the configuration. Here we lean directly on the configschema package, rather than on helper/schema.Backend as before, because it's generally sufficient for our needs here and this prepares us for the helper/schema package later moving out into its own repository to seed a "plugin SDK".
This commit is contained in:
parent
591aaf1e6a
commit
5782357c28
|
@ -1,14 +1,17 @@
|
|||
package atlas
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/config/configschema"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
|
@ -17,6 +20,9 @@ import (
|
|||
"github.com/mitchellh/colorstring"
|
||||
)
|
||||
|
||||
const EnvVarToken = "ATLAS_TOKEN"
|
||||
const EnvVarAddress = "ATLAS_ADDRESS"
|
||||
|
||||
// Backend is an implementation of EnhancedBackend that performs all operations
|
||||
// in Atlas. State must currently also be stored in Atlas, although it is worth
|
||||
// investigating in the future if state storage can be external as well.
|
||||
|
@ -44,97 +50,130 @@ type Backend struct {
|
|||
opLock sync.Mutex
|
||||
}
|
||||
|
||||
// New returns a new initialized Atlas backend.
|
||||
func New() *Backend {
|
||||
b := &Backend{}
|
||||
b.schema = &schema.Backend{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
func (b *Backend) ConfigSchema() *configschema.Block {
|
||||
return &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"name": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
Description: schemaDescriptions["name"],
|
||||
Description: "Full name of the environment in Terraform Enterprise, such as 'myorg/myenv'",
|
||||
},
|
||||
|
||||
"access_token": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: schemaDescriptions["access_token"],
|
||||
DefaultFunc: schema.EnvDefaultFunc("ATLAS_TOKEN", nil),
|
||||
},
|
||||
|
||||
"address": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
"access_token": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Description: schemaDescriptions["address"],
|
||||
DefaultFunc: schema.EnvDefaultFunc("ATLAS_ADDRESS", defaultAtlasServer),
|
||||
Description: "Access token to use to access Terraform Enterprise; the ATLAS_TOKEN environment variable is used if this argument is not set",
|
||||
},
|
||||
"address": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Description: "Base URL for your Terraform Enterprise installation; the ATLAS_ADDRESS environment variable is used if this argument is not set, finally falling back to a default of 'https://atlas.hashicorp.com/' if neither are set.",
|
||||
},
|
||||
},
|
||||
|
||||
ConfigureFunc: b.configure,
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Backend) configure(ctx context.Context) error {
|
||||
d := schema.FromContextBackendConfig(ctx)
|
||||
func (b *Backend) ValidateConfig(obj cty.Value) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// Parse the address
|
||||
addr := d.Get("address").(string)
|
||||
addrUrl, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error parsing 'address': %s", err)
|
||||
name := obj.GetAttr("name").AsString()
|
||||
if ct := strings.Count(name, "/"); ct != 1 {
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Invalid workspace selector",
|
||||
`The "name" argument must be an organization name and a workspace name separated by a slash, such as "acme/network-production".`,
|
||||
cty.Path{cty.GetAttrStep{Name: "name"}},
|
||||
))
|
||||
}
|
||||
|
||||
// Parse the org/env
|
||||
name := d.Get("name").(string)
|
||||
parts := strings.Split(name, "/")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("malformed name '%s', expected format '<org>/<name>'", name)
|
||||
if v := obj.GetAttr("address"); !v.IsNull() {
|
||||
addr := v.AsString()
|
||||
_, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Invalid Terraform Enterprise URL",
|
||||
fmt.Sprintf(`The "address" argument must be a valid URL: %s.`, err),
|
||||
cty.Path{cty.GetAttrStep{Name: "address"}},
|
||||
))
|
||||
}
|
||||
}
|
||||
org := parts[0]
|
||||
env := parts[1]
|
||||
|
||||
// Setup the client
|
||||
b.stateClient = &stateClient{
|
||||
Server: addr,
|
||||
ServerURL: addrUrl,
|
||||
AccessToken: d.Get("access_token").(string),
|
||||
User: org,
|
||||
Name: env,
|
||||
return diags
|
||||
}
|
||||
|
||||
func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
client := &stateClient{
|
||||
// This is optionally set during Atlas Terraform runs.
|
||||
RunId: os.Getenv("ATLAS_RUN_ID"),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
name := obj.GetAttr("name").AsString() // assumed valid due to ValidateConfig method
|
||||
slashIdx := strings.Index(name, "/")
|
||||
client.User = name[:slashIdx]
|
||||
client.Name = name[slashIdx+1:]
|
||||
|
||||
func (b *Backend) Input(ui terraform.UIInput, c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
|
||||
return b.schema.Input(ui, c)
|
||||
}
|
||||
|
||||
func (b *Backend) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||
return b.schema.Validate(c)
|
||||
}
|
||||
|
||||
func (b *Backend) Configure(c *terraform.ResourceConfig) error {
|
||||
return b.schema.Configure(c)
|
||||
}
|
||||
|
||||
func (b *Backend) State(name string) (state.State, error) {
|
||||
if name != backend.DefaultStateName {
|
||||
return nil, backend.ErrNamedStatesNotSupported
|
||||
if v := obj.GetAttr("access_token"); !v.IsNull() {
|
||||
client.AccessToken = v.AsString()
|
||||
} else {
|
||||
client.AccessToken = os.Getenv(EnvVarToken)
|
||||
if client.AccessToken == "" {
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Missing Terraform Enterprise access token",
|
||||
`The "access_token" argument must be set unless the ATLAS_TOKEN environment variable is set to provide the authentication token for Terraform Enterprise.`,
|
||||
cty.Path{cty.GetAttrStep{Name: "access_token"}},
|
||||
))
|
||||
}
|
||||
}
|
||||
return &remote.State{Client: b.stateClient}, nil
|
||||
|
||||
if v := obj.GetAttr("address"); !v.IsNull() {
|
||||
addr := v.AsString()
|
||||
addrURL, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
// We already validated the URL in ValidateConfig, so this shouldn't happen
|
||||
panic(err)
|
||||
}
|
||||
client.Server = addr
|
||||
client.ServerURL = addrURL
|
||||
} else {
|
||||
addr := os.Getenv(EnvVarAddress)
|
||||
if addr == "" {
|
||||
addr = defaultAtlasServer
|
||||
}
|
||||
addrURL, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Invalid Terraform Enterprise URL",
|
||||
fmt.Sprintf(`The ATLAS_ADDRESS environment variable must contain a valid URL: %s.`, err),
|
||||
cty.Path{cty.GetAttrStep{Name: "address"}},
|
||||
))
|
||||
}
|
||||
client.Server = addr
|
||||
client.ServerURL = addrURL
|
||||
}
|
||||
|
||||
b.stateClient = client
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func (b *Backend) States() ([]string, error) {
|
||||
return nil, backend.ErrNamedStatesNotSupported
|
||||
}
|
||||
|
||||
func (b *Backend) DeleteState(name string) error {
|
||||
return backend.ErrNamedStatesNotSupported
|
||||
}
|
||||
|
||||
func (b *Backend) States() ([]string, error) {
|
||||
return nil, backend.ErrNamedStatesNotSupported
|
||||
func (b *Backend) State(name string) (state.State, error) {
|
||||
if name != backend.DefaultStateName {
|
||||
return nil, backend.ErrNamedStatesNotSupported
|
||||
}
|
||||
|
||||
return &remote.State{Client: b.stateClient}, nil
|
||||
}
|
||||
|
||||
// Colorize returns the Colorize structure that can be used for colorizing
|
||||
|
@ -150,12 +189,3 @@ func (b *Backend) Colorize() *colorstring.Colorize {
|
|||
Disable: true,
|
||||
}
|
||||
}
|
||||
|
||||
var schemaDescriptions = map[string]string{
|
||||
"name": "Full name of the environment in Atlas, such as 'hashicorp/myenv'",
|
||||
"access_token": "Access token to use to access Atlas. If ATLAS_TOKEN is set then\n" +
|
||||
"this will override any saved value for this.",
|
||||
"address": "Address to your Atlas installation. This defaults to the publicly\n" +
|
||||
"hosted version at 'https://atlas.hashicorp.com/'. This address\n" +
|
||||
"should contain the full HTTP scheme to use.",
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestImpl(t *testing.T) {
|
||||
|
@ -18,16 +18,18 @@ func TestConfigure_envAddr(t *testing.T) {
|
|||
defer os.Setenv("ATLAS_ADDRESS", os.Getenv("ATLAS_ADDRESS"))
|
||||
os.Setenv("ATLAS_ADDRESS", "http://foo.com")
|
||||
|
||||
b := New()
|
||||
err := b.Configure(terraform.NewResourceConfig(config.TestRawConfig(t, map[string]interface{}{
|
||||
"name": "foo/bar",
|
||||
})))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
b := &Backend{}
|
||||
diags := b.Configure(cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.StringVal("foo/bar"),
|
||||
"address": cty.NullVal(cty.String),
|
||||
"access_token": cty.StringVal("placeholder"),
|
||||
}))
|
||||
for _, diag := range diags {
|
||||
t.Error(diag)
|
||||
}
|
||||
|
||||
if b.stateClient.Server != "http://foo.com" {
|
||||
t.Fatalf("bad: %#v", b.stateClient)
|
||||
if got, want := b.stateClient.Server, "http://foo.com"; got != want {
|
||||
t.Fatalf("wrong URL %#v; want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,15 +37,17 @@ func TestConfigure_envToken(t *testing.T) {
|
|||
defer os.Setenv("ATLAS_TOKEN", os.Getenv("ATLAS_TOKEN"))
|
||||
os.Setenv("ATLAS_TOKEN", "foo")
|
||||
|
||||
b := New()
|
||||
err := b.Configure(terraform.NewResourceConfig(config.TestRawConfig(t, map[string]interface{}{
|
||||
"name": "foo/bar",
|
||||
})))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
b := &Backend{}
|
||||
diags := b.Configure(cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.StringVal("foo/bar"),
|
||||
"address": cty.NullVal(cty.String),
|
||||
"access_token": cty.NullVal(cty.String),
|
||||
}))
|
||||
for _, diag := range diags {
|
||||
t.Error(diag)
|
||||
}
|
||||
|
||||
if b.stateClient.AccessToken != "foo" {
|
||||
t.Fatalf("bad: %#v", b.stateClient)
|
||||
if got, want := b.stateClient.AccessToken, "foo"; got != want {
|
||||
t.Fatalf("wrong access token %#v; want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,14 +13,23 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func testStateClient(t *testing.T, c map[string]interface{}) remote.Client {
|
||||
b := backend.TestBackendConfig(t, New(), c)
|
||||
func testStateClient(t *testing.T, c map[string]string) remote.Client {
|
||||
vals := make(map[string]cty.Value)
|
||||
for k, s := range c {
|
||||
vals[k] = cty.StringVal(s)
|
||||
}
|
||||
synthBody := configs.SynthBody("<test>", vals)
|
||||
|
||||
b := backend.TestBackendConfig(t, &Backend{}, synthBody)
|
||||
raw, err := b.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -42,7 +51,7 @@ func TestStateClient(t *testing.T) {
|
|||
t.Skipf("skipping, ATLAS_TOKEN must be set")
|
||||
}
|
||||
|
||||
client := testStateClient(t, map[string]interface{}{
|
||||
client := testStateClient(t, map[string]string{
|
||||
"access_token": token,
|
||||
"name": "hashicorp/test-remote-state",
|
||||
})
|
||||
|
@ -53,7 +62,7 @@ func TestStateClient(t *testing.T) {
|
|||
func TestStateClient_noRetryOnBadCerts(t *testing.T) {
|
||||
acctest.RemoteTestPrecheck(t)
|
||||
|
||||
client := testStateClient(t, map[string]interface{}{
|
||||
client := testStateClient(t, map[string]string{
|
||||
"access_token": "NOT_REQUIRED",
|
||||
"name": "hashicorp/test-remote-state",
|
||||
})
|
||||
|
@ -99,7 +108,7 @@ func TestStateClient_ReportedConflictEqualStates(t *testing.T) {
|
|||
srv := fakeAtlas.Server()
|
||||
defer srv.Close()
|
||||
|
||||
client := testStateClient(t, map[string]interface{}{
|
||||
client := testStateClient(t, map[string]string{
|
||||
"access_token": "sometoken",
|
||||
"name": "someuser/some-test-remote-state",
|
||||
"address": srv.URL,
|
||||
|
@ -124,7 +133,7 @@ func TestStateClient_NoConflict(t *testing.T) {
|
|||
srv := fakeAtlas.Server()
|
||||
defer srv.Close()
|
||||
|
||||
client := testStateClient(t, map[string]interface{}{
|
||||
client := testStateClient(t, map[string]string{
|
||||
"access_token": "sometoken",
|
||||
"name": "someuser/some-test-remote-state",
|
||||
"address": srv.URL,
|
||||
|
@ -152,7 +161,7 @@ func TestStateClient_LegitimateConflict(t *testing.T) {
|
|||
srv := fakeAtlas.Server()
|
||||
defer srv.Close()
|
||||
|
||||
client := testStateClient(t, map[string]interface{}{
|
||||
client := testStateClient(t, map[string]string{
|
||||
"access_token": "sometoken",
|
||||
"name": "someuser/some-test-remote-state",
|
||||
"address": srv.URL,
|
||||
|
@ -191,7 +200,7 @@ func TestStateClient_UnresolvableConflict(t *testing.T) {
|
|||
srv := fakeAtlas.Server()
|
||||
defer srv.Close()
|
||||
|
||||
client := testStateClient(t, map[string]interface{}{
|
||||
client := testStateClient(t, map[string]string{
|
||||
"access_token": "sometoken",
|
||||
"name": "someuser/some-test-remote-state",
|
||||
"address": srv.URL,
|
||||
|
|
|
@ -9,10 +9,16 @@ import (
|
|||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/command/clistate"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/config/configschema"
|
||||
"github.com/hashicorp/terraform/configs/configload"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
// DefaultStateName is the name of the default, initial state that every
|
||||
|
@ -41,11 +47,45 @@ type InitFn func() Backend
|
|||
|
||||
// Backend is the minimal interface that must be implemented to enable Terraform.
|
||||
type Backend interface {
|
||||
// Ask for input and configure the backend. Similar to
|
||||
// terraform.ResourceProvider.
|
||||
Input(terraform.UIInput, *terraform.ResourceConfig) (*terraform.ResourceConfig, error)
|
||||
Validate(*terraform.ResourceConfig) ([]string, []error)
|
||||
Configure(*terraform.ResourceConfig) error
|
||||
// ConfigSchema returns a description of the expected configuration
|
||||
// structure for the receiving backend.
|
||||
//
|
||||
// This method does not have any side-effects for the backend and can
|
||||
// be safely used before configuring.
|
||||
ConfigSchema() *configschema.Block
|
||||
|
||||
// ValidateConfig checks the validity of the values in the given
|
||||
// configuration, assuming that its structure has already been validated
|
||||
// per the schema returned by ConfigSchema.
|
||||
//
|
||||
// This method does not have any side-effects for the backend and can
|
||||
// be safely used before configuring. It also does not consult any
|
||||
// external data such as environment variables, disk files, etc. Validation
|
||||
// that requires such external data should be deferred until the
|
||||
// Configure call.
|
||||
//
|
||||
// If error diagnostics are returned then the configuration is not valid
|
||||
// and must not subsequently be passed to the Configure method.
|
||||
//
|
||||
// This method may return configuration-contextual diagnostics such
|
||||
// as tfdiags.AttributeValue, and so the caller should provide the
|
||||
// necessary context via the diags.InConfigBody method before returning
|
||||
// diagnostics to the user.
|
||||
ValidateConfig(cty.Value) tfdiags.Diagnostics
|
||||
|
||||
// Configure uses the provided configuration to set configuration fields
|
||||
// within the backend.
|
||||
//
|
||||
// The given configuration is assumed to have already been validated
|
||||
// against the schema returned by ConfigSchema and passed validation
|
||||
// via ValidateConfig.
|
||||
//
|
||||
// This method may be called only once per backend instance, and must be
|
||||
// called before all other methods except where otherwise stated.
|
||||
//
|
||||
// If error diagnostics are returned, the internal state of the instance
|
||||
// is undefined and no other methods may be called.
|
||||
Configure(cty.Value) tfdiags.Diagnostics
|
||||
|
||||
// State returns the current state for this environment. This state may
|
||||
// not be loaded locally: the proper APIs should be called on state.State
|
||||
|
@ -96,7 +136,7 @@ type Enhanced interface {
|
|||
type Local interface {
|
||||
// Context returns a runnable terraform Context. The operation parameter
|
||||
// doesn't need a Type set but it needs other options set such as Module.
|
||||
Context(*Operation) (*terraform.Context, state.State, error)
|
||||
Context(*Operation) (*terraform.Context, state.State, tfdiags.Diagnostics)
|
||||
}
|
||||
|
||||
// An operation represents an operation for Terraform to execute.
|
||||
|
@ -128,8 +168,13 @@ type Operation struct {
|
|||
PlanOutPath string // PlanOutPath is the path to save the plan
|
||||
PlanOutBackend *terraform.BackendState
|
||||
|
||||
// Module settings specify the root module to use for operations.
|
||||
Module *module.Tree
|
||||
// ConfigDir is the path to the directory containing the configuration's
|
||||
// root module.
|
||||
ConfigDir string
|
||||
|
||||
// ConfigLoader is a configuration loader that can be used to load
|
||||
// configuration from ConfigDir.
|
||||
ConfigLoader *configload.Loader
|
||||
|
||||
// Plan is a plan that was passed as an argument. This is valid for
|
||||
// plan and apply arguments but may not work for all backends.
|
||||
|
@ -165,6 +210,22 @@ type Operation struct {
|
|||
Workspace string
|
||||
}
|
||||
|
||||
// HasConfig returns true if and only if the operation has a ConfigDir value
|
||||
// that refers to a directory containing at least one Terraform configuration
|
||||
// file.
|
||||
func (o *Operation) HasConfig() bool {
|
||||
return o.ConfigLoader.IsConfigDir(o.ConfigDir)
|
||||
}
|
||||
|
||||
// Config loads the configuration that the operation applies to, using the
|
||||
// ConfigDir and ConfigLoader fields within the receiving operation.
|
||||
func (o *Operation) Config() (*configs.Config, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
config, hclDiags := o.ConfigLoader.LoadConfig(o.ConfigDir)
|
||||
diags = diags.Append(hclDiags)
|
||||
return config, diags
|
||||
}
|
||||
|
||||
// RunningOperation is the result of starting an operation.
|
||||
type RunningOperation struct {
|
||||
// For implementers of a backend, this context should not wrap the
|
||||
|
@ -184,9 +245,9 @@ type RunningOperation struct {
|
|||
// to avoid running operations during process exit.
|
||||
Cancel context.CancelFunc
|
||||
|
||||
// Err is the error of the operation. This is populated after
|
||||
// the operation has completed.
|
||||
Err error
|
||||
// Result is the exit status of the operation, populated only after the
|
||||
// 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.
|
||||
|
@ -201,3 +262,16 @@ type RunningOperation struct {
|
|||
// after the operation completes to avoid read/write races.
|
||||
State *terraform.State
|
||||
}
|
||||
|
||||
// OperationResult describes the result status of an operation.
|
||||
type OperationResult int
|
||||
|
||||
const (
|
||||
// OperationSuccess indicates that the operation completed as expected.
|
||||
OperationSuccess OperationResult = 0
|
||||
|
||||
// OperationFailure indicates that the operation encountered some sort
|
||||
// of error, and thus may have been only partially performed or not
|
||||
// performed at all.
|
||||
OperationFailure OperationResult = 1
|
||||
)
|
||||
|
|
|
@ -48,6 +48,10 @@ type CLIOpts struct {
|
|||
CLI cli.Ui
|
||||
CLIColor *colorstring.Colorize
|
||||
|
||||
// ShowDiagnostics is a function that will format and print diagnostic
|
||||
// messages to the UI.
|
||||
ShowDiagnostics func(vals ...interface{})
|
||||
|
||||
// StatePath is the local path where state is read from.
|
||||
//
|
||||
// StateOutPath is the local path where the state will be written.
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/backend/remote-state/inmem"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestDeprecateBackend(t *testing.T) {
|
||||
|
@ -12,21 +12,19 @@ func TestDeprecateBackend(t *testing.T) {
|
|||
deprecatedBackend := deprecateBackend(
|
||||
inmem.New(),
|
||||
deprecateMessage,
|
||||
)()
|
||||
)
|
||||
|
||||
warns, errs := deprecatedBackend.Validate(&terraform.ResourceConfig{})
|
||||
if errs != nil {
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
diags := deprecatedBackend.ValidateConfig(cty.EmptyObjectVal)
|
||||
if len(diags) != 1 {
|
||||
t.Errorf("got %d diagnostics; want 1", len(diags))
|
||||
for _, diag := range diags {
|
||||
t.Errorf("- %s", diag)
|
||||
}
|
||||
t.Fatal("validation errors")
|
||||
return
|
||||
}
|
||||
|
||||
if len(warns) != 1 {
|
||||
t.Fatalf("expected 1 warning, got %q", warns)
|
||||
}
|
||||
|
||||
if warns[0] != deprecateMessage {
|
||||
t.Fatalf("expected %q, got %q", deprecateMessage, warns[0])
|
||||
desc := diags[0].Description()
|
||||
if desc.Summary != deprecateMessage {
|
||||
t.Fatalf("wrong message %q; want %q", desc.Summary, deprecateMessage)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,17 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/command/clistate"
|
||||
"github.com/hashicorp/terraform/config/configschema"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/colorstring"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -37,6 +41,9 @@ type Local struct {
|
|||
CLI cli.Ui
|
||||
CLIColor *colorstring.Colorize
|
||||
|
||||
// ShowDiagnostics prints diagnostic messages to the UI.
|
||||
ShowDiagnostics func(vals ...interface{})
|
||||
|
||||
// The State* paths are set from the backend config, and may be left blank
|
||||
// to use the defaults. If the actual paths for the local backend state are
|
||||
// needed, use the StatePaths method.
|
||||
|
@ -89,106 +96,92 @@ type Local struct {
|
|||
// exact commands that are being run.
|
||||
RunningInAutomation bool
|
||||
|
||||
schema *schema.Backend
|
||||
opLock sync.Mutex
|
||||
}
|
||||
|
||||
// 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 {
|
||||
b := &Local{
|
||||
Backend: backend,
|
||||
func (b *Local) ConfigSchema() *configschema.Block {
|
||||
if b.Backend != nil {
|
||||
return b.Backend.ConfigSchema()
|
||||
}
|
||||
|
||||
b.schema = &schema.Backend{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
return &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"path": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
},
|
||||
|
||||
"workspace_dir": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
"workspace_dir": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
},
|
||||
|
||||
"environment_dir": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
ConflictsWith: []string{"workspace_dir"},
|
||||
Deprecated: "workspace_dir should be used instead, with the same meaning",
|
||||
},
|
||||
// environment_dir was previously a deprecated alias for
|
||||
// workspace_dir, but now removed.
|
||||
},
|
||||
|
||||
ConfigureFunc: b.configure,
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Local) configure(ctx context.Context) error {
|
||||
d := schema.FromContextBackendConfig(ctx)
|
||||
|
||||
// Set the path if it is set
|
||||
pathRaw, ok := d.GetOk("path")
|
||||
if ok {
|
||||
path := pathRaw.(string)
|
||||
if path == "" {
|
||||
return fmt.Errorf("configured path is empty")
|
||||
}
|
||||
|
||||
b.StatePath = path
|
||||
b.StateOutPath = path
|
||||
func (b *Local) ValidateConfig(obj cty.Value) tfdiags.Diagnostics {
|
||||
if b.Backend != nil {
|
||||
return b.Backend.ValidateConfig(obj)
|
||||
}
|
||||
|
||||
if raw, ok := d.GetOk("workspace_dir"); ok {
|
||||
path := raw.(string)
|
||||
if path != "" {
|
||||
b.StateWorkspaceDir = path
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
if val := obj.GetAttr("path"); !val.IsNull() {
|
||||
p := val.AsString()
|
||||
if p == "" {
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Invalid local state file path",
|
||||
`The "path" attribute value must not be empty.`,
|
||||
cty.Path{cty.GetAttrStep{Name: "path"}},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy name, which ConflictsWith workspace_dir
|
||||
if raw, ok := d.GetOk("environment_dir"); ok {
|
||||
path := raw.(string)
|
||||
if path != "" {
|
||||
b.StateWorkspaceDir = path
|
||||
if val := obj.GetAttr("workspace_dir"); !val.IsNull() {
|
||||
p := val.AsString()
|
||||
if p == "" {
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Invalid local workspace directory path",
|
||||
`The "workspace_dir" attribute value must not be empty.`,
|
||||
cty.Path{cty.GetAttrStep{Name: "workspace_dir"}},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return diags
|
||||
}
|
||||
|
||||
func (b *Local) Input(ui terraform.UIInput, c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
|
||||
f := b.schema.Input
|
||||
func (b *Local) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||
if b.Backend != nil {
|
||||
f = b.Backend.Input
|
||||
return b.Backend.Configure(obj)
|
||||
}
|
||||
return f(ui, c)
|
||||
}
|
||||
|
||||
func (b *Local) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||
f := b.schema.Validate
|
||||
if b.Backend != nil {
|
||||
f = b.Backend.Validate
|
||||
}
|
||||
return f(c)
|
||||
}
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
func (b *Local) Configure(c *terraform.ResourceConfig) error {
|
||||
f := b.schema.Configure
|
||||
if b.Backend != nil {
|
||||
f = b.Backend.Configure
|
||||
type Config struct {
|
||||
Path string `hcl:"path,optional"`
|
||||
WorkspaceDir string `hcl:"workspace_dir,optional"`
|
||||
}
|
||||
return f(c)
|
||||
|
||||
if val := obj.GetAttr("path"); !val.IsNull() {
|
||||
p := val.AsString()
|
||||
b.StatePath = p
|
||||
b.StateOutPath = p
|
||||
} else {
|
||||
b.StatePath = DefaultStateFilename
|
||||
b.StateOutPath = DefaultStateFilename
|
||||
}
|
||||
|
||||
if val := obj.GetAttr("workspace_dir"); !val.IsNull() {
|
||||
p := val.AsString()
|
||||
b.StateWorkspaceDir = p
|
||||
} else {
|
||||
b.StateWorkspaceDir = DefaultWorkspaceDir
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func (b *Local) State(name string) (state.State, error) {
|
||||
|
@ -339,7 +332,11 @@ func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.
|
|||
// the state was locked during context creation, unlock the state when
|
||||
// the operation completes
|
||||
defer func() {
|
||||
runningOp.Err = op.StateLocker.Unlock(runningOp.Err)
|
||||
err := op.StateLocker.Unlock(nil)
|
||||
if err != nil {
|
||||
b.ShowDiagnostics(err)
|
||||
}
|
||||
runningOp.Result = backend.OperationFailure
|
||||
}()
|
||||
|
||||
defer b.opLock.Unlock()
|
||||
|
@ -397,6 +394,28 @@ func (b *Local) opWait(
|
|||
return
|
||||
}
|
||||
|
||||
// ReportResult is a helper for the common chore of setting the status of
|
||||
// a running operation and showing any diagnostics produced during that
|
||||
// operation.
|
||||
//
|
||||
// If the given diagnostics contains errors then the operation's result
|
||||
// will be set to backend.OperationFailure. It will be set to
|
||||
// backend.OperationSuccess otherwise. It will then use b.ShowDiagnostics
|
||||
// to show the given diagnostics before returning.
|
||||
//
|
||||
// Callers should feel free to do each of these operations separately in
|
||||
// more complex cases where e.g. diagnostics are interleaved with other
|
||||
// output, but terminating immediately after reporting error diagnostics is
|
||||
// common and can be expressed concisely via this method.
|
||||
func (b *Local) ReportResult(op *backend.RunningOperation, diags tfdiags.Diagnostics) {
|
||||
if diags.HasErrors() {
|
||||
op.Result = backend.OperationFailure
|
||||
} else {
|
||||
op.Result = backend.OperationSuccess
|
||||
}
|
||||
b.ShowDiagnostics(diags)
|
||||
}
|
||||
|
||||
// Colorize returns the Colorize structure that can be used for colorizing
|
||||
// output. This is gauranteed to always return a non-nil value and so is useful
|
||||
// as a helper to wrap any potentially colored strings.
|
||||
|
@ -411,6 +430,39 @@ func (b *Local) Colorize() *colorstring.Colorize {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *Local) schemaConfigure(ctx context.Context) error {
|
||||
d := schema.FromContextBackendConfig(ctx)
|
||||
|
||||
// Set the path if it is set
|
||||
pathRaw, ok := d.GetOk("path")
|
||||
if ok {
|
||||
path := pathRaw.(string)
|
||||
if path == "" {
|
||||
return fmt.Errorf("configured path is empty")
|
||||
}
|
||||
|
||||
b.StatePath = path
|
||||
b.StateOutPath = path
|
||||
}
|
||||
|
||||
if raw, ok := d.GetOk("workspace_dir"); ok {
|
||||
path := raw.(string)
|
||||
if path != "" {
|
||||
b.StateWorkspaceDir = path
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy name, which ConflictsWith workspace_dir
|
||||
if raw, ok := d.GetOk("environment_dir"); ok {
|
||||
path := raw.(string)
|
||||
if path != "" {
|
||||
b.StateWorkspaceDir = path
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatePaths returns the StatePath, StateOutPath, and StateBackupPath as
|
||||
// configured from the CLI.
|
||||
func (b *Local) StatePaths(name string) (string, string, string) {
|
||||
|
|
|
@ -6,15 +6,13 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/command/format"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
func (b *Local) opApply(
|
||||
|
@ -24,17 +22,18 @@ func (b *Local) opApply(
|
|||
runningOp *backend.RunningOperation) {
|
||||
log.Printf("[INFO] backend/local: starting Apply operation")
|
||||
|
||||
// If we have a nil module at this point, then set it to an empty tree
|
||||
// to avoid any potential crashes.
|
||||
if op.Plan == nil && op.Module == nil && !op.Destroy {
|
||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(applyErrNoConfig))
|
||||
return
|
||||
}
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// If we have a nil module at this point, then set it to an empty tree
|
||||
// to avoid any potential crashes.
|
||||
if op.Module == nil {
|
||||
op.Module = module.NewEmptyTree()
|
||||
if op.Plan == 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.",
|
||||
))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
// Setup our count hook that keeps track of resource changes
|
||||
|
@ -50,7 +49,8 @@ func (b *Local) opApply(
|
|||
// Get our context
|
||||
tfCtx, opState, err := b.context(op)
|
||||
if err != nil {
|
||||
runningOp.Err = err
|
||||
diags = diags.Append(err)
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,9 @@ func (b *Local) opApply(
|
|||
log.Printf("[INFO] backend/local: apply calling Refresh")
|
||||
_, err := tfCtx.Refresh()
|
||||
if err != nil {
|
||||
runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err)
|
||||
diags = diags.Append(err)
|
||||
runningOp.Result = backend.OperationFailure
|
||||
b.ShowDiagnostics(diags)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +75,8 @@ func (b *Local) opApply(
|
|||
log.Printf("[INFO] backend/local: apply calling Plan")
|
||||
plan, err := tfCtx.Plan()
|
||||
if err != nil {
|
||||
runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err)
|
||||
diags = diags.Append(err)
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -113,15 +116,17 @@ func (b *Local) opApply(
|
|||
Description: desc,
|
||||
})
|
||||
if err != nil {
|
||||
runningOp.Err = errwrap.Wrapf("Error asking for approval: {{err}}", err)
|
||||
diags = diags.Append(errwrap.Wrapf("Error asking for approval: {{err}}", err))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
if v != "yes" {
|
||||
if op.Destroy {
|
||||
runningOp.Err = errors.New("Destroy cancelled.")
|
||||
b.CLI.Info("Destroy cancelled.")
|
||||
} else {
|
||||
runningOp.Err = errors.New("Apply cancelled.")
|
||||
b.CLI.Info("Apply cancelled.")
|
||||
}
|
||||
runningOp.Result = backend.OperationFailure
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -150,26 +155,31 @@ func (b *Local) opApply(
|
|||
|
||||
// Persist the state
|
||||
if err := opState.WriteState(applyState); err != nil {
|
||||
runningOp.Err = b.backupStateForError(applyState, err)
|
||||
diags = diags.Append(b.backupStateForError(applyState, err))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
if err := opState.PersistState(); err != nil {
|
||||
runningOp.Err = b.backupStateForError(applyState, err)
|
||||
diags = diags.Append(b.backupStateForError(applyState, err))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
if applyErr != nil {
|
||||
runningOp.Err = fmt.Errorf(
|
||||
"Error applying plan:\n\n"+
|
||||
"%s\n\n"+
|
||||
"Terraform does not automatically rollback in the face of errors.\n"+
|
||||
"Instead, your Terraform state file has been partially updated with\n"+
|
||||
"any resources that successfully completed. Please address the error\n"+
|
||||
"above and apply again to incrementally change your infrastructure.",
|
||||
multierror.Flatten(applyErr))
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
applyErr.Error(),
|
||||
"Terraform does not automatically rollback in the face of errors. Instead, your Terraform state file has been partially updated with any resources that successfully completed. Please address the error above and apply again to incrementally change your infrastructure.",
|
||||
))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
// If we've accumulated any warnings along the way then we'll show them
|
||||
// here just before we show the summary and next steps. If we encountered
|
||||
// errors then we would've returned early at some other point above.
|
||||
b.ShowDiagnostics(diags)
|
||||
|
||||
// If we have a UI, output the results
|
||||
if b.CLI != nil {
|
||||
if op.Destroy {
|
||||
|
@ -236,15 +246,6 @@ func (b *Local) backupStateForError(applyState *terraform.State, err error) erro
|
|||
return errors.New(stateWriteBackedUpError)
|
||||
}
|
||||
|
||||
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 stateWriteBackedUpError = `Failed to persist state to backend.
|
||||
|
||||
The error shown above has prevented Terraform from writing the updated state
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/configs/configload"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
|
@ -24,19 +24,16 @@ func TestLocal_applyBasic(t *testing.T) {
|
|||
|
||||
p.ApplyReturn = &terraform.InstanceState{ID: "yes"}
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
|
||||
defer modCleanup()
|
||||
|
||||
op := testOperationApply()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationApply(t, "./test-fixtures/apply")
|
||||
defer configCleanup()
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatal("operation failed")
|
||||
}
|
||||
|
||||
if p.RefreshCalled {
|
||||
|
@ -66,16 +63,16 @@ func TestLocal_applyEmptyDir(t *testing.T) {
|
|||
|
||||
p.ApplyReturn = &terraform.InstanceState{ID: "yes"}
|
||||
|
||||
op := testOperationApply()
|
||||
op.Module = nil
|
||||
op, configCleanup := testOperationApply(t, "./test-fixtures/empty")
|
||||
defer configCleanup()
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err == nil {
|
||||
t.Fatal("should error")
|
||||
if run.Result == backend.OperationSuccess {
|
||||
t.Fatal("operation succeeded; want error")
|
||||
}
|
||||
|
||||
if p.ApplyCalled {
|
||||
|
@ -94,8 +91,8 @@ func TestLocal_applyEmptyDirDestroy(t *testing.T) {
|
|||
|
||||
p.ApplyReturn = nil
|
||||
|
||||
op := testOperationApply()
|
||||
op.Module = nil
|
||||
op, configCleanup := testOperationApply(t, "./test-fixtures/empty")
|
||||
defer configCleanup()
|
||||
op.Destroy = true
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
|
@ -103,8 +100,8 @@ func TestLocal_applyEmptyDirDestroy(t *testing.T) {
|
|||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatalf("apply operation failed")
|
||||
}
|
||||
|
||||
if p.ApplyCalled {
|
||||
|
@ -148,19 +145,16 @@ func TestLocal_applyError(t *testing.T) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-error")
|
||||
defer modCleanup()
|
||||
|
||||
op := testOperationApply()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationApply(t, "./test-fixtures/apply-error")
|
||||
defer configCleanup()
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err == nil {
|
||||
t.Fatal("should error")
|
||||
if run.Result == backend.OperationSuccess {
|
||||
t.Fatal("operation succeeded; want failure")
|
||||
}
|
||||
|
||||
checkState(t, b.StateOutPath, `
|
||||
|
@ -171,8 +165,8 @@ test_instance.foo:
|
|||
}
|
||||
|
||||
func TestLocal_applyBackendFail(t *testing.T) {
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
|
||||
defer modCleanup()
|
||||
op, configCleanup := testOperationApply(t, "./test-fixtures/apply")
|
||||
defer configCleanup()
|
||||
|
||||
b, cleanup := TestLocal(t)
|
||||
defer cleanup()
|
||||
|
@ -193,23 +187,15 @@ func TestLocal_applyBackendFail(t *testing.T) {
|
|||
|
||||
p.ApplyReturn = &terraform.InstanceState{ID: "yes"}
|
||||
|
||||
op := testOperationApply()
|
||||
op.Module = mod
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err == nil {
|
||||
if run.Result == backend.OperationSuccess {
|
||||
t.Fatalf("apply succeeded; want error")
|
||||
}
|
||||
|
||||
errStr := run.Err.Error()
|
||||
if !strings.Contains(errStr, "terraform state push errored.tfstate") {
|
||||
t.Fatalf("wrong error message:\n%s", errStr)
|
||||
}
|
||||
|
||||
msgStr := b.CLI.(*cli.MockUi).ErrorWriter.String()
|
||||
if !strings.Contains(msgStr, "Failed to save state: fake failure") {
|
||||
t.Fatalf("missing original error message in output:\n%s", msgStr)
|
||||
|
@ -244,10 +230,16 @@ func (s failingState) WriteState(state *terraform.State) error {
|
|||
return errors.New("fake failure")
|
||||
}
|
||||
|
||||
func testOperationApply() *backend.Operation {
|
||||
func testOperationApply(t *testing.T, configDir string) (*backend.Operation, func()) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := configload.MustLoadConfigForTests(t, configDir)
|
||||
|
||||
return &backend.Operation{
|
||||
Type: backend.OperationTypeApply,
|
||||
}
|
||||
Type: backend.OperationTypeApply,
|
||||
ConfigDir: configDir,
|
||||
ConfigLoader: configLoader,
|
||||
}, configCleanup
|
||||
}
|
||||
|
||||
// testApplyState is just a common state that we use for testing refresh.
|
||||
|
|
|
@ -3,19 +3,18 @@ package local
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/command/clistate"
|
||||
"github.com/hashicorp/terraform/command/format"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
// backend.Local implementation.
|
||||
func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State, error) {
|
||||
func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State, tfdiags.Diagnostics) {
|
||||
// Make sure the type is invalid. We use this as a way to know not
|
||||
// to ask for input/validate.
|
||||
op.Type = backend.OperationTypeInvalid
|
||||
|
@ -29,19 +28,24 @@ func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State,
|
|||
return b.context(op)
|
||||
}
|
||||
|
||||
func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State, error) {
|
||||
func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// Get the state.
|
||||
s, err := b.State(op.Workspace)
|
||||
if err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
|
||||
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
if err := op.StateLocker.Lock(s, op.Type.String()); err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error locking state: {{err}}", err)
|
||||
diags = diags.Append(errwrap.Wrapf("Error locking state: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
if err := s.RefreshState(); err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
|
||||
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
// Initialize our context options
|
||||
|
@ -52,13 +56,18 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
|
|||
|
||||
// Copy set options from the operation
|
||||
opts.Destroy = op.Destroy
|
||||
opts.Module = op.Module
|
||||
opts.Targets = op.Targets
|
||||
opts.UIInput = op.UIIn
|
||||
if op.Variables != nil {
|
||||
opts.Variables = op.Variables
|
||||
}
|
||||
|
||||
// FIXME: Configuration is temporarily stubbed out here to artificially
|
||||
// create a stopping point in our work to switch to the new config loader.
|
||||
// This means no backend-provided Terraform operations will actually work.
|
||||
// This will be addressed in a subsequent commit.
|
||||
opts.Module = nil
|
||||
|
||||
// Load our state
|
||||
// By the time we get here, the backend creation code in "command" took
|
||||
// care of making s.State() return a state compatible with our plan,
|
||||
|
@ -79,11 +88,13 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
|
|||
b.pluginInitRequired(rpe)
|
||||
// we wrote the full UI error here, so return a generic error for flow
|
||||
// control in the command.
|
||||
return nil, nil, errors.New("error satisfying plugin requirements")
|
||||
diags = diags.Append(errors.New("Can't satisfy plugin requirements"))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
diags = diags.Append(err)
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
// If we have an operation, then we automatically do the input/validate
|
||||
|
@ -96,45 +107,19 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
|
|||
mode |= terraform.InputModeVarUnset
|
||||
|
||||
if err := tfCtx.Input(mode); err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error asking for user input: {{err}}", err)
|
||||
diags = diags.Append(errwrap.Wrapf("Error asking for user input: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
}
|
||||
}
|
||||
|
||||
// If validation is enabled, validate
|
||||
if b.OpValidation {
|
||||
diags := tfCtx.Validate()
|
||||
if len(diags) > 0 {
|
||||
if diags.HasErrors() {
|
||||
// If there are warnings _and_ errors then we'll take this
|
||||
// path and return them all together in this error.
|
||||
return nil, nil, diags.Err()
|
||||
}
|
||||
|
||||
// For now we can't propagate warnings any further without
|
||||
// printing them directly to the UI, so we'll need to
|
||||
// format them here ourselves.
|
||||
for _, diag := range diags {
|
||||
if diag.Severity() != tfdiags.Warning {
|
||||
continue
|
||||
}
|
||||
if b.CLI != nil {
|
||||
// FIXME: We don't have access to the source code cache
|
||||
// in here, so we can't produce source code snippets
|
||||
// from this codepath.
|
||||
b.CLI.Warn(format.Diagnostic(diag, nil, b.Colorize(), 72))
|
||||
} else {
|
||||
desc := diag.Description()
|
||||
log.Printf("[WARN] backend/local: %s", desc.Summary)
|
||||
}
|
||||
}
|
||||
|
||||
// Make a newline before continuing
|
||||
b.CLI.Output("")
|
||||
}
|
||||
validateDiags := tfCtx.Validate()
|
||||
diags = diags.Append(validateDiags)
|
||||
}
|
||||
}
|
||||
|
||||
return tfCtx, s, nil
|
||||
return tfCtx, s, diags
|
||||
}
|
||||
|
||||
const validateWarnHeader = `
|
||||
|
|
|
@ -8,10 +8,10 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/command/format"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -20,27 +20,30 @@ func (b *Local) opPlan(
|
|||
cancelCtx context.Context,
|
||||
op *backend.Operation,
|
||||
runningOp *backend.RunningOperation) {
|
||||
|
||||
log.Printf("[INFO] backend/local: starting Plan operation")
|
||||
|
||||
if b.CLI != nil && op.Plan != nil {
|
||||
b.CLI.Output(b.Colorize().Color(
|
||||
"[reset][bold][yellow]" +
|
||||
"The plan command received a saved plan file as input. This command\n" +
|
||||
"will output the saved plan. This will not modify the already-existing\n" +
|
||||
"plan. If you wish to generate a new plan, please pass in a configuration\n" +
|
||||
"directory as an argument.\n\n"))
|
||||
}
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// A local plan requires either a plan or a module
|
||||
if op.Plan == nil && op.Module == nil && !op.Destroy {
|
||||
runningOp.Err = fmt.Errorf(strings.TrimSpace(planErrNoConfig))
|
||||
if b.CLI != nil && op.Plan != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Can't re-plan a saved plan",
|
||||
"The plan command was given a saved plan file as its input. This command generates a new plan, and so it requires a configuration directory as its argument.",
|
||||
))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a nil module at this point, then set it to an empty tree
|
||||
// to avoid any potential crashes.
|
||||
if op.Module == nil {
|
||||
op.Module = module.NewEmptyTree()
|
||||
// Local planning requires a config, unless we're planning to destroy.
|
||||
if !op.Destroy && !op.HasConfig() {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"No configuration files",
|
||||
"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, run plan with the -destroy option. Otherwise, create a Terraform configuration file (.tf file) and try again.",
|
||||
))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
// Setup our count hook that keeps track of resource changes
|
||||
|
@ -55,7 +58,8 @@ func (b *Local) opPlan(
|
|||
// Get our context
|
||||
tfCtx, opState, err := b.context(op)
|
||||
if err != nil {
|
||||
runningOp.Err = err
|
||||
diags = diags.Append(err)
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -72,7 +76,8 @@ func (b *Local) opPlan(
|
|||
|
||||
_, err := tfCtx.Refresh()
|
||||
if err != nil {
|
||||
runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err)
|
||||
diags = diags.Append(err)
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
if b.CLI != nil {
|
||||
|
@ -95,7 +100,8 @@ func (b *Local) opPlan(
|
|||
}
|
||||
|
||||
if planErr != nil {
|
||||
runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", planErr)
|
||||
diags = diags.Append(planErr)
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
// Record state
|
||||
|
@ -119,7 +125,12 @@ func (b *Local) opPlan(
|
|||
}
|
||||
f.Close()
|
||||
if err != nil {
|
||||
runningOp.Err = fmt.Errorf("Error writing plan file: %s", err)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to write plan file",
|
||||
fmt.Sprintf("The plan file could not be written: %s.", err),
|
||||
))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +145,11 @@ func (b *Local) opPlan(
|
|||
|
||||
b.renderPlan(dispPlan)
|
||||
|
||||
// If we've accumulated any warnings along the way then we'll show them
|
||||
// here just before we show the summary and next steps. If we encountered
|
||||
// errors then we would've returned early at some other point above.
|
||||
b.ShowDiagnostics(diags)
|
||||
|
||||
// Give the user some next-steps, unless we're running in an automation
|
||||
// tool which is presumed to provide its own UI for further actions.
|
||||
if !b.RunningInAutomation {
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/configs/configload"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -19,11 +19,8 @@ func TestLocal_planBasic(t *testing.T) {
|
|||
defer cleanup()
|
||||
p := TestLocalProvider(t, b, "test")
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
|
||||
defer modCleanup()
|
||||
|
||||
op := testOperationPlan()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
|
||||
defer configCleanup()
|
||||
op.PlanRefresh = true
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
|
@ -31,8 +28,8 @@ func TestLocal_planBasic(t *testing.T) {
|
|||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatalf("plan operation failed")
|
||||
}
|
||||
|
||||
if !p.DiffCalled {
|
||||
|
@ -45,9 +42,6 @@ func TestLocal_planInAutomation(t *testing.T) {
|
|||
defer cleanup()
|
||||
TestLocalProvider(t, b, "test")
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
|
||||
defer modCleanup()
|
||||
|
||||
const msg = `You didn't specify an "-out" parameter`
|
||||
|
||||
// When we're "in automation" we omit certain text from the
|
||||
|
@ -59,8 +53,8 @@ func TestLocal_planInAutomation(t *testing.T) {
|
|||
b.RunningInAutomation = false
|
||||
b.CLI = cli.NewMockUi()
|
||||
{
|
||||
op := testOperationPlan()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
|
||||
defer configCleanup()
|
||||
op.PlanRefresh = true
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
|
@ -68,8 +62,8 @@ func TestLocal_planInAutomation(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatalf("plan operation failed")
|
||||
}
|
||||
|
||||
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||
|
@ -83,8 +77,8 @@ func TestLocal_planInAutomation(t *testing.T) {
|
|||
b.RunningInAutomation = true
|
||||
b.CLI = cli.NewMockUi()
|
||||
{
|
||||
op := testOperationPlan()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
|
||||
defer configCleanup()
|
||||
op.PlanRefresh = true
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
|
@ -92,8 +86,8 @@ func TestLocal_planInAutomation(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatalf("plan operation failed")
|
||||
}
|
||||
|
||||
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||
|
@ -109,8 +103,10 @@ func TestLocal_planNoConfig(t *testing.T) {
|
|||
defer cleanup()
|
||||
TestLocalProvider(t, b, "test")
|
||||
|
||||
op := testOperationPlan()
|
||||
op.Module = nil
|
||||
b.CLI = cli.NewMockUi()
|
||||
|
||||
op, configCleanup := testOperationPlan(t, "./test-fixtures/empty")
|
||||
defer configCleanup()
|
||||
op.PlanRefresh = true
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
|
@ -119,11 +115,11 @@ func TestLocal_planNoConfig(t *testing.T) {
|
|||
}
|
||||
<-run.Done()
|
||||
|
||||
err = run.Err
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
if run.Result == backend.OperationSuccess {
|
||||
t.Fatal("plan operation succeeded; want failure")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "configuration") {
|
||||
output := b.CLI.(*cli.MockUi).ErrorWriter.String()
|
||||
if !strings.Contains(output, "configuration") {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -134,19 +130,16 @@ func TestLocal_planRefreshFalse(t *testing.T) {
|
|||
p := TestLocalProvider(t, b, "test")
|
||||
terraform.TestStateFile(t, b.StatePath, testPlanState())
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
|
||||
defer modCleanup()
|
||||
|
||||
op := testOperationPlan()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationPlan(t, "./test-fixtures/empty")
|
||||
defer configCleanup()
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatalf("plan operation failed")
|
||||
}
|
||||
|
||||
if p.RefreshCalled {
|
||||
|
@ -164,17 +157,14 @@ func TestLocal_planDestroy(t *testing.T) {
|
|||
p := TestLocalProvider(t, b, "test")
|
||||
terraform.TestStateFile(t, b.StatePath, testPlanState())
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
|
||||
defer modCleanup()
|
||||
|
||||
outDir := testTempDir(t)
|
||||
defer os.RemoveAll(outDir)
|
||||
planPath := filepath.Join(outDir, "plan.tfplan")
|
||||
|
||||
op := testOperationPlan()
|
||||
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
|
||||
defer configCleanup()
|
||||
op.Destroy = true
|
||||
op.PlanRefresh = true
|
||||
op.Module = mod
|
||||
op.PlanOutPath = planPath
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
|
@ -182,8 +172,8 @@ func TestLocal_planDestroy(t *testing.T) {
|
|||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatalf("plan operation failed")
|
||||
}
|
||||
|
||||
if !p.RefreshCalled {
|
||||
|
@ -210,15 +200,12 @@ func TestLocal_planOutPathNoChange(t *testing.T) {
|
|||
TestLocalProvider(t, b, "test")
|
||||
terraform.TestStateFile(t, b.StatePath, testPlanState())
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
|
||||
defer modCleanup()
|
||||
|
||||
outDir := testTempDir(t)
|
||||
defer os.RemoveAll(outDir)
|
||||
planPath := filepath.Join(outDir, "plan.tfplan")
|
||||
|
||||
op := testOperationPlan()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
|
||||
defer configCleanup()
|
||||
op.PlanOutPath = planPath
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
|
@ -226,8 +213,8 @@ func TestLocal_planOutPathNoChange(t *testing.T) {
|
|||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatalf("plan operation failed")
|
||||
}
|
||||
|
||||
plan := testReadPlan(t, planPath)
|
||||
|
@ -273,14 +260,11 @@ func TestLocal_planScaleOutNoDupeCount(t *testing.T) {
|
|||
actual := new(CountHook)
|
||||
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, actual)
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-scaleout")
|
||||
defer modCleanup()
|
||||
|
||||
outDir := testTempDir(t)
|
||||
defer os.RemoveAll(outDir)
|
||||
|
||||
op := testOperationPlan()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan-scaleout")
|
||||
defer configCleanup()
|
||||
op.PlanRefresh = true
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
|
@ -288,8 +272,8 @@ func TestLocal_planScaleOutNoDupeCount(t *testing.T) {
|
|||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
<-run.Done()
|
||||
if run.Err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatalf("plan operation failed")
|
||||
}
|
||||
|
||||
expected := new(CountHook)
|
||||
|
@ -304,10 +288,16 @@ func TestLocal_planScaleOutNoDupeCount(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testOperationPlan() *backend.Operation {
|
||||
func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func()) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := configload.MustLoadConfigForTests(t, configDir)
|
||||
|
||||
return &backend.Operation{
|
||||
Type: backend.OperationTypePlan,
|
||||
}
|
||||
Type: backend.OperationTypePlan,
|
||||
ConfigDir: configDir,
|
||||
ConfigLoader: configLoader,
|
||||
}, configCleanup
|
||||
}
|
||||
|
||||
// testPlanState is just a common state that we use for testing refresh.
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
func (b *Local) opRefresh(
|
||||
|
@ -18,6 +18,9 @@ func (b *Local) opRefresh(
|
|||
cancelCtx context.Context,
|
||||
op *backend.Operation,
|
||||
runningOp *backend.RunningOperation) {
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// Check if our state exists if we're performing a refresh operation. We
|
||||
// only do this if we're managing state with this backend.
|
||||
if b.Backend == nil {
|
||||
|
@ -27,26 +30,22 @@ func (b *Local) opRefresh(
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
runningOp.Err = fmt.Errorf(
|
||||
"There was an error reading the Terraform state that is needed\n"+
|
||||
"for refreshing. The path and error are shown below.\n\n"+
|
||||
"Path: %s\n\nError: %s",
|
||||
b.StatePath, err)
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Cannot read state file",
|
||||
fmt.Sprintf("Failed to read %s: %s", b.StatePath, err),
|
||||
))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have no config module given to use, create an empty tree to
|
||||
// avoid crashes when Terraform.Context is initialized.
|
||||
if op.Module == nil {
|
||||
op.Module = module.NewEmptyTree()
|
||||
}
|
||||
|
||||
// Get our context
|
||||
tfCtx, opState, err := b.context(op)
|
||||
if err != nil {
|
||||
runningOp.Err = err
|
||||
tfCtx, opState, contextDiags := b.context(op)
|
||||
diags = diags.Append(contextDiags)
|
||||
if contextDiags.HasErrors() {
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -76,17 +75,20 @@ func (b *Local) opRefresh(
|
|||
// write the resulting state to the running op
|
||||
runningOp.State = newState
|
||||
if refreshErr != nil {
|
||||
runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", refreshErr)
|
||||
diags = diags.Append(refreshErr)
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
// Write and persist the state
|
||||
if err := opState.WriteState(newState); err != nil {
|
||||
runningOp.Err = errwrap.Wrapf("Error writing state: {{err}}", err)
|
||||
diags = diags.Append(errwrap.Wrapf("Failed to write state: {{err}}", err))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
if err := opState.PersistState(); err != nil {
|
||||
runningOp.Err = errwrap.Wrapf("Error saving state: {{err}}", err)
|
||||
diags = diags.Append(errwrap.Wrapf("Failed to save state: {{err}}", err))
|
||||
b.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/configs/configload"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -20,11 +20,8 @@ func TestLocal_refresh(t *testing.T) {
|
|||
p.RefreshFn = nil
|
||||
p.RefreshReturn = &terraform.InstanceState{ID: "yes"}
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/refresh")
|
||||
defer modCleanup()
|
||||
|
||||
op := testOperationRefresh()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationRefresh(t, "./test-fixtures/refresh")
|
||||
defer configCleanup()
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
|
@ -43,7 +40,7 @@ test_instance.foo:
|
|||
`)
|
||||
}
|
||||
|
||||
func TestLocal_refreshNilModule(t *testing.T) {
|
||||
func TestLocal_refreshNoConfig(t *testing.T) {
|
||||
b, cleanup := TestLocal(t)
|
||||
defer cleanup()
|
||||
p := TestLocalProvider(t, b, "test")
|
||||
|
@ -52,8 +49,8 @@ func TestLocal_refreshNilModule(t *testing.T) {
|
|||
p.RefreshFn = nil
|
||||
p.RefreshReturn = &terraform.InstanceState{ID: "yes"}
|
||||
|
||||
op := testOperationRefresh()
|
||||
op.Module = nil
|
||||
op, configCleanup := testOperationRefresh(t, "./test-fixtures/empty")
|
||||
defer configCleanup()
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
|
@ -84,8 +81,8 @@ func TestLocal_refreshNilModuleWithInput(t *testing.T) {
|
|||
|
||||
b.OpInput = true
|
||||
|
||||
op := testOperationRefresh()
|
||||
op.Module = nil
|
||||
op, configCleanup := testOperationRefresh(t, "./test-fixtures/empty")
|
||||
defer configCleanup()
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
|
@ -121,15 +118,12 @@ func TestLocal_refreshInput(t *testing.T) {
|
|||
p.RefreshFn = nil
|
||||
p.RefreshReturn = &terraform.InstanceState{ID: "yes"}
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/refresh-var-unset")
|
||||
defer modCleanup()
|
||||
|
||||
// Enable input asking since it is normally disabled by default
|
||||
b.OpInput = true
|
||||
b.ContextOpts.UIInput = &terraform.MockUIInput{InputReturnString: "bar"}
|
||||
|
||||
op := testOperationRefresh()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationRefresh(t, "./test-fixtures/refresh-var-unset")
|
||||
defer configCleanup()
|
||||
op.UIIn = b.ContextOpts.UIInput
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
|
@ -158,14 +152,11 @@ func TestLocal_refreshValidate(t *testing.T) {
|
|||
p.RefreshFn = nil
|
||||
p.RefreshReturn = &terraform.InstanceState{ID: "yes"}
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/refresh")
|
||||
defer modCleanup()
|
||||
|
||||
// Enable validation
|
||||
b.OpValidation = true
|
||||
|
||||
op := testOperationRefresh()
|
||||
op.Module = mod
|
||||
op, configCleanup := testOperationRefresh(t, "./test-fixtures/refresh")
|
||||
defer configCleanup()
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
|
@ -184,10 +175,16 @@ test_instance.foo:
|
|||
`)
|
||||
}
|
||||
|
||||
func testOperationRefresh() *backend.Operation {
|
||||
func testOperationRefresh(t *testing.T, configDir string) (*backend.Operation, func()) {
|
||||
t.Helper()
|
||||
|
||||
_, configLoader, configCleanup := configload.MustLoadConfigForTests(t, configDir)
|
||||
|
||||
return &backend.Operation{
|
||||
Type: backend.OperationTypeRefresh,
|
||||
}
|
||||
Type: backend.OperationTypeRefresh,
|
||||
ConfigDir: configDir,
|
||||
ConfigLoader: configLoader,
|
||||
}, configCleanup
|
||||
}
|
||||
|
||||
// testRefreshState is just a common state that we use for testing refresh.
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
func (b *Local) CLIInit(opts *backend.CLIOpts) error {
|
||||
b.CLI = opts.CLI
|
||||
b.CLIColor = opts.CLIColor
|
||||
b.ShowDiagnostics = opts.ShowDiagnostics
|
||||
b.ContextOpts = opts.ContextOpts
|
||||
b.OpInput = opts.Input
|
||||
b.OpValidation = opts.Validation
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
// TestLocal returns a configured Local struct with temporary paths and
|
||||
|
@ -17,15 +18,27 @@ import (
|
|||
// No operations will be called on the returned value, so you can still set
|
||||
// public fields without any locks.
|
||||
func TestLocal(t *testing.T) (*Local, func()) {
|
||||
t.Helper()
|
||||
|
||||
tempDir := testTempDir(t)
|
||||
|
||||
local := New()
|
||||
local.StatePath = filepath.Join(tempDir, "state.tfstate")
|
||||
local.StateOutPath = filepath.Join(tempDir, "state.tfstate")
|
||||
local.StateBackupPath = filepath.Join(tempDir, "state.tfstate.bak")
|
||||
local.StateWorkspaceDir = filepath.Join(tempDir, "state.tfstate.d")
|
||||
local.ContextOpts = &terraform.ContextOpts{}
|
||||
|
||||
var local *Local
|
||||
local = &Local{
|
||||
StatePath: filepath.Join(tempDir, "state.tfstate"),
|
||||
StateOutPath: filepath.Join(tempDir, "state.tfstate"),
|
||||
StateBackupPath: filepath.Join(tempDir, "state.tfstate.bak"),
|
||||
StateWorkspaceDir: filepath.Join(tempDir, "state.tfstate.d"),
|
||||
ContextOpts: &terraform.ContextOpts{},
|
||||
ShowDiagnostics: func(vals ...interface{}) {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(vals...)
|
||||
for _, diag := range diags {
|
||||
t.Log(diag.Description().Summary)
|
||||
if local.CLI != nil {
|
||||
local.CLI.Error(diag.Description().Summary)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
cleanup := func() {
|
||||
if err := os.RemoveAll(tempDir); err != nil {
|
||||
t.Fatal("error clecanup up test:", err)
|
||||
|
@ -70,7 +83,7 @@ func TestLocalProvider(t *testing.T, b *Local, name string) *terraform.MockResou
|
|||
// TestNewLocalSingle is a factory for creating a TestLocalSingleState.
|
||||
// This function matches the signature required for backend/init.
|
||||
func TestNewLocalSingle() backend.Backend {
|
||||
return &TestLocalSingleState{Local: New()}
|
||||
return &TestLocalSingleState{Local: &Local{}}
|
||||
}
|
||||
|
||||
// TestLocalSingleState is a backend implementation that wraps Local
|
||||
|
@ -102,7 +115,7 @@ func (b *TestLocalSingleState) DeleteState(string) error {
|
|||
// TestNewLocalNoDefault is a factory for creating a TestLocalNoDefaultState.
|
||||
// This function matches the signature required for backend/init.
|
||||
func TestNewLocalNoDefault() backend.Backend {
|
||||
return &TestLocalNoDefaultState{Local: New()}
|
||||
return &TestLocalNoDefaultState{Local: &Local{}}
|
||||
}
|
||||
|
||||
// TestLocalNoDefaultState is a backend implementation that wraps
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package backend
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/config/configschema"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// Nil is a no-op implementation of Backend.
|
||||
|
@ -11,17 +13,15 @@ import (
|
|||
// backend interface for testing.
|
||||
type Nil struct{}
|
||||
|
||||
func (Nil) Input(
|
||||
ui terraform.UIInput,
|
||||
c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
|
||||
return c, nil
|
||||
func (Nil) ConfigSchema() *configschema.Block {
|
||||
return &configschema.Block{}
|
||||
}
|
||||
|
||||
func (Nil) Validate(*terraform.ResourceConfig) ([]string, []error) {
|
||||
return nil, nil
|
||||
func (Nil) ValidateConfig(cty.Value) tfdiags.Diagnostics {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Nil) Configure(*terraform.ResourceConfig) error {
|
||||
func (Nil) Configure(cty.Value) tfdiags.Diagnostics {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestArtifactoryClient_impl(t *testing.T) {
|
||||
|
@ -15,18 +17,18 @@ func TestArtifactoryFactory(t *testing.T) {
|
|||
// This test just instantiates the client. Shouldn't make any actual
|
||||
// requests nor incur any costs.
|
||||
|
||||
config := make(map[string]interface{})
|
||||
config["url"] = "http://artifactory.local:8081/artifactory"
|
||||
config["repo"] = "terraform-repo"
|
||||
config["subpath"] = "myproject"
|
||||
config := make(map[string]cty.Value)
|
||||
config["url"] = cty.StringVal("http://artifactory.local:8081/artifactory")
|
||||
config["repo"] = cty.StringVal("terraform-repo")
|
||||
config["subpath"] = cty.StringVal("myproject")
|
||||
|
||||
// For this test we'll provide the credentials as config. The
|
||||
// acceptance tests implicitly test passing credentials as
|
||||
// environment variables.
|
||||
config["username"] = "test"
|
||||
config["password"] = "testpass"
|
||||
config["username"] = cty.StringVal("test")
|
||||
config["password"] = cty.StringVal("testpass")
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), config)
|
||||
b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", config))
|
||||
|
||||
state, err := b.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
|
|
|
@ -40,7 +40,7 @@ func TestBackendConfig(t *testing.T) {
|
|||
"access_key": "QUNDRVNTX0tFWQ0K",
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), config).(*Backend)
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
|
||||
|
||||
if b.containerName != "tfcontainer" {
|
||||
t.Fatalf("Incorrect bucketName was populated")
|
||||
|
@ -57,12 +57,12 @@ func TestBackend(t *testing.T) {
|
|||
res := setupResources(t, keyName)
|
||||
defer destroyResources(t, res.resourceGroupName)
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.containerName,
|
||||
"key": keyName,
|
||||
"access_key": res.accessKey,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
@ -74,19 +74,19 @@ func TestBackendLocked(t *testing.T) {
|
|||
res := setupResources(t, keyName)
|
||||
defer destroyResources(t, res.resourceGroupName)
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.containerName,
|
||||
"key": keyName,
|
||||
"access_key": res.accessKey,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.containerName,
|
||||
"key": keyName,
|
||||
"access_key": res.accessKey,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
backend.TestBackendStateLocks(t, b1, b2)
|
||||
backend.TestBackendStateForceUnlock(t, b1, b2)
|
||||
|
|
|
@ -21,12 +21,12 @@ func TestRemoteClient(t *testing.T) {
|
|||
res := setupResources(t, keyName)
|
||||
defer destroyResources(t, res.resourceGroupName)
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.containerName,
|
||||
"key": keyName,
|
||||
"access_key": res.accessKey,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
state, err := b.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
|
@ -43,19 +43,19 @@ func TestRemoteClientLocks(t *testing.T) {
|
|||
res := setupResources(t, keyName)
|
||||
defer destroyResources(t, res.resourceGroupName)
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.containerName,
|
||||
"key": keyName,
|
||||
"access_key": res.accessKey,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.containerName,
|
||||
"key": keyName,
|
||||
"access_key": res.accessKey,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
s1, err := b1.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
|
|
|
@ -10,7 +10,8 @@ import (
|
|||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// Backend implements backend.Backend for remote state backends.
|
||||
|
@ -30,7 +31,8 @@ type Backend struct {
|
|||
client remote.Client
|
||||
}
|
||||
|
||||
func (b *Backend) Configure(rc *terraform.ResourceConfig) error {
|
||||
func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||
|
||||
// Set our configureFunc manually
|
||||
b.Backend.ConfigureFunc = func(ctx context.Context) error {
|
||||
c, err := b.ConfigureFunc(ctx)
|
||||
|
@ -43,8 +45,7 @@ func (b *Backend) Configure(rc *terraform.ResourceConfig) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Run the normal configuration
|
||||
return b.Backend.Configure(rc)
|
||||
return b.Backend.Configure(obj)
|
||||
}
|
||||
|
||||
func (b *Backend) States() ([]string, error) {
|
||||
|
|
|
@ -52,15 +52,15 @@ func TestBackend(t *testing.T) {
|
|||
path := fmt.Sprintf("tf-unit/%s", time.Now().String())
|
||||
|
||||
// Get the backend. We need two to test locking.
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": path,
|
||||
})
|
||||
}))
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": path,
|
||||
})
|
||||
}))
|
||||
|
||||
// Test
|
||||
backend.TestBackendStates(t, b1)
|
||||
|
@ -71,17 +71,17 @@ func TestBackend_lockDisabled(t *testing.T) {
|
|||
path := fmt.Sprintf("tf-unit/%s", time.Now().String())
|
||||
|
||||
// Get the backend. We need two to test locking.
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": path,
|
||||
"lock": false,
|
||||
})
|
||||
}))
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": path + "different", // Diff so locking test would fail if it was locking
|
||||
"lock": false,
|
||||
})
|
||||
}))
|
||||
|
||||
// Test
|
||||
backend.TestBackendStates(t, b1)
|
||||
|
@ -90,11 +90,11 @@ func TestBackend_lockDisabled(t *testing.T) {
|
|||
|
||||
func TestBackend_gzip(t *testing.T) {
|
||||
// Get the backend
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": fmt.Sprintf("tf-unit/%s", time.Now().String()),
|
||||
"gzip": true,
|
||||
})
|
||||
}))
|
||||
|
||||
// Test
|
||||
backend.TestBackendStates(t, b)
|
||||
|
|
|
@ -20,10 +20,10 @@ func TestRemoteClient_impl(t *testing.T) {
|
|||
|
||||
func TestRemoteClient(t *testing.T) {
|
||||
// Get the backend
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": fmt.Sprintf("tf-unit/%s", time.Now().String()),
|
||||
})
|
||||
}))
|
||||
|
||||
// Grab the client
|
||||
state, err := b.State(backend.DefaultStateName)
|
||||
|
@ -40,10 +40,10 @@ func TestRemoteClient_gzipUpgrade(t *testing.T) {
|
|||
statePath := fmt.Sprintf("tf-unit/%s", time.Now().String())
|
||||
|
||||
// Get the backend
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": statePath,
|
||||
})
|
||||
}))
|
||||
|
||||
// Grab the client
|
||||
state, err := b.State(backend.DefaultStateName)
|
||||
|
@ -55,11 +55,11 @@ func TestRemoteClient_gzipUpgrade(t *testing.T) {
|
|||
remote.TestClient(t, state.(*remote.State).Client)
|
||||
|
||||
// create a new backend with gzip
|
||||
b = backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b = backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": statePath,
|
||||
"gzip": true,
|
||||
})
|
||||
}))
|
||||
|
||||
// Grab the client
|
||||
state, err = b.State(backend.DefaultStateName)
|
||||
|
@ -75,18 +75,18 @@ func TestConsul_stateLock(t *testing.T) {
|
|||
path := fmt.Sprintf("tf-unit/%s", time.Now().String())
|
||||
|
||||
// create 2 instances to get 2 remote.Clients
|
||||
sA, err := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
sA, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": path,
|
||||
}).State(backend.DefaultStateName)
|
||||
})).State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sB, err := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
sB, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": path,
|
||||
}).State(backend.DefaultStateName)
|
||||
})).State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -96,10 +96,10 @@ func TestConsul_stateLock(t *testing.T) {
|
|||
|
||||
func TestConsul_destroyLock(t *testing.T) {
|
||||
// Get the backend
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": fmt.Sprintf("tf-unit/%s", time.Now().String()),
|
||||
})
|
||||
}))
|
||||
|
||||
// Grab the client
|
||||
s, err := b.State(backend.DefaultStateName)
|
||||
|
@ -135,18 +135,18 @@ func TestConsul_lostLock(t *testing.T) {
|
|||
path := fmt.Sprintf("tf-unit/%s", time.Now().String())
|
||||
|
||||
// create 2 instances to get 2 remote.Clients
|
||||
sA, err := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
sA, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": path,
|
||||
}).State(backend.DefaultStateName)
|
||||
})).State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sB, err := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
sB, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": path + "-not-used",
|
||||
}).State(backend.DefaultStateName)
|
||||
})).State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -190,10 +190,10 @@ func TestConsul_lostLockConnection(t *testing.T) {
|
|||
|
||||
path := fmt.Sprintf("tf-unit/%s", time.Now().String())
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"address": srv.HTTPAddr,
|
||||
"path": path,
|
||||
})
|
||||
}))
|
||||
|
||||
s, err := b.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
|
|
|
@ -7,7 +7,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestEtcdClient_impl(t *testing.T) {
|
||||
|
@ -21,19 +23,19 @@ func TestEtcdClient(t *testing.T) {
|
|||
}
|
||||
|
||||
// Get the backend
|
||||
config := map[string]interface{}{
|
||||
"endpoints": endpoint,
|
||||
"path": fmt.Sprintf("tf-unit/%s", time.Now().String()),
|
||||
config := map[string]cty.Value{
|
||||
"endpoints": cty.StringVal(endpoint),
|
||||
"path": cty.StringVal(fmt.Sprintf("tf-unit/%s", time.Now().String())),
|
||||
}
|
||||
|
||||
if username := os.Getenv("ETCD_USERNAME"); username != "" {
|
||||
config["username"] = username
|
||||
config["username"] = cty.StringVal(username)
|
||||
}
|
||||
if password := os.Getenv("ETCD_PASSWORD"); password != "" {
|
||||
config["password"] = password
|
||||
config["password"] = cty.StringVal(password)
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), config)
|
||||
b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", config))
|
||||
state, err := b.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("Error for valid config: %s", err)
|
||||
|
|
|
@ -55,15 +55,15 @@ func TestBackend(t *testing.T) {
|
|||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend. We need two to test locking.
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": etcdv3Endpoints,
|
||||
"prefix": prefix,
|
||||
})
|
||||
}))
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": etcdv3Endpoints,
|
||||
"prefix": prefix,
|
||||
})
|
||||
}))
|
||||
|
||||
// Test
|
||||
backend.TestBackendStates(t, b1)
|
||||
|
@ -78,17 +78,17 @@ func TestBackend_lockDisabled(t *testing.T) {
|
|||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend. We need two to test locking.
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": etcdv3Endpoints,
|
||||
"prefix": prefix,
|
||||
"lock": false,
|
||||
})
|
||||
}))
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": etcdv3Endpoints,
|
||||
"prefix": prefix + "/" + "different", // Diff so locking test would fail if it was locking
|
||||
"lock": false,
|
||||
})
|
||||
}))
|
||||
|
||||
// Test
|
||||
backend.TestBackendStateLocks(t, b1, b2)
|
||||
|
|
|
@ -22,10 +22,10 @@ func TestRemoteClient(t *testing.T) {
|
|||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": etcdv3Endpoints,
|
||||
"prefix": prefix,
|
||||
})
|
||||
}))
|
||||
|
||||
// Grab the client
|
||||
state, err := b.State(backend.DefaultStateName)
|
||||
|
@ -44,18 +44,18 @@ func TestEtcdv3_stateLock(t *testing.T) {
|
|||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend
|
||||
s1, err := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
s1, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": etcdv3Endpoints,
|
||||
"prefix": prefix,
|
||||
}).State(backend.DefaultStateName)
|
||||
})).State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s2, err := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
s2, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": etcdv3Endpoints,
|
||||
"prefix": prefix,
|
||||
}).State(backend.DefaultStateName)
|
||||
})).State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -70,10 +70,10 @@ func TestEtcdv3_destroyLock(t *testing.T) {
|
|||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": etcdv3Endpoints,
|
||||
"prefix": prefix,
|
||||
})
|
||||
}))
|
||||
|
||||
// Grab the client
|
||||
s, err := b.State(backend.DefaultStateName)
|
||||
|
|
|
@ -187,7 +187,7 @@ func setupBackend(t *testing.T, bucket, prefix, key string) backend.Backend {
|
|||
"encryption_key": key,
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), config)
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config))
|
||||
be := b.(*Backend)
|
||||
|
||||
// create the bucket if it doesn't exist
|
||||
|
|
|
@ -3,6 +3,9 @@ package http
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
)
|
||||
|
||||
|
@ -13,16 +16,16 @@ func TestBackend_impl(t *testing.T) {
|
|||
func TestHTTPClientFactory(t *testing.T) {
|
||||
// defaults
|
||||
|
||||
conf := map[string]interface{}{
|
||||
"address": "http://127.0.0.1:8888/foo",
|
||||
conf := map[string]cty.Value{
|
||||
"address": cty.StringVal("http://127.0.0.1:8888/foo"),
|
||||
}
|
||||
b := backend.TestBackendConfig(t, New(), conf).(*Backend)
|
||||
b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend)
|
||||
client := b.client
|
||||
|
||||
if client == nil {
|
||||
t.Fatal("Unexpected failure, address")
|
||||
}
|
||||
if client.URL.String() != conf["address"] {
|
||||
if client.URL.String() != "http://127.0.0.1:8888/foo" {
|
||||
t.Fatalf("Expected address \"%s\", got \"%s\"", conf["address"], client.URL.String())
|
||||
}
|
||||
if client.UpdateMethod != "POST" {
|
||||
|
@ -39,18 +42,18 @@ func TestHTTPClientFactory(t *testing.T) {
|
|||
}
|
||||
|
||||
// custom
|
||||
conf = map[string]interface{}{
|
||||
"address": "http://127.0.0.1:8888/foo",
|
||||
"update_method": "BLAH",
|
||||
"lock_address": "http://127.0.0.1:8888/bar",
|
||||
"lock_method": "BLIP",
|
||||
"unlock_address": "http://127.0.0.1:8888/baz",
|
||||
"unlock_method": "BLOOP",
|
||||
"username": "user",
|
||||
"password": "pass",
|
||||
conf = map[string]cty.Value{
|
||||
"address": cty.StringVal("http://127.0.0.1:8888/foo"),
|
||||
"update_method": cty.StringVal("BLAH"),
|
||||
"lock_address": cty.StringVal("http://127.0.0.1:8888/bar"),
|
||||
"lock_method": cty.StringVal("BLIP"),
|
||||
"unlock_address": cty.StringVal("http://127.0.0.1:8888/baz"),
|
||||
"unlock_method": cty.StringVal("BLOOP"),
|
||||
"username": cty.StringVal("user"),
|
||||
"password": cty.StringVal("pass"),
|
||||
}
|
||||
|
||||
b = backend.TestBackendConfig(t, New(), conf).(*Backend)
|
||||
b = backend.TestBackendConfig(t, New(), configs.SynthBody("synth", conf)).(*Backend)
|
||||
client = b.client
|
||||
|
||||
if client == nil {
|
||||
|
@ -59,13 +62,13 @@ func TestHTTPClientFactory(t *testing.T) {
|
|||
if client.UpdateMethod != "BLAH" {
|
||||
t.Fatalf("Expected update_method \"%s\", got \"%s\"", "BLAH", client.UpdateMethod)
|
||||
}
|
||||
if client.LockURL.String() != conf["lock_address"] || client.LockMethod != "BLIP" {
|
||||
if client.LockURL.String() != conf["lock_address"].AsString() || client.LockMethod != "BLIP" {
|
||||
t.Fatalf("Unexpected lock_address \"%s\" vs \"%s\" or lock_method \"%s\" vs \"%s\"", client.LockURL.String(),
|
||||
conf["lock_address"], client.LockMethod, conf["lock_method"])
|
||||
conf["lock_address"].AsString(), client.LockMethod, conf["lock_method"])
|
||||
}
|
||||
if client.UnlockURL.String() != conf["unlock_address"] || client.UnlockMethod != "BLOOP" {
|
||||
if client.UnlockURL.String() != conf["unlock_address"].AsString() || client.UnlockMethod != "BLOOP" {
|
||||
t.Fatalf("Unexpected unlock_address \"%s\" vs \"%s\" or unlock_method \"%s\" vs \"%s\"", client.UnlockURL.String(),
|
||||
conf["unlock_address"], client.UnlockMethod, conf["unlock_method"])
|
||||
conf["unlock_address"].AsString(), client.UnlockMethod, conf["unlock_method"])
|
||||
}
|
||||
if client.Username != "user" || client.Password != "pass" {
|
||||
t.Fatalf("Unexpected username \"%s\" vs \"%s\" or password \"%s\" vs \"%s\"", client.Username, conf["username"],
|
||||
|
|
|
@ -3,6 +3,7 @@ package inmem
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -20,7 +21,7 @@ func TestBackendConfig(t *testing.T) {
|
|||
"lock_id": testID,
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), config).(*Backend)
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
|
||||
|
||||
s, err := b.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
|
@ -39,14 +40,14 @@ func TestBackendConfig(t *testing.T) {
|
|||
|
||||
func TestBackend(t *testing.T) {
|
||||
defer Reset()
|
||||
b := backend.TestBackendConfig(t, New(), nil).(*Backend)
|
||||
b := backend.TestBackendConfig(t, New(), hcl.EmptyBody()).(*Backend)
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestBackendLocked(t *testing.T) {
|
||||
defer Reset()
|
||||
b1 := backend.TestBackendConfig(t, New(), nil).(*Backend)
|
||||
b2 := backend.TestBackendConfig(t, New(), nil).(*Backend)
|
||||
b1 := backend.TestBackendConfig(t, New(), hcl.EmptyBody()).(*Backend)
|
||||
b2 := backend.TestBackendConfig(t, New(), hcl.EmptyBody()).(*Backend)
|
||||
|
||||
backend.TestBackendStateLocks(t, b1, b2)
|
||||
}
|
||||
|
@ -54,7 +55,7 @@ func TestBackendLocked(t *testing.T) {
|
|||
// use the this backen to test the remote.State implementation
|
||||
func TestRemoteState(t *testing.T) {
|
||||
defer Reset()
|
||||
b := backend.TestBackendConfig(t, New(), nil)
|
||||
b := backend.TestBackendConfig(t, New(), hcl.EmptyBody())
|
||||
|
||||
workspace := "workspace"
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package inmem
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
)
|
||||
|
@ -14,7 +15,7 @@ func TestRemoteClient_impl(t *testing.T) {
|
|||
|
||||
func TestRemoteClient(t *testing.T) {
|
||||
defer Reset()
|
||||
b := backend.TestBackendConfig(t, New(), nil)
|
||||
b := backend.TestBackendConfig(t, New(), hcl.EmptyBody())
|
||||
|
||||
s, err := b.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
|
@ -26,7 +27,7 @@ func TestRemoteClient(t *testing.T) {
|
|||
|
||||
func TestInmemLocks(t *testing.T) {
|
||||
defer Reset()
|
||||
s, err := backend.TestBackendConfig(t, New(), nil).State(backend.DefaultStateName)
|
||||
s, err := backend.TestBackendConfig(t, New(), hcl.EmptyBody()).State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -30,10 +30,10 @@ func TestBackend(t *testing.T) {
|
|||
directory := fmt.Sprintf("terraform-remote-manta-test-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
"path": directory,
|
||||
"object_name": keyName,
|
||||
}).(*Backend)
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"path": directory,
|
||||
"objectName": keyName,
|
||||
})).(*Backend)
|
||||
|
||||
createMantaFolder(t, b.storageClient, directory)
|
||||
defer deleteMantaFolder(t, b.storageClient, directory)
|
||||
|
@ -47,15 +47,15 @@ func TestBackendLocked(t *testing.T) {
|
|||
directory := fmt.Sprintf("terraform-remote-manta-test-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
"path": directory,
|
||||
"object_name": keyName,
|
||||
}).(*Backend)
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"path": directory,
|
||||
"objectName": keyName,
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
"path": directory,
|
||||
"object_name": keyName,
|
||||
}).(*Backend)
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"path": directory,
|
||||
"objectName": keyName,
|
||||
})).(*Backend)
|
||||
|
||||
createMantaFolder(t, b1.storageClient, directory)
|
||||
defer deleteMantaFolder(t, b1.storageClient, directory)
|
||||
|
|
|
@ -20,10 +20,10 @@ func TestRemoteClient(t *testing.T) {
|
|||
directory := fmt.Sprintf("terraform-remote-manta-test-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
"path": directory,
|
||||
"object_name": keyName,
|
||||
}).(*Backend)
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"path": directory,
|
||||
"objectName": keyName,
|
||||
})).(*Backend)
|
||||
|
||||
createMantaFolder(t, b.storageClient, directory)
|
||||
defer deleteMantaFolder(t, b.storageClient, directory)
|
||||
|
@ -41,15 +41,15 @@ func TestRemoteClientLocks(t *testing.T) {
|
|||
directory := fmt.Sprintf("terraform-remote-manta-test-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
"path": directory,
|
||||
"object_name": keyName,
|
||||
}).(*Backend)
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"path": directory,
|
||||
"objectName": keyName,
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
"path": directory,
|
||||
"object_name": keyName,
|
||||
}).(*Backend)
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"path": directory,
|
||||
"objectName": keyName,
|
||||
})).(*Backend)
|
||||
|
||||
createMantaFolder(t, b1.storageClient, directory)
|
||||
defer deleteMantaFolder(t, b1.storageClient, directory)
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/config/hcl2shim"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
@ -42,7 +42,7 @@ func TestBackendConfig(t *testing.T) {
|
|||
"dynamodb_table": "dynamoTable",
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), config).(*Backend)
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
|
||||
|
||||
if *b.s3Client.Config.Region != "us-west-1" {
|
||||
t.Fatalf("Incorrect region was populated")
|
||||
|
@ -68,22 +68,16 @@ func TestBackendConfig(t *testing.T) {
|
|||
|
||||
func TestBackendConfig_invalidKey(t *testing.T) {
|
||||
testACC(t)
|
||||
cfg := map[string]interface{}{
|
||||
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{
|
||||
"region": "us-west-1",
|
||||
"bucket": "tf-test",
|
||||
"key": "/leading-slash",
|
||||
"encrypt": true,
|
||||
"dynamodb_table": "dynamoTable",
|
||||
}
|
||||
})
|
||||
|
||||
rawCfg, err := config.NewRawConfig(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resCfg := terraform.NewResourceConfig(rawCfg)
|
||||
|
||||
_, errs := New().Validate(resCfg)
|
||||
if len(errs) != 1 {
|
||||
diags := New().ValidateConfig(cfg)
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("expected config validation error")
|
||||
}
|
||||
}
|
||||
|
@ -94,11 +88,11 @@ func TestBackend(t *testing.T) {
|
|||
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b.s3Client, bucketName)
|
||||
defer deleteS3Bucket(t, b.s3Client, bucketName)
|
||||
|
@ -112,19 +106,19 @@ func TestBackendLocked(t *testing.T) {
|
|||
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
|
||||
keyName := "test/state"
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
"dynamodb_table": bucketName,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
"dynamodb_table": bucketName,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b1.s3Client, bucketName)
|
||||
defer deleteS3Bucket(t, b1.s3Client, bucketName)
|
||||
|
@ -141,11 +135,11 @@ func TestBackendExtraPaths(t *testing.T) {
|
|||
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
|
||||
keyName := "test/state/tfstate"
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b.s3Client, bucketName)
|
||||
defer deleteS3Bucket(t, b.s3Client, bucketName)
|
||||
|
@ -256,11 +250,11 @@ func TestBackendPrefixInWorkspace(t *testing.T) {
|
|||
testACC(t)
|
||||
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": "test-env.tfstate",
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": "test-env.tfstate",
|
||||
"workspace_key_prefix": "env",
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b.s3Client, bucketName)
|
||||
defer deleteS3Bucket(t, b.s3Client, bucketName)
|
||||
|
@ -284,33 +278,33 @@ func TestKeyEnv(t *testing.T) {
|
|||
keyName := "some/paths/tfstate"
|
||||
|
||||
bucket0Name := fmt.Sprintf("terraform-remote-s3-test-%x-0", time.Now().Unix())
|
||||
b0 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b0 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucket0Name,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
"workspace_key_prefix": "",
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b0.s3Client, bucket0Name)
|
||||
defer deleteS3Bucket(t, b0.s3Client, bucket0Name)
|
||||
|
||||
bucket1Name := fmt.Sprintf("terraform-remote-s3-test-%x-1", time.Now().Unix())
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucket1Name,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
"workspace_key_prefix": "project/env:",
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b1.s3Client, bucket1Name)
|
||||
defer deleteS3Bucket(t, b1.s3Client, bucket1Name)
|
||||
|
||||
bucket2Name := fmt.Sprintf("terraform-remote-s3-test-%x-2", time.Now().Unix())
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucket2Name,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b2.s3Client, bucket2Name)
|
||||
defer deleteS3Bucket(t, b2.s3Client, bucket2Name)
|
||||
|
|
|
@ -24,11 +24,11 @@ func TestRemoteClient(t *testing.T) {
|
|||
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b.s3Client, bucketName)
|
||||
defer deleteS3Bucket(t, b.s3Client, bucketName)
|
||||
|
@ -46,19 +46,19 @@ func TestRemoteClientLocks(t *testing.T) {
|
|||
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
"dynamodb_table": bucketName,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
"dynamodb_table": bucketName,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b1.s3Client, bucketName)
|
||||
defer deleteS3Bucket(t, b1.s3Client, bucketName)
|
||||
|
@ -84,19 +84,19 @@ func TestForceUnlock(t *testing.T) {
|
|||
bucketName := fmt.Sprintf("terraform-remote-s3-test-force-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
"dynamodb_table": bucketName,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"encrypt": true,
|
||||
"dynamodb_table": bucketName,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b1.s3Client, bucketName)
|
||||
defer deleteS3Bucket(t, b1.s3Client, bucketName)
|
||||
|
@ -161,11 +161,11 @@ func TestRemoteClient_clientMD5(t *testing.T) {
|
|||
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"dynamodb_table": bucketName,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b.s3Client, bucketName)
|
||||
defer deleteS3Bucket(t, b.s3Client, bucketName)
|
||||
|
@ -209,11 +209,11 @@ func TestRemoteClient_stateChecksum(t *testing.T) {
|
|||
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
|
||||
keyName := "testState"
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
"dynamodb_table": bucketName,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
createS3Bucket(t, b1.s3Client, bucketName)
|
||||
defer deleteS3Bucket(t, b1.s3Client, bucketName)
|
||||
|
@ -240,10 +240,10 @@ func TestRemoteClient_stateChecksum(t *testing.T) {
|
|||
|
||||
// Use b2 without a dynamodb_table to bypass the lock table to write the state directly.
|
||||
// client2 will write the "incorrect" state, simulating s3 eventually consistency delays
|
||||
b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"key": keyName,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
s2, err := b2.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
@ -46,7 +46,7 @@ func TestBackendConfig(t *testing.T) {
|
|||
"container": "test-tfstate",
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), config).(*Backend)
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
|
||||
|
||||
if b.container != "test-tfstate" {
|
||||
t.Fatal("Incorrect path was provided.")
|
||||
|
@ -61,9 +61,9 @@ func TestBackend(t *testing.T) {
|
|||
|
||||
container := fmt.Sprintf("terraform-state-swift-test-%x", time.Now().Unix())
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"container": container,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
defer deleteSwiftContainer(t, b.client, container)
|
||||
|
||||
|
@ -75,9 +75,9 @@ func TestBackendPath(t *testing.T) {
|
|||
|
||||
path := fmt.Sprintf("terraform-state-swift-test-%x", time.Now().Unix())
|
||||
t.Logf("[DEBUG] Generating backend config")
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"path": path,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
t.Logf("[DEBUG] Backend configured")
|
||||
|
||||
defer deleteSwiftContainer(t, b.client, path)
|
||||
|
@ -131,10 +131,10 @@ func TestBackendArchive(t *testing.T) {
|
|||
container := fmt.Sprintf("terraform-state-swift-test-%x", time.Now().Unix())
|
||||
archiveContainer := fmt.Sprintf("%s_archive", container)
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"archive_container": archiveContainer,
|
||||
"container": container,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
defer deleteSwiftContainer(t, b.client, container)
|
||||
defer deleteSwiftContainer(t, b.client, archiveContainer)
|
||||
|
|
|
@ -18,9 +18,9 @@ func TestRemoteClient(t *testing.T) {
|
|||
|
||||
container := fmt.Sprintf("terraform-state-swift-test-%x", time.Now().Unix())
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), map[string]interface{}{
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"container": container,
|
||||
}).(*Backend)
|
||||
})).(*Backend)
|
||||
|
||||
state, err := b.State(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
|
|
|
@ -5,41 +5,61 @@ import (
|
|||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
|
||||
"github.com/hashicorp/terraform/config/hcl2shim"
|
||||
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcldec"
|
||||
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// TestBackendConfig validates and configures the backend with the
|
||||
// given configuration.
|
||||
func TestBackendConfig(t *testing.T, b Backend, c map[string]interface{}) Backend {
|
||||
func TestBackendConfig(t *testing.T, b Backend, c hcl.Body) Backend {
|
||||
t.Helper()
|
||||
|
||||
// Get the proper config structure
|
||||
rc, err := config.NewRawConfig(c)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
conf := terraform.NewResourceConfig(rc)
|
||||
t.Logf("TestBackendConfig on %T with %#v", b, c)
|
||||
|
||||
// Validate
|
||||
warns, errs := b.Validate(conf)
|
||||
if len(warns) > 0 {
|
||||
t.Fatalf("warnings: %s", warns)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("errors: %s", errs)
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
schema := b.ConfigSchema()
|
||||
spec := schema.DecoderSpec()
|
||||
obj, decDiags := hcldec.Decode(c, spec, nil)
|
||||
diags = diags.Append(decDiags)
|
||||
|
||||
valDiags := b.ValidateConfig(obj)
|
||||
diags = diags.Append(valDiags.InConfigBody(c))
|
||||
|
||||
if len(diags) != 0 {
|
||||
t.Fatal(diags)
|
||||
}
|
||||
|
||||
// Configure
|
||||
if err := b.Configure(conf); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
confDiags := b.Configure(obj)
|
||||
if len(confDiags) != 0 {
|
||||
confDiags = confDiags.InConfigBody(c)
|
||||
t.Fatal(confDiags)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// TestWrapConfig takes a raw data structure and converts it into a
|
||||
// synthetic hcl.Body to use for testing.
|
||||
//
|
||||
// The given structure should only include values that can be accepted by
|
||||
// hcl2shim.HCL2ValueFromConfigValue. If incompatible values are given,
|
||||
// this function will panic.
|
||||
func TestWrapConfig(raw map[string]interface{}) hcl.Body {
|
||||
obj := hcl2shim.HCL2ValueFromConfigValue(raw)
|
||||
return configs.SynthBody("<TestWrapConfig>", obj.AsValueMap())
|
||||
}
|
||||
|
||||
// TestBackend will test the functionality of a Backend. The backend is
|
||||
// assumed to already be configured. This will test state functionality.
|
||||
// If the backend reports it doesn't support multi-state by returning the
|
||||
|
|
|
@ -1,497 +1,116 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
backendInit "github.com/hashicorp/terraform/backend/init"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
backendinit "github.com/hashicorp/terraform/backend/init"
|
||||
"github.com/hashicorp/terraform/config/hcl2shim"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
func dataSourceRemoteState() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Read: dataSourceRemoteStateRead,
|
||||
return &schema.Resource{
|
||||
Read: dataSourceRemoteStateRead,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"backend": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
if vStr, ok := v.(string); ok && vStr == "_local" {
|
||||
ws = append(ws, "Use of the %q backend is now officially "+
|
||||
"supported as %q. Please update your configuration to ensure "+
|
||||
"compatibility with future versions of Terraform.",
|
||||
"_local", "local")
|
||||
}
|
||||
Schema: map[string]*schema.Schema{
|
||||
"backend": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
|
||||
if vStr, ok := v.(string); ok && vStr == "_local" {
|
||||
ws = append(ws, "Use of the %q backend is now officially "+
|
||||
"supported as %q. Please update your configuration to ensure "+
|
||||
"compatibility with future versions of Terraform.",
|
||||
"_local", "local")
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
},
|
||||
return
|
||||
},
|
||||
},
|
||||
|
||||
// This field now contains all possible attributes that are supported
|
||||
// by any of the existing backends. When merging this into 0.12 this
|
||||
// should be reverted and instead the new 'cty.DynamicPseudoType' type
|
||||
// should be used to make this work with any future backends as well.
|
||||
"config": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"hostname": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"organization": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"token": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"workspaces": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Schema{Type: schema.TypeMap},
|
||||
},
|
||||
"username": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"password": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"url": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"repo": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"subpath": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"storage_account_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"container_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"access_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"environment": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"resource_group_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"arm_subscription_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"arm_client_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"arm_client_secret": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"arm_tenant_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"access_token": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"address": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"scheme": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"datacenter": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"http_auth": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"gzip": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"lock": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"ca_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"cert_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"key_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"endpoints": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"prefix": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"cacert_path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"cert_path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"key_path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"bucket": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"credentials": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"project": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"region": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"encryption_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"update_method": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"lock_address": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"lock_method": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"unlock_address": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"unlock_method": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"skip_cert_verification": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"account": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"user": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"key_material": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"key_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"insecure_skip_tls_verify": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"object_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"endpoint": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"encrypt": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"acl": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"secret_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"kms_key_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"lock_table": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"dynamodb_table": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"profile": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"shared_credentials_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"role_arn": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"assume_role_policy": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"external_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"session_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"workspace_key_prefix": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"skip_credentials_validation": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"skip_get_ec2_platforms": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"skip_region_validation": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"skip_requesting_account_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"skip_metadata_api_check": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"auth_url": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"container": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"user_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"user_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"region_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"tenant_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"tenant_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"domain_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"domain_name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"insecure": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"cacert_file": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"cert": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"archive_container": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"archive_path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"expire_after": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"config": {
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"defaults": {
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
"defaults": {
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"environment": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: backend.DefaultStateName,
|
||||
Deprecated: "Terraform environments are now called workspaces. Please use the workspace key instead.",
|
||||
},
|
||||
"environment": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: backend.DefaultStateName,
|
||||
Deprecated: "Terraform environments are now called workspaces. Please use the workspace key instead.",
|
||||
},
|
||||
|
||||
"workspace": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: backend.DefaultStateName,
|
||||
},
|
||||
"workspace": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: backend.DefaultStateName,
|
||||
},
|
||||
|
||||
"__has_dynamic_attributes": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
"__has_dynamic_attributes": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
|
||||
backendType := d.Get("backend").(string)
|
||||
backendType := d.Get("backend").(string)
|
||||
|
||||
// Get the configuration in a type we want. This is a bit of a hack but makes
|
||||
// things work for the 'remote' backend as well. This can simply be deleted or
|
||||
// reverted when merging this 0.12.
|
||||
raw := make(map[string]interface{})
|
||||
if cfg, ok := d.GetOk("config"); ok {
|
||||
if raw, ok = cfg.(*schema.Set).List()[0].(map[string]interface{}); ok {
|
||||
for k, v := range raw {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
if v == "" {
|
||||
delete(raw, k)
|
||||
}
|
||||
case []interface{}:
|
||||
if len(v) == 0 {
|
||||
delete(raw, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rawConfig, err := config.NewRawConfig(raw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing backend: %s", err)
|
||||
}
|
||||
|
||||
// Don't break people using the old _local syntax - but note warning above
|
||||
if backendType == "_local" {
|
||||
log.Println(`[INFO] Switching old (unsupported) backend "_local" to "local"`)
|
||||
backendType = "local"
|
||||
// Don't break people using the old _local syntax - but note warning above
|
||||
if backendType == "_local" {
|
||||
log.Println(`[INFO] Switching old (unsupported) backend "_local" to "local"`)
|
||||
backendType = "local"
|
||||
}
|
||||
|
||||
// Create the client to access our remote state
|
||||
log.Printf("[DEBUG] Initializing remote state backend: %s", backendType)
|
||||
f := backendInit.Backend(backendType)
|
||||
f := backendinit.Backend(backendType)
|
||||
if f == nil {
|
||||
return fmt.Errorf("Unknown backend type: %s", backendType)
|
||||
return fmt.Errorf("Unknown backend type: %s", backendType)
|
||||
}
|
||||
b := f()
|
||||
|
||||
warns, errs := b.Validate(terraform.NewResourceConfig(rawConfig))
|
||||
for _, warning := range warns {
|
||||
log.Printf("[DEBUG] Warning validating backend config: %s", warning)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("error validating backend config: %s", multierror.Append(nil, errs...))
|
||||
}
|
||||
schema := b.ConfigSchema()
|
||||
rawConfig := d.Get("config")
|
||||
configVal := hcl2shim.HCL2ValueFromConfigValue(rawConfig)
|
||||
|
||||
// Configure the backend
|
||||
if err := b.Configure(terraform.NewResourceConfig(rawConfig)); err != nil {
|
||||
return fmt.Errorf("error initializing backend: %s", err)
|
||||
// Try to coerce the provided value into the desired configuration type.
|
||||
configVal, err := schema.CoerceValue(configVal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid %s backend configuration: %s", backendType, tfdiags.FormatError(err))
|
||||
}
|
||||
validateDiags := b.ValidateConfig(configVal)
|
||||
if validateDiags.HasErrors() {
|
||||
return validateDiags.Err()
|
||||
}
|
||||
configureDiags := b.Configure(configVal)
|
||||
if configureDiags.HasErrors() {
|
||||
return configureDiags.Err()
|
||||
}
|
||||
|
||||
// environment is deprecated in favour of workspace.
|
||||
// If both keys are set workspace should win.
|
||||
name := d.Get("environment").(string)
|
||||
if ws, ok := d.GetOk("workspace"); ok && ws != backend.DefaultStateName {
|
||||
name = ws.(string)
|
||||
name = ws.(string)
|
||||
}
|
||||
|
||||
state, err := b.State(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading the remote state: %s", err)
|
||||
return fmt.Errorf("error loading the remote state: %s", err)
|
||||
}
|
||||
if err := state.RefreshState(); err != nil {
|
||||
return err
|
||||
return err
|
||||
}
|
||||
d.SetId(time.Now().UTC().String())
|
||||
|
||||
|
@ -499,24 +118,24 @@ func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
|
|||
|
||||
defaults := d.Get("defaults").(map[string]interface{})
|
||||
for key, val := range defaults {
|
||||
outputMap[key] = val
|
||||
outputMap[key] = val
|
||||
}
|
||||
|
||||
remoteState := state.State()
|
||||
if remoteState.Empty() {
|
||||
log.Println("[DEBUG] empty remote state")
|
||||
log.Println("[DEBUG] empty remote state")
|
||||
} else {
|
||||
for key, val := range remoteState.RootModule().Outputs {
|
||||
if val.Value != nil {
|
||||
outputMap[key] = val.Value
|
||||
for key, val := range remoteState.RootModule().Outputs {
|
||||
if val.Value != nil {
|
||||
outputMap[key] = val.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mappedOutputs := remoteStateFlatten(outputMap)
|
||||
|
||||
for key, val := range mappedOutputs {
|
||||
d.UnsafeSetFieldRaw(key, val)
|
||||
d.UnsafeSetFieldRaw(key, val)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/command/clistate"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
@ -27,9 +28,9 @@ import (
|
|||
|
||||
// BackendOpts are the options used to initialize a backend.Backend.
|
||||
type BackendOpts struct {
|
||||
// Module is the root module from which we will extract the terraform and
|
||||
// backend configuration.
|
||||
Config *config.Config
|
||||
// Config is a representation of the backend configuration block given in
|
||||
// the root module, or nil if no such block is present.
|
||||
Config *configs.Backend
|
||||
|
||||
// ConfigFile is a path to a file that contains configuration that
|
||||
// is merged directly into the backend configuration when loaded
|
||||
|
|
|
@ -80,6 +80,25 @@ func (m *Meta) loadSingleModule(dir string) (*configs.Module, tfdiags.Diagnostic
|
|||
return module, diags
|
||||
}
|
||||
|
||||
// loadBackendConfig reads configuration from the given directory and returns
|
||||
// the backend configuration defined by that module, if any. Nil is returned
|
||||
// if the specified module does not have an explicit backend configuration.
|
||||
//
|
||||
// This is a convenience method for command code that will delegate to the
|
||||
// configured backend to do most of its work, since in that case it is the
|
||||
// backend that will do the full configuration load.
|
||||
//
|
||||
// Although this method returns only the backend configuration, at present it
|
||||
// actually loads and validates the entire configuration first. Therefore errors
|
||||
// returned may be about other aspects of the configuration. This behavior may
|
||||
// change in future, so callers must not rely on it. (That is, they must expect
|
||||
// that a call to loadSingleModule or loadConfig could fail on the same
|
||||
// directory even if loadBackendConfig succeeded.)
|
||||
func (m *Meta) loadBackendConfig(rootDir string) (*configs.Backend, tfdiags.Diagnostics) {
|
||||
mod, diags := m.loadSingleModule(rootDir)
|
||||
return mod.Backend, diags
|
||||
}
|
||||
|
||||
// installModules reads a root module from the given directory and attempts
|
||||
// recursively install all of its descendent modules.
|
||||
//
|
||||
|
|
|
@ -3,6 +3,11 @@ package schema
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/config/configschema"
|
||||
"github.com/hashicorp/terraform/config/hcl2shim"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -38,41 +43,50 @@ func FromContextBackendConfig(ctx context.Context) *ResourceData {
|
|||
return ctx.Value(backendConfigKey).(*ResourceData)
|
||||
}
|
||||
|
||||
func (b *Backend) Input(
|
||||
input terraform.UIInput,
|
||||
c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
|
||||
if b == nil {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
return schemaMap(b.Schema).Input(input, c)
|
||||
func (b *Backend) ConfigSchema() *configschema.Block {
|
||||
// This is an alias of CoreConfigSchema just to implement the
|
||||
// backend.Backend interface.
|
||||
return b.CoreConfigSchema()
|
||||
}
|
||||
|
||||
func (b *Backend) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||
if b == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return schemaMap(b.Schema).Validate(c)
|
||||
}
|
||||
|
||||
func (b *Backend) Configure(c *terraform.ResourceConfig) error {
|
||||
func (b *Backend) ValidateConfig(obj cty.Value) tfdiags.Diagnostics {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
shimRC := b.shimConfig(obj)
|
||||
warns, errs := schemaMap(b.Schema).Validate(shimRC)
|
||||
for _, warn := range warns {
|
||||
diags = diags.Append(tfdiags.SimpleWarning(warn))
|
||||
}
|
||||
for _, err := range errs {
|
||||
diags = diags.Append(err)
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
sm := schemaMap(b.Schema)
|
||||
shimRC := b.shimConfig(obj)
|
||||
|
||||
// Get a ResourceData for this configuration. To do this, we actually
|
||||
// generate an intermediary "diff" although that is never exposed.
|
||||
diff, err := sm.Diff(nil, c, nil, nil)
|
||||
diff, err := sm.Diff(nil, shimRC, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
diags = diags.Append(err)
|
||||
return diags
|
||||
}
|
||||
|
||||
data, err := sm.Data(nil, diff)
|
||||
if err != nil {
|
||||
return err
|
||||
diags = diags.Append(err)
|
||||
return diags
|
||||
}
|
||||
b.config = data
|
||||
|
||||
|
@ -80,11 +94,24 @@ func (b *Backend) Configure(c *terraform.ResourceConfig) error {
|
|||
err = b.ConfigureFunc(context.WithValue(
|
||||
context.Background(), backendConfigKey, data))
|
||||
if err != nil {
|
||||
return err
|
||||
diags = diags.Append(err)
|
||||
return diags
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return diags
|
||||
}
|
||||
|
||||
// shimConfig turns a new-style cty.Value configuration (which must be of
|
||||
// an object type) into a minimal old-style *terraform.ResourceConfig object
|
||||
// that should be populated enough to appease the not-yet-updated functionality
|
||||
// in this package. This should be removed once everything is updated.
|
||||
func (b *Backend) shimConfig(obj cty.Value) *terraform.ResourceConfig {
|
||||
shimMap := hcl2shim.ConfigValueFromHCL2(obj).(map[string]interface{})
|
||||
return &terraform.ResourceConfig{
|
||||
Config: shimMap,
|
||||
Raw: shimMap,
|
||||
}
|
||||
}
|
||||
|
||||
// Config returns the configuration. This is available after Configure is
|
||||
|
|
|
@ -5,15 +5,14 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestBackendValidate(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
B *Backend
|
||||
Config map[string]interface{}
|
||||
Config map[string]cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
|
@ -26,7 +25,7 @@ func TestBackendValidate(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
map[string]cty.Value{},
|
||||
true,
|
||||
},
|
||||
|
||||
|
@ -40,8 +39,8 @@ func TestBackendValidate(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
map[string]cty.Value{
|
||||
"foo": cty.StringVal("bar"),
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
@ -49,14 +48,9 @@ func TestBackendValidate(t *testing.T) {
|
|||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||
c, err := config.NewRawConfig(tc.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
_, es := tc.B.Validate(terraform.NewResourceConfig(c))
|
||||
if len(es) > 0 != tc.Err {
|
||||
t.Fatalf("%d: %#v", i, es)
|
||||
diags := tc.B.ValidateConfig(cty.ObjectVal(tc.Config))
|
||||
if diags.HasErrors() != tc.Err {
|
||||
t.Errorf("wrong number of diagnostics")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -66,7 +60,7 @@ func TestBackendConfigure(t *testing.T) {
|
|||
cases := []struct {
|
||||
Name string
|
||||
B *Backend
|
||||
Config map[string]interface{}
|
||||
Config map[string]cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
|
@ -88,8 +82,8 @@ func TestBackendConfigure(t *testing.T) {
|
|||
return nil
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": 42,
|
||||
map[string]cty.Value{
|
||||
"foo": cty.NumberIntVal(42),
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
@ -97,14 +91,9 @@ func TestBackendConfigure(t *testing.T) {
|
|||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||
c, err := config.NewRawConfig(tc.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
err = tc.B.Configure(terraform.NewResourceConfig(c))
|
||||
if err != nil != tc.Err {
|
||||
t.Fatalf("%d: %s", i, err)
|
||||
diags := tc.B.Configure(cty.ObjectVal(tc.Config))
|
||||
if diags.HasErrors() != tc.Err {
|
||||
t.Errorf("wrong number of diagnostics")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue