Merge pull request #27076 from hashicorp/jbardin/legacy-cleanup
Legacy types cleanup
This commit is contained in:
commit
f2c5aa6d28
|
@ -10,6 +10,8 @@ import (
|
|||
"github.com/hashicorp/terraform/plans"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
||||
legacy "github.com/hashicorp/terraform/internal/legacy/terraform"
|
||||
)
|
||||
|
||||
func TestCountHook_impl(t *testing.T) {
|
||||
|
@ -19,8 +21,8 @@ func TestCountHook_impl(t *testing.T) {
|
|||
func TestCountHookPostDiff_DestroyDeposed(t *testing.T) {
|
||||
h := new(CountHook)
|
||||
|
||||
resources := map[string]*terraform.InstanceDiff{
|
||||
"lorem": &terraform.InstanceDiff{DestroyDeposed: true},
|
||||
resources := map[string]*legacy.InstanceDiff{
|
||||
"lorem": &legacy.InstanceDiff{DestroyDeposed: true},
|
||||
}
|
||||
|
||||
for k := range resources {
|
||||
|
@ -47,11 +49,11 @@ func TestCountHookPostDiff_DestroyDeposed(t *testing.T) {
|
|||
func TestCountHookPostDiff_DestroyOnly(t *testing.T) {
|
||||
h := new(CountHook)
|
||||
|
||||
resources := map[string]*terraform.InstanceDiff{
|
||||
"foo": &terraform.InstanceDiff{Destroy: true},
|
||||
"bar": &terraform.InstanceDiff{Destroy: true},
|
||||
"lorem": &terraform.InstanceDiff{Destroy: true},
|
||||
"ipsum": &terraform.InstanceDiff{Destroy: true},
|
||||
resources := map[string]*legacy.InstanceDiff{
|
||||
"foo": &legacy.InstanceDiff{Destroy: true},
|
||||
"bar": &legacy.InstanceDiff{Destroy: true},
|
||||
"lorem": &legacy.InstanceDiff{Destroy: true},
|
||||
"ipsum": &legacy.InstanceDiff{Destroy: true},
|
||||
}
|
||||
|
||||
for k := range resources {
|
||||
|
@ -78,20 +80,20 @@ func TestCountHookPostDiff_DestroyOnly(t *testing.T) {
|
|||
func TestCountHookPostDiff_AddOnly(t *testing.T) {
|
||||
h := new(CountHook)
|
||||
|
||||
resources := map[string]*terraform.InstanceDiff{
|
||||
"foo": &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{RequiresNew: true},
|
||||
resources := map[string]*legacy.InstanceDiff{
|
||||
"foo": &legacy.InstanceDiff{
|
||||
Attributes: map[string]*legacy.ResourceAttrDiff{
|
||||
"foo": &legacy.ResourceAttrDiff{RequiresNew: true},
|
||||
},
|
||||
},
|
||||
"bar": &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{RequiresNew: true},
|
||||
"bar": &legacy.InstanceDiff{
|
||||
Attributes: map[string]*legacy.ResourceAttrDiff{
|
||||
"foo": &legacy.ResourceAttrDiff{RequiresNew: true},
|
||||
},
|
||||
},
|
||||
"lorem": &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{RequiresNew: true},
|
||||
"lorem": &legacy.InstanceDiff{
|
||||
Attributes: map[string]*legacy.ResourceAttrDiff{
|
||||
"foo": &legacy.ResourceAttrDiff{RequiresNew: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -120,23 +122,23 @@ func TestCountHookPostDiff_AddOnly(t *testing.T) {
|
|||
func TestCountHookPostDiff_ChangeOnly(t *testing.T) {
|
||||
h := new(CountHook)
|
||||
|
||||
resources := map[string]*terraform.InstanceDiff{
|
||||
"foo": &terraform.InstanceDiff{
|
||||
resources := map[string]*legacy.InstanceDiff{
|
||||
"foo": &legacy.InstanceDiff{
|
||||
Destroy: false,
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{},
|
||||
Attributes: map[string]*legacy.ResourceAttrDiff{
|
||||
"foo": &legacy.ResourceAttrDiff{},
|
||||
},
|
||||
},
|
||||
"bar": &terraform.InstanceDiff{
|
||||
"bar": &legacy.InstanceDiff{
|
||||
Destroy: false,
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{},
|
||||
Attributes: map[string]*legacy.ResourceAttrDiff{
|
||||
"foo": &legacy.ResourceAttrDiff{},
|
||||
},
|
||||
},
|
||||
"lorem": &terraform.InstanceDiff{
|
||||
"lorem": &legacy.InstanceDiff{
|
||||
Destroy: false,
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{},
|
||||
Attributes: map[string]*legacy.ResourceAttrDiff{
|
||||
"foo": &legacy.ResourceAttrDiff{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -197,11 +199,11 @@ func TestCountHookPostDiff_Mixed(t *testing.T) {
|
|||
func TestCountHookPostDiff_NoChange(t *testing.T) {
|
||||
h := new(CountHook)
|
||||
|
||||
resources := map[string]*terraform.InstanceDiff{
|
||||
"foo": &terraform.InstanceDiff{},
|
||||
"bar": &terraform.InstanceDiff{},
|
||||
"lorem": &terraform.InstanceDiff{},
|
||||
"ipsum": &terraform.InstanceDiff{},
|
||||
resources := map[string]*legacy.InstanceDiff{
|
||||
"foo": &legacy.InstanceDiff{},
|
||||
"bar": &legacy.InstanceDiff{},
|
||||
"lorem": &legacy.InstanceDiff{},
|
||||
"ipsum": &legacy.InstanceDiff{},
|
||||
}
|
||||
|
||||
for k := range resources {
|
||||
|
@ -261,23 +263,23 @@ func TestCountHookPostDiff_DataSource(t *testing.T) {
|
|||
func TestCountHookApply_ChangeOnly(t *testing.T) {
|
||||
h := new(CountHook)
|
||||
|
||||
resources := map[string]*terraform.InstanceDiff{
|
||||
"foo": &terraform.InstanceDiff{
|
||||
resources := map[string]*legacy.InstanceDiff{
|
||||
"foo": &legacy.InstanceDiff{
|
||||
Destroy: false,
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{},
|
||||
Attributes: map[string]*legacy.ResourceAttrDiff{
|
||||
"foo": &legacy.ResourceAttrDiff{},
|
||||
},
|
||||
},
|
||||
"bar": &terraform.InstanceDiff{
|
||||
"bar": &legacy.InstanceDiff{
|
||||
Destroy: false,
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{},
|
||||
Attributes: map[string]*legacy.ResourceAttrDiff{
|
||||
"foo": &legacy.ResourceAttrDiff{},
|
||||
},
|
||||
},
|
||||
"lorem": &terraform.InstanceDiff{
|
||||
"lorem": &legacy.InstanceDiff{
|
||||
Destroy: false,
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{},
|
||||
Attributes: map[string]*legacy.ResourceAttrDiff{
|
||||
"foo": &legacy.ResourceAttrDiff{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -306,11 +308,11 @@ func TestCountHookApply_ChangeOnly(t *testing.T) {
|
|||
func TestCountHookApply_DestroyOnly(t *testing.T) {
|
||||
h := new(CountHook)
|
||||
|
||||
resources := map[string]*terraform.InstanceDiff{
|
||||
"foo": &terraform.InstanceDiff{Destroy: true},
|
||||
"bar": &terraform.InstanceDiff{Destroy: true},
|
||||
"lorem": &terraform.InstanceDiff{Destroy: true},
|
||||
"ipsum": &terraform.InstanceDiff{Destroy: true},
|
||||
resources := map[string]*legacy.InstanceDiff{
|
||||
"foo": &legacy.InstanceDiff{Destroy: true},
|
||||
"bar": &legacy.InstanceDiff{Destroy: true},
|
||||
"lorem": &legacy.InstanceDiff{Destroy: true},
|
||||
"ipsum": &legacy.InstanceDiff{Destroy: true},
|
||||
}
|
||||
|
||||
for k := range resources {
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
"time"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/internal/legacy/terraform"
|
||||
"github.com/hashicorp/terraform/states/statemgr"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// LocalState manages a state storage that is local to the filesystem.
|
||||
|
|
|
@ -41,6 +41,7 @@ import (
|
|||
|
||||
backendInit "github.com/hashicorp/terraform/backend/init"
|
||||
backendLocal "github.com/hashicorp/terraform/backend/local"
|
||||
legacy "github.com/hashicorp/terraform/internal/legacy/terraform"
|
||||
_ "github.com/hashicorp/terraform/internal/logging"
|
||||
)
|
||||
|
||||
|
@ -404,7 +405,7 @@ func testStateFileWorkspaceDefault(t *testing.T, workspace string, s *states.Sta
|
|||
|
||||
// testStateFileRemote writes the state out to the remote statefile
|
||||
// in the cwd. Use `testCwd` to change into a temp cwd.
|
||||
func testStateFileRemote(t *testing.T, s *terraform.State) string {
|
||||
func testStateFileRemote(t *testing.T, s *legacy.State) string {
|
||||
t.Helper()
|
||||
|
||||
path := filepath.Join(DefaultDataDir, DefaultStateFilename)
|
||||
|
@ -418,7 +419,7 @@ func testStateFileRemote(t *testing.T, s *terraform.State) string {
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := terraform.WriteState(s, f); err != nil {
|
||||
if err := legacy.WriteState(s, f); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
|
@ -446,9 +447,9 @@ func testStateRead(t *testing.T, path string) *states.State {
|
|||
// testDataStateRead reads a "data state", which is a file format resembling
|
||||
// our state format v3 that is used only to track current backend settings.
|
||||
//
|
||||
// This old format still uses *terraform.State, but should be replaced with
|
||||
// This old format still uses *legacy.State, but should be replaced with
|
||||
// a more specialized type in a later release.
|
||||
func testDataStateRead(t *testing.T, path string) *terraform.State {
|
||||
func testDataStateRead(t *testing.T, path string) *legacy.State {
|
||||
t.Helper()
|
||||
|
||||
f, err := os.Open(path)
|
||||
|
@ -457,7 +458,7 @@ func testDataStateRead(t *testing.T, path string) *terraform.State {
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
s, err := terraform.ReadState(f)
|
||||
s, err := legacy.ReadState(f)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -719,7 +720,7 @@ func testInputMap(t *testing.T, answers map[string]string) func() {
|
|||
// be returned about the backend configuration having changed and that
|
||||
// "terraform init" must be run, since the test backend config cache created
|
||||
// by this function contains the hash for an empty configuration.
|
||||
func testBackendState(t *testing.T, s *states.State, c int) (*terraform.State, *httptest.Server) {
|
||||
func testBackendState(t *testing.T, s *states.State, c int) (*legacy.State, *httptest.Server) {
|
||||
t.Helper()
|
||||
|
||||
var b64md5 string
|
||||
|
@ -759,8 +760,8 @@ func testBackendState(t *testing.T, s *states.State, c int) (*terraform.State, *
|
|||
configSchema := b.ConfigSchema()
|
||||
hash := backendConfig.Hash(configSchema)
|
||||
|
||||
state := terraform.NewState()
|
||||
state.Backend = &terraform.BackendState{
|
||||
state := legacy.NewState()
|
||||
state.Backend = &legacy.BackendState{
|
||||
Type: "http",
|
||||
ConfigRaw: json.RawMessage(fmt.Sprintf(`{"address":%q}`, srv.URL)),
|
||||
Hash: uint64(hash),
|
||||
|
@ -772,10 +773,10 @@ func testBackendState(t *testing.T, s *states.State, c int) (*terraform.State, *
|
|||
// testRemoteState is used to make a test HTTP server to return a given
|
||||
// state file that can be used for testing legacy remote state.
|
||||
//
|
||||
// The return values are a *terraform.State instance that should be written
|
||||
// The return values are a *legacy.State instance that should be written
|
||||
// as the "data state" (really: backend state) and the server that the
|
||||
// returned data state refers to.
|
||||
func testRemoteState(t *testing.T, s *states.State, c int) (*terraform.State, *httptest.Server) {
|
||||
func testRemoteState(t *testing.T, s *states.State, c int) (*legacy.State, *httptest.Server) {
|
||||
t.Helper()
|
||||
|
||||
var b64md5 string
|
||||
|
@ -795,10 +796,10 @@ func testRemoteState(t *testing.T, s *states.State, c int) (*terraform.State, *h
|
|||
resp.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
retState := terraform.NewState()
|
||||
retState := legacy.NewState()
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(cb))
|
||||
b := &terraform.BackendState{
|
||||
b := &legacy.BackendState{
|
||||
Type: "http",
|
||||
}
|
||||
b.SetConfig(cty.ObjectVal(map[string]cty.Value{
|
||||
|
|
|
@ -31,6 +31,8 @@ import (
|
|||
"github.com/hashicorp/terraform/tfdiags"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/colorstring"
|
||||
|
||||
legacy "github.com/hashicorp/terraform/internal/legacy/terraform"
|
||||
)
|
||||
|
||||
// Meta are the meta-options that are available on all or most commands.
|
||||
|
@ -147,7 +149,7 @@ type Meta struct {
|
|||
configLoader *configload.Loader
|
||||
|
||||
// backendState is the currently active backend state
|
||||
backendState *terraform.BackendState
|
||||
backendState *legacy.BackendState
|
||||
|
||||
// Variables for the context (private)
|
||||
variableArgs rawFlags
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
|
||||
backendInit "github.com/hashicorp/terraform/backend/init"
|
||||
backendLocal "github.com/hashicorp/terraform/backend/local"
|
||||
legacy "github.com/hashicorp/terraform/internal/legacy/terraform"
|
||||
)
|
||||
|
||||
// BackendOpts are the options used to initialize a backend.Backend.
|
||||
|
@ -160,7 +161,7 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
|
|||
// with inside backendFromConfig, because we still need that codepath
|
||||
// to be able to recognize the lack of a config as distinct from
|
||||
// explicitly setting local until we do some more refactoring here.
|
||||
m.backendState = &terraform.BackendState{
|
||||
m.backendState = &legacy.BackendState{
|
||||
Type: "local",
|
||||
ConfigRaw: json.RawMessage("{}"),
|
||||
}
|
||||
|
@ -461,7 +462,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
|||
s := sMgr.State()
|
||||
if s == nil {
|
||||
log.Printf("[TRACE] Meta.Backend: backend has not previously been initialized in this working directory")
|
||||
s = terraform.NewState()
|
||||
s = legacy.NewState()
|
||||
} else if s.Backend != nil {
|
||||
log.Printf("[TRACE] Meta.Backend: working directory was previously initialized for %q backend", s.Backend.Type)
|
||||
} else {
|
||||
|
@ -818,9 +819,9 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local
|
|||
// Store the metadata in our saved state location
|
||||
s := sMgr.State()
|
||||
if s == nil {
|
||||
s = terraform.NewState()
|
||||
s = legacy.NewState()
|
||||
}
|
||||
s.Backend = &terraform.BackendState{
|
||||
s.Backend = &legacy.BackendState{
|
||||
Type: c.Type,
|
||||
ConfigRaw: json.RawMessage(configJSON),
|
||||
Hash: uint64(cHash),
|
||||
|
@ -902,9 +903,9 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista
|
|||
// Update the backend state
|
||||
s = sMgr.State()
|
||||
if s == nil {
|
||||
s = terraform.NewState()
|
||||
s = legacy.NewState()
|
||||
}
|
||||
s.Backend = &terraform.BackendState{
|
||||
s.Backend = &legacy.BackendState{
|
||||
Type: c.Type,
|
||||
ConfigRaw: json.RawMessage(configJSON),
|
||||
Hash: uint64(cHash),
|
||||
|
@ -996,7 +997,7 @@ func (m *Meta) backend_C_r_S_unchanged(c *configs.Backend, cHash int, sMgr *clis
|
|||
// this function will conservatively assume that migration is required,
|
||||
// expecting that the migration code will subsequently deal with the same
|
||||
// errors.
|
||||
func (m *Meta) backendConfigNeedsMigration(c *configs.Backend, s *terraform.BackendState) bool {
|
||||
func (m *Meta) backendConfigNeedsMigration(c *configs.Backend, s *legacy.BackendState) bool {
|
||||
if s == nil || s.Empty() {
|
||||
log.Print("[TRACE] backendConfigNeedsMigration: no cached config, so migration is required")
|
||||
return true
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
tfplugin "github.com/hashicorp/terraform/plugin"
|
||||
"github.com/hashicorp/terraform/plugin/discovery"
|
||||
"github.com/hashicorp/terraform/provisioners"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// NOTE WELL: The logic in this file is primarily about plugin types OTHER THAN
|
||||
|
@ -120,7 +119,7 @@ func (m *Meta) pluginDirs(includeAutoInstalled bool) []string {
|
|||
return dirs
|
||||
}
|
||||
|
||||
func (m *Meta) provisionerFactories() map[string]terraform.ProvisionerFactory {
|
||||
func (m *Meta) provisionerFactories() map[string]provisioners.Factory {
|
||||
dirs := m.pluginDirs(true)
|
||||
plugins := discovery.FindPlugins("provisioner", dirs)
|
||||
plugins, _ = plugins.ValidateVersions()
|
||||
|
@ -131,7 +130,7 @@ func (m *Meta) provisionerFactories() map[string]terraform.ProvisionerFactory {
|
|||
// name here, even though the discovery interface forces us to pretend
|
||||
// that might not be true.
|
||||
|
||||
factories := make(map[string]terraform.ProvisionerFactory)
|
||||
factories := make(map[string]provisioners.Factory)
|
||||
|
||||
// Wire up the internal provisioners first. These might be overridden
|
||||
// by discovered provisioners below.
|
||||
|
@ -175,7 +174,7 @@ func internalPluginClient(kind, name string) (*plugin.Client, error) {
|
|||
return plugin.NewClient(cfg), nil
|
||||
}
|
||||
|
||||
func provisionerFactory(meta discovery.PluginMeta) terraform.ProvisionerFactory {
|
||||
func provisionerFactory(meta discovery.PluginMeta) provisioners.Factory {
|
||||
return func() (provisioners.Interface, error) {
|
||||
cfg := &plugin.ClientConfig{
|
||||
Cmd: exec.Command(meta.Path),
|
||||
|
@ -191,7 +190,7 @@ func provisionerFactory(meta discovery.PluginMeta) terraform.ProvisionerFactory
|
|||
}
|
||||
}
|
||||
|
||||
func internalProvisionerFactory(meta discovery.PluginMeta) terraform.ProvisionerFactory {
|
||||
func internalProvisionerFactory(meta discovery.PluginMeta) provisioners.Factory {
|
||||
return func() (provisioners.Interface, error) {
|
||||
client, err := internalPluginClient("provisioner", meta.Name)
|
||||
if err != nil {
|
||||
|
|
|
@ -5,8 +5,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/backend/remote-state/inmem"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
|
||||
legacy "github.com/hashicorp/terraform/internal/legacy/terraform"
|
||||
)
|
||||
|
||||
// Since we can't unlock a local state file, just test that calling unlock
|
||||
|
@ -24,7 +25,7 @@ func TestUnlock(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
err = terraform.WriteState(terraform.NewState(), f)
|
||||
err = legacy.WriteState(legacy.NewState(), f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
|
|
@ -13,8 +13,9 @@ import (
|
|||
"github.com/hashicorp/terraform/backend/remote-state/inmem"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/hashicorp/terraform/states/statemgr"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
|
||||
legacy "github.com/hashicorp/terraform/internal/legacy/terraform"
|
||||
)
|
||||
|
||||
func TestWorkspace_createAndChange(t *testing.T) {
|
||||
|
@ -379,14 +380,14 @@ func TestWorkspace_deleteWithState(t *testing.T) {
|
|||
}
|
||||
|
||||
// create a non-empty state
|
||||
originalState := &terraform.State{
|
||||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
originalState := &legacy.State{
|
||||
Modules: []*legacy.ModuleState{
|
||||
&legacy.ModuleState{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*terraform.ResourceState{
|
||||
"test_instance.foo": &terraform.ResourceState{
|
||||
Resources: map[string]*legacy.ResourceState{
|
||||
"test_instance.foo": &legacy.ResourceState{
|
||||
Type: "test_instance",
|
||||
Primary: &terraform.InstanceState{
|
||||
Primary: &legacy.InstanceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
|
@ -400,7 +401,7 @@ func TestWorkspace_deleteWithState(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := terraform.WriteState(originalState, f); err != nil {
|
||||
if err := legacy.WriteState(originalState, f); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
@ -17,7 +16,6 @@ import (
|
|||
"github.com/hashicorp/terraform/providers"
|
||||
"github.com/hashicorp/terraform/provisioners"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/hashicorp/terraform/states/statefile"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
|
@ -897,37 +895,3 @@ func (c *Context) watchStop(walker *ContextGraphWalker) (chan struct{}, <-chan s
|
|||
|
||||
return stop, wait
|
||||
}
|
||||
|
||||
// ShimLegacyState is a helper that takes the legacy state type and
|
||||
// converts it to the new state type.
|
||||
//
|
||||
// This is implemented as a state file upgrade, so it will not preserve
|
||||
// parts of the state structure that are not included in a serialized state,
|
||||
// such as the resolved results of any local values, outputs in non-root
|
||||
// modules, etc.
|
||||
func ShimLegacyState(legacy *State) (*states.State, error) {
|
||||
if legacy == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err := WriteState(legacy, &buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := statefile.Read(&buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.State, err
|
||||
}
|
||||
|
||||
// MustShimLegacyState is a wrapper around ShimLegacyState that panics if
|
||||
// the conversion does not succeed. This is primarily intended for tests where
|
||||
// the given legacy state is an object constructed within the test.
|
||||
func MustShimLegacyState(legacy *State) *states.State {
|
||||
ret, err := ShimLegacyState(legacy)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -1864,7 +1864,7 @@ func TestContext2Apply_cancelProvisioner(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -2302,7 +2302,7 @@ func TestContext2Apply_provisionerInterpCount(t *testing.T) {
|
|||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
}
|
||||
|
||||
provisioners := map[string]ProvisionerFactory{
|
||||
provisioners := map[string]provisioners.Factory{
|
||||
"local-exec": testProvisionerFuncFixed(pr),
|
||||
}
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
|
@ -3484,7 +3484,7 @@ func TestContext2Apply_multiVarComprehensive(t *testing.T) {
|
|||
m := testModule(t, "apply-multi-var-comprehensive")
|
||||
p := testProvider("test")
|
||||
|
||||
configs := map[string]*ResourceConfig{}
|
||||
configs := map[string]cty.Value{}
|
||||
var configsLock sync.Mutex
|
||||
|
||||
p.ApplyResourceChangeFn = testApplyFn
|
||||
|
@ -3498,7 +3498,7 @@ func TestContext2Apply_multiVarComprehensive(t *testing.T) {
|
|||
// and so the assertions below expect an old-style ResourceConfig, which
|
||||
// we'll construct via our shim for now to avoid rewriting all of the
|
||||
// assertions.
|
||||
configs[key] = NewResourceConfigShimmed(req.Config, p.GetSchemaReturn.ResourceTypes["test_thing"])
|
||||
configs[key] = req.ProposedNewState
|
||||
|
||||
retVals := make(map[string]cty.Value)
|
||||
for it := proposed.ElementIterator(); it.Next(); {
|
||||
|
@ -3563,102 +3563,99 @@ func TestContext2Apply_multiVarComprehensive(t *testing.T) {
|
|||
t.Fatalf("errors during plan")
|
||||
}
|
||||
|
||||
checkConfig := func(key string, want map[string]interface{}) {
|
||||
checkConfig := func(key string, want cty.Value) {
|
||||
configsLock.Lock()
|
||||
defer configsLock.Unlock()
|
||||
|
||||
if _, ok := configs[key]; !ok {
|
||||
got, ok := configs[key]
|
||||
if !ok {
|
||||
t.Errorf("no config recorded for %s; expected a configuration", key)
|
||||
return
|
||||
}
|
||||
got := configs[key].Config
|
||||
|
||||
t.Run("config for "+key, func(t *testing.T) {
|
||||
want["key"] = key // to avoid doing this for every example
|
||||
for _, problem := range deep.Equal(got, want) {
|
||||
t.Errorf(problem)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
checkConfig("multi_count_var.0", map[string]interface{}{
|
||||
"source_id": hcl2shim.UnknownVariableValue,
|
||||
"source_name": "source.0",
|
||||
})
|
||||
checkConfig("multi_count_var.2", map[string]interface{}{
|
||||
"source_id": hcl2shim.UnknownVariableValue,
|
||||
"source_name": "source.2",
|
||||
})
|
||||
checkConfig("multi_count_derived.0", map[string]interface{}{
|
||||
"source_id": hcl2shim.UnknownVariableValue,
|
||||
"source_name": "source.0",
|
||||
})
|
||||
checkConfig("multi_count_derived.2", map[string]interface{}{
|
||||
"source_id": hcl2shim.UnknownVariableValue,
|
||||
"source_name": "source.2",
|
||||
})
|
||||
checkConfig("whole_splat", map[string]interface{}{
|
||||
"source_ids": []interface{}{
|
||||
hcl2shim.UnknownVariableValue,
|
||||
hcl2shim.UnknownVariableValue,
|
||||
hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
"source_names": []interface{}{
|
||||
"source.0",
|
||||
"source.1",
|
||||
"source.2",
|
||||
},
|
||||
"source_ids_from_func": hcl2shim.UnknownVariableValue,
|
||||
"source_names_from_func": []interface{}{
|
||||
"source.0",
|
||||
"source.1",
|
||||
"source.2",
|
||||
},
|
||||
|
||||
"source_ids_wrapped": []interface{}{
|
||||
[]interface{}{
|
||||
hcl2shim.UnknownVariableValue,
|
||||
hcl2shim.UnknownVariableValue,
|
||||
hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
},
|
||||
"source_names_wrapped": []interface{}{
|
||||
[]interface{}{
|
||||
"source.0",
|
||||
"source.1",
|
||||
"source.2",
|
||||
},
|
||||
},
|
||||
|
||||
"first_source_id": hcl2shim.UnknownVariableValue,
|
||||
"first_source_name": "source.0",
|
||||
})
|
||||
checkConfig("child.whole_splat", map[string]interface{}{
|
||||
"source_ids": []interface{}{
|
||||
hcl2shim.UnknownVariableValue,
|
||||
hcl2shim.UnknownVariableValue,
|
||||
hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
"source_names": []interface{}{
|
||||
"source.0",
|
||||
"source.1",
|
||||
"source.2",
|
||||
},
|
||||
|
||||
"source_ids_wrapped": []interface{}{
|
||||
[]interface{}{
|
||||
hcl2shim.UnknownVariableValue,
|
||||
hcl2shim.UnknownVariableValue,
|
||||
hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
},
|
||||
"source_names_wrapped": []interface{}{
|
||||
[]interface{}{
|
||||
"source.0",
|
||||
"source.1",
|
||||
"source.2",
|
||||
},
|
||||
},
|
||||
})
|
||||
checkConfig("multi_count_var.0", cty.ObjectVal(map[string]cty.Value{
|
||||
"source_id": cty.UnknownVal(cty.String),
|
||||
"source_name": cty.StringVal("source.0"),
|
||||
}))
|
||||
checkConfig("multi_count_var.2", cty.ObjectVal(map[string]cty.Value{
|
||||
"source_id": cty.UnknownVal(cty.String),
|
||||
"source_name": cty.StringVal("source.2"),
|
||||
}))
|
||||
checkConfig("multi_count_derived.0", cty.ObjectVal(map[string]cty.Value{
|
||||
"source_id": cty.UnknownVal(cty.String),
|
||||
"source_name": cty.StringVal("source.0"),
|
||||
}))
|
||||
checkConfig("multi_count_derived.2", cty.ObjectVal(map[string]cty.Value{
|
||||
"source_id": cty.UnknownVal(cty.String),
|
||||
"source_name": cty.StringVal("source.2"),
|
||||
}))
|
||||
checkConfig("whole_splat", cty.ObjectVal(map[string]cty.Value{
|
||||
"source_ids": cty.ListVal([]cty.Value{
|
||||
cty.UnknownVal(cty.String),
|
||||
cty.UnknownVal(cty.String),
|
||||
cty.UnknownVal(cty.String),
|
||||
}),
|
||||
"source_names": cty.ListVal([]cty.Value{
|
||||
cty.StringVal("source.0"),
|
||||
cty.StringVal("source.1"),
|
||||
cty.StringVal("source.2"),
|
||||
}),
|
||||
"source_ids_from_func": cty.UnknownVal(cty.String),
|
||||
"source_names_from_func": cty.ListVal([]cty.Value{
|
||||
cty.StringVal("source.0"),
|
||||
cty.StringVal("source.1"),
|
||||
cty.StringVal("source.2"),
|
||||
}),
|
||||
"source_ids_wrapped": cty.ListVal([]cty.Value{
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.UnknownVal(cty.String),
|
||||
cty.UnknownVal(cty.String),
|
||||
cty.UnknownVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
"source_names_wrapped": cty.ListVal([]cty.Value{
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("source.0"),
|
||||
cty.StringVal("source.1"),
|
||||
cty.StringVal("source.2"),
|
||||
}),
|
||||
}),
|
||||
"first_source_id": cty.UnknownVal(cty.String),
|
||||
"first_source_name": cty.StringVal("source.0"),
|
||||
}))
|
||||
checkConfig("child.whole_splat", cty.ObjectVal(map[string]cty.Value{
|
||||
"source_ids": cty.ListVal([]cty.Value{
|
||||
cty.UnknownVal(cty.String),
|
||||
cty.UnknownVal(cty.String),
|
||||
cty.UnknownVal(cty.String),
|
||||
}),
|
||||
"source_names": cty.ListVal([]cty.Value{
|
||||
cty.StringVal("source.0"),
|
||||
cty.StringVal("source.1"),
|
||||
cty.StringVal("source.2"),
|
||||
}),
|
||||
"source_ids_wrapped": cty.ListVal([]cty.Value{
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.UnknownVal(cty.String),
|
||||
cty.UnknownVal(cty.String),
|
||||
cty.UnknownVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
"source_names_wrapped": cty.ListVal([]cty.Value{
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("source.0"),
|
||||
cty.StringVal("source.1"),
|
||||
cty.StringVal("source.2"),
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
t.Run("apply", func(t *testing.T) {
|
||||
state, diags := ctx.Apply()
|
||||
|
@ -4086,7 +4083,7 @@ func TestContext2Apply_provisionerModule(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -4135,7 +4132,7 @@ func TestContext2Apply_Provisioner_compute(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
Variables: InputValues{
|
||||
|
@ -4193,7 +4190,7 @@ func TestContext2Apply_provisionerCreateFail(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -4230,7 +4227,7 @@ func TestContext2Apply_provisionerCreateFailNoId(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -4267,7 +4264,7 @@ func TestContext2Apply_provisionerFail(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -4315,7 +4312,7 @@ func TestContext2Apply_provisionerFail_createBeforeDestroy(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
State: state,
|
||||
|
@ -4666,7 +4663,7 @@ func TestContext2Apply_provisionerFailContinue(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -4714,7 +4711,7 @@ func TestContext2Apply_provisionerFailContinueHook(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -4768,7 +4765,7 @@ func TestContext2Apply_provisionerDestroy(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -4820,7 +4817,7 @@ func TestContext2Apply_provisionerDestroyFail(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -4889,7 +4886,7 @@ func TestContext2Apply_provisionerDestroyFailContinue(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -4959,7 +4956,7 @@ func TestContext2Apply_provisionerDestroyFailContinueFail(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -5026,7 +5023,7 @@ func TestContext2Apply_provisionerDestroyTainted(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
Variables: InputValues{
|
||||
|
@ -5087,7 +5084,7 @@ func TestContext2Apply_provisionerResourceRef(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -5133,7 +5130,7 @@ func TestContext2Apply_provisionerSelfRef(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -5186,7 +5183,7 @@ func TestContext2Apply_provisionerMultiSelfRef(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -5246,7 +5243,7 @@ func TestContext2Apply_provisionerMultiSelfRefSingle(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -5301,7 +5298,7 @@ func TestContext2Apply_provisionerExplicitSelfRef(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -5330,7 +5327,7 @@ func TestContext2Apply_provisionerExplicitSelfRef(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -5370,7 +5367,7 @@ func TestContext2Apply_provisionerForEachSelfRef(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -5397,7 +5394,7 @@ func TestContext2Apply_Provisioner_Diff(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -5445,7 +5442,7 @@ func TestContext2Apply_Provisioner_Diff(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
State: state,
|
||||
|
@ -6250,7 +6247,7 @@ func TestContext2Apply_destroyTaintedProvisioner(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
State: state,
|
||||
|
@ -9834,7 +9831,7 @@ func TestContext2Apply_plannedConnectionRefs(t *testing.T) {
|
|||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
}
|
||||
|
||||
provisioners := map[string]ProvisionerFactory{
|
||||
provisioners := map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
}
|
||||
|
||||
|
@ -12194,7 +12191,7 @@ func TestContext2Apply_provisionerSensitive(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
Variables: InputValues{
|
||||
|
|
|
@ -26,7 +26,7 @@ type contextComponentFactory interface {
|
|||
// basicComponentFactory just calls a factory from a map directly.
|
||||
type basicComponentFactory struct {
|
||||
providers map[addrs.Provider]providers.Factory
|
||||
provisioners map[string]ProvisionerFactory
|
||||
provisioners map[string]provisioners.Factory
|
||||
}
|
||||
|
||||
func (c *basicComponentFactory) ResourceProviders() []string {
|
||||
|
|
|
@ -32,7 +32,7 @@ func simpleMockComponentFactory() *basicComponentFactory {
|
|||
return provider, nil
|
||||
},
|
||||
},
|
||||
provisioners: map[string]ProvisionerFactory{
|
||||
provisioners: map[string]provisioners.Factory{
|
||||
"test": func() (provisioners.Interface, error) {
|
||||
return provisioner, nil
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/hashicorp/terraform/providers"
|
||||
"github.com/hashicorp/terraform/provisioners"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
|
@ -16,7 +17,7 @@ import (
|
|||
type contextTestFixture struct {
|
||||
Config *configs.Config
|
||||
Providers map[addrs.Provider]providers.Factory
|
||||
Provisioners map[string]ProvisionerFactory
|
||||
Provisioners map[string]provisioners.Factory
|
||||
}
|
||||
|
||||
// ContextOpts returns a ContextOps pre-populated with the elements of this
|
||||
|
|
|
@ -22,10 +22,14 @@ func TestContextImport_basic(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -59,10 +63,14 @@ func TestContextImport_countIndex(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -117,10 +125,14 @@ func TestContextImport_collision(t *testing.T) {
|
|||
}),
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -152,9 +164,13 @@ func TestContextImport_missingType(t *testing.T) {
|
|||
p := testProvider("aws")
|
||||
m := testModule(t, "import-provider")
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -189,10 +205,14 @@ func TestContextImport_missingType(t *testing.T) {
|
|||
func TestContextImport_moduleProvider(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -249,10 +269,14 @@ func TestContextImport_providerModule(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -317,10 +341,14 @@ func TestContextImport_providerConfig(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -368,10 +396,14 @@ func TestContextImport_providerConfigResources(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -403,10 +435,14 @@ func TestContextImport_refresh(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -450,10 +486,14 @@ func TestContextImport_refreshNil(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -494,10 +534,14 @@ func TestContextImport_module(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -532,10 +576,14 @@ func TestContextImport_moduleDepth2(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -570,10 +618,14 @@ func TestContextImport_moduleDiff(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -622,14 +674,20 @@ func TestContextImport_multiState(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
},
|
||||
&InstanceState{
|
||||
ID: "bar",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance_thing"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
{
|
||||
TypeName: "aws_instance_thing",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("bar"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -685,18 +743,26 @@ func TestContextImport_multiStateSame(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
p.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "foo",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
},
|
||||
&InstanceState{
|
||||
ID: "bar",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance_thing"},
|
||||
},
|
||||
&InstanceState{
|
||||
ID: "qux",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance_thing"},
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
{
|
||||
TypeName: "aws_instance_thing",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("bar"),
|
||||
}),
|
||||
},
|
||||
{
|
||||
TypeName: "aws_instance_thing",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("qux"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -778,6 +844,16 @@ resource "test_resource" "unused" {
|
|||
},
|
||||
}
|
||||
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "test_resource",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("test"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||
"github.com/hashicorp/terraform/plans"
|
||||
"github.com/hashicorp/terraform/providers"
|
||||
"github.com/hashicorp/terraform/provisioners"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
@ -930,7 +931,7 @@ func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
State: state,
|
||||
|
@ -1654,7 +1655,7 @@ func TestContext2Plan_provisionerCycle(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"local-exec": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
|
|
@ -443,20 +443,6 @@ func checkStateString(t *testing.T, state *states.State, expected string) {
|
|||
}
|
||||
}
|
||||
|
||||
func resourceState(resourceType, resourceID string) *ResourceState {
|
||||
providerResource := strings.Split(resourceType, "_")
|
||||
return &ResourceState{
|
||||
Type: resourceType,
|
||||
Primary: &InstanceState{
|
||||
ID: resourceID,
|
||||
Attributes: map[string]string{
|
||||
"id": resourceID,
|
||||
},
|
||||
},
|
||||
Provider: "provider." + providerResource[0],
|
||||
}
|
||||
}
|
||||
|
||||
// Test helper that gives a function 3 seconds to finish, assumes deadlock and
|
||||
// fails test if it does not.
|
||||
func testCheckDeadlock(t *testing.T, f func()) {
|
||||
|
|
|
@ -709,7 +709,7 @@ func TestContext2Validate_provisionerConfig_bad(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -744,7 +744,7 @@ func TestContext2Validate_badResourceConnection(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -776,7 +776,7 @@ func TestContext2Validate_badProvisionerConnection(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -822,7 +822,7 @@ func TestContext2Validate_provisionerConfig_good(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
})
|
||||
|
@ -983,7 +983,7 @@ func TestContext2Validate_targetedDestroy(t *testing.T) {
|
|||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
||||
},
|
||||
Provisioners: map[string]ProvisionerFactory{
|
||||
Provisioners: map[string]provisioners.Factory{
|
||||
"shell": testProvisionerFuncFixed(pr),
|
||||
},
|
||||
State: state,
|
||||
|
|
1451
terraform/diff.go
1451
terraform/diff.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -26,18 +26,28 @@ func TestEvalReadState(t *testing.T) {
|
|||
provider := providers.Interface(mockProvider)
|
||||
|
||||
cases := map[string]struct {
|
||||
Resources map[string]*ResourceState
|
||||
State *states.State
|
||||
Node *EvalReadState
|
||||
ExpectedInstanceId string
|
||||
}{
|
||||
"ReadState gets primary instance state": {
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.bar": &ResourceState{
|
||||
Primary: &InstanceState{
|
||||
ID: "i-abc123",
|
||||
},
|
||||
},
|
||||
},
|
||||
State: states.BuildState(func(s *states.SyncState) {
|
||||
providerAddr := addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("aws"),
|
||||
Module: addrs.RootModule,
|
||||
}
|
||||
oneAddr := addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "bar",
|
||||
}.Absolute(addrs.RootModuleInstance)
|
||||
s.SetResourceProvider(oneAddr, providerAddr)
|
||||
s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.NoKey), &states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
||||
}, providerAddr)
|
||||
}),
|
||||
|
||||
Node: &EvalReadState{
|
||||
Addr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
|
@ -56,15 +66,7 @@ func TestEvalReadState(t *testing.T) {
|
|||
for k, c := range cases {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
ctx := new(MockEvalContext)
|
||||
state := MustShimLegacyState(&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: c.Resources,
|
||||
},
|
||||
},
|
||||
})
|
||||
ctx.StateState = state.SyncWrapper()
|
||||
ctx.StateState = c.State.SyncWrapper()
|
||||
ctx.PathPath = addrs.RootModuleInstance
|
||||
|
||||
diags := c.Node.Eval(ctx)
|
||||
|
@ -97,18 +99,28 @@ func TestEvalReadStateDeposed(t *testing.T) {
|
|||
provider := providers.Interface(mockProvider)
|
||||
|
||||
cases := map[string]struct {
|
||||
Resources map[string]*ResourceState
|
||||
State *states.State
|
||||
Node *EvalReadStateDeposed
|
||||
ExpectedInstanceId string
|
||||
}{
|
||||
"ReadStateDeposed gets deposed instance": {
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.bar": &ResourceState{
|
||||
Deposed: []*InstanceState{
|
||||
&InstanceState{ID: "i-abc123"},
|
||||
},
|
||||
},
|
||||
},
|
||||
State: states.BuildState(func(s *states.SyncState) {
|
||||
providerAddr := addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("aws"),
|
||||
Module: addrs.RootModule,
|
||||
}
|
||||
oneAddr := addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "bar",
|
||||
}.Absolute(addrs.RootModuleInstance)
|
||||
s.SetResourceProvider(oneAddr, providerAddr)
|
||||
s.SetResourceInstanceDeposed(oneAddr.Instance(addrs.NoKey), states.DeposedKey("00000001"), &states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
||||
}, providerAddr)
|
||||
}),
|
||||
|
||||
Node: &EvalReadStateDeposed{
|
||||
Addr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
|
@ -127,15 +139,7 @@ func TestEvalReadStateDeposed(t *testing.T) {
|
|||
for k, c := range cases {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
ctx := new(MockEvalContext)
|
||||
state := MustShimLegacyState(&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: c.Resources,
|
||||
},
|
||||
},
|
||||
})
|
||||
ctx.StateState = state.SyncWrapper()
|
||||
ctx.StateState = c.State.SyncWrapper()
|
||||
ctx.PathPath = addrs.RootModuleInstance
|
||||
|
||||
diags := c.Node.Eval(ctx)
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
package terraform
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=InstanceType instancetype.go
|
||||
|
||||
// InstanceType is an enum of the various types of instances store in the State
|
||||
type InstanceType int
|
||||
|
||||
const (
|
||||
TypeInvalid InstanceType = iota
|
||||
TypePrimary
|
||||
TypeTainted
|
||||
TypeDeposed
|
||||
)
|
|
@ -1,26 +0,0 @@
|
|||
// Code generated by "stringer -type=InstanceType instancetype.go"; DO NOT EDIT.
|
||||
|
||||
package terraform
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[TypeInvalid-0]
|
||||
_ = x[TypePrimary-1]
|
||||
_ = x[TypeTainted-2]
|
||||
_ = x[TypeDeposed-3]
|
||||
}
|
||||
|
||||
const _InstanceType_name = "TypeInvalidTypePrimaryTypeTaintedTypeDeposed"
|
||||
|
||||
var _InstanceType_index = [...]uint8{0, 11, 22, 33, 44}
|
||||
|
||||
func (i InstanceType) String() string {
|
||||
if i < 0 || i >= InstanceType(len(_InstanceType_index)-1) {
|
||||
return "InstanceType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _InstanceType_name[_InstanceType_index[i]:_InstanceType_index[i+1]]
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(make([]interface{}, 0))
|
||||
gob.Register(make([]map[string]interface{}, 0))
|
||||
gob.Register(make(map[string]interface{}))
|
||||
gob.Register(make(map[string]string))
|
||||
}
|
||||
|
||||
// Plan represents a single Terraform execution plan, which contains
|
||||
// all the information necessary to make an infrastructure change.
|
||||
//
|
||||
// A plan has to contain basically the entire state of the world
|
||||
// necessary to make a change: the state, diff, config, backend config, etc.
|
||||
// This is so that it can run alone without any other data.
|
||||
type Plan struct {
|
||||
// Diff describes the resource actions that must be taken when this
|
||||
// plan is applied.
|
||||
Diff *Diff
|
||||
|
||||
// Config represents the entire configuration that was present when this
|
||||
// plan was created.
|
||||
Config *configs.Config
|
||||
|
||||
// State is the Terraform state that was current when this plan was
|
||||
// created.
|
||||
//
|
||||
// It is not allowed to apply a plan that has a stale state, since its
|
||||
// diff could be outdated.
|
||||
State *State
|
||||
|
||||
// Vars retains the variables that were set when creating the plan, so
|
||||
// that the same variables can be applied during apply.
|
||||
Vars map[string]cty.Value
|
||||
|
||||
// Targets, if non-empty, contains a set of resource address strings that
|
||||
// identify graph nodes that were selected as targets for plan.
|
||||
//
|
||||
// When targets are set, any graph node that is not directly targeted or
|
||||
// indirectly targeted via dependencies is excluded from the graph.
|
||||
Targets []string
|
||||
|
||||
// TerraformVersion is the version of Terraform that was used to create
|
||||
// this plan.
|
||||
//
|
||||
// It is not allowed to apply a plan created with a different version of
|
||||
// Terraform, since the other fields of this structure may be interpreted
|
||||
// in different ways between versions.
|
||||
TerraformVersion string
|
||||
|
||||
// ProviderSHA256s is a map giving the SHA256 hashes of the exact binaries
|
||||
// used as plugins for each provider during plan.
|
||||
//
|
||||
// These must match between plan and apply to ensure that the diff is
|
||||
// correctly interpreted, since different provider versions may have
|
||||
// different attributes or attribute value constraints.
|
||||
ProviderSHA256s map[string][]byte
|
||||
|
||||
// Backend is the backend that this plan should use and store data with.
|
||||
Backend *BackendState
|
||||
|
||||
// Destroy indicates that this plan was created for a full destroy operation
|
||||
Destroy bool
|
||||
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (p *Plan) String() string {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("DIFF:\n\n")
|
||||
buf.WriteString(p.Diff.String())
|
||||
buf.WriteString("\n\nSTATE:\n\n")
|
||||
buf.WriteString(p.State.String())
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (p *Plan) init() {
|
||||
p.once.Do(func() {
|
||||
if p.Diff == nil {
|
||||
p.Diff = new(Diff)
|
||||
p.Diff.init()
|
||||
}
|
||||
|
||||
if p.State == nil {
|
||||
p.State = new(State)
|
||||
p.State.init()
|
||||
}
|
||||
|
||||
if p.Vars == nil {
|
||||
p.Vars = make(map[string]cty.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// The format byte is prefixed into the plan file format so that we have
|
||||
// the ability in the future to change the file format if we want for any
|
||||
// reason.
|
||||
const planFormatMagic = "tfplan"
|
||||
const planFormatVersion byte = 2
|
||||
|
||||
// ReadPlan reads a plan structure out of a reader in the format that
|
||||
// was written by WritePlan.
|
||||
func ReadPlan(src io.Reader) (*Plan, error) {
|
||||
return nil, fmt.Errorf("terraform.ReadPlan is no longer in use; use planfile.Open instead")
|
||||
}
|
||||
|
||||
// WritePlan writes a plan somewhere in a binary format.
|
||||
func WritePlan(d *Plan, dst io.Writer) error {
|
||||
return fmt.Errorf("terraform.WritePlan is no longer in use; use planfile.Create instead")
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
@ -76,9 +76,6 @@ type MockProvider struct {
|
|||
ImportResourceStateResponse providers.ImportResourceStateResponse
|
||||
ImportResourceStateRequest providers.ImportResourceStateRequest
|
||||
ImportResourceStateFn func(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse
|
||||
// Legacy return type for existing tests, which will be shimmed into an
|
||||
// ImportResourceStateResponse if set
|
||||
ImportStateReturn []*InstanceState
|
||||
|
||||
ReadDataSourceCalled bool
|
||||
ReadDataSourceResponse providers.ReadDataSourceResponse
|
||||
|
@ -323,60 +320,34 @@ func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeReques
|
|||
return p.ApplyResourceChangeResponse
|
||||
}
|
||||
|
||||
func (p *MockProvider) ImportResourceState(r providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
|
||||
func (p *MockProvider) ImportResourceState(r providers.ImportResourceStateRequest) (resp providers.ImportResourceStateResponse) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if p.ImportStateReturn != nil {
|
||||
for _, is := range p.ImportStateReturn {
|
||||
if is.Attributes == nil {
|
||||
is.Attributes = make(map[string]string)
|
||||
}
|
||||
is.Attributes["id"] = is.ID
|
||||
|
||||
typeName := is.Ephemeral.Type
|
||||
// Use the requested type if the resource has no type of it's own.
|
||||
// We still return the empty type, which will error, but this prevents a panic.
|
||||
if typeName == "" {
|
||||
typeName = r.TypeName
|
||||
}
|
||||
|
||||
schema := p.GetSchemaReturn.ResourceTypes[typeName]
|
||||
if schema == nil {
|
||||
panic("no schema found for " + typeName)
|
||||
}
|
||||
|
||||
private, err := json.Marshal(is.Meta)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
state, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, schema.ImpliedType())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
state, err = schema.CoerceValue(state)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p.ImportResourceStateResponse.ImportedResources = append(
|
||||
p.ImportResourceStateResponse.ImportedResources,
|
||||
providers.ImportedResource{
|
||||
TypeName: is.Ephemeral.Type,
|
||||
State: state,
|
||||
Private: private,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
p.ImportResourceStateCalled = true
|
||||
p.ImportResourceStateRequest = r
|
||||
if p.ImportResourceStateFn != nil {
|
||||
return p.ImportResourceStateFn(r)
|
||||
}
|
||||
|
||||
// fixup the cty value to match the schema
|
||||
for i, res := range p.ImportResourceStateResponse.ImportedResources {
|
||||
schema := p.GetSchemaReturn.ResourceTypes[res.TypeName]
|
||||
if schema == nil {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(errors.New("no schema found for " + res.TypeName))
|
||||
return resp
|
||||
}
|
||||
|
||||
var err error
|
||||
res.State, err = schema.CoerceValue(res.State)
|
||||
if err != nil {
|
||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||
return resp
|
||||
}
|
||||
|
||||
p.ImportResourceStateResponse.ImportedResources[i] = res
|
||||
}
|
||||
|
||||
return p.ImportResourceStateResponse
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/provisioners"
|
||||
)
|
||||
|
||||
func TestMockResourceProvisioner_impl(t *testing.T) {
|
||||
var _ ResourceProvisioner = new(MockResourceProvisioner)
|
||||
}
|
||||
|
||||
// simpleMockProvisioner returns a MockProvisioner that is pre-configured
|
||||
// with schema for its own config, with the same content as returned by
|
||||
// function simpleTestSchema.
|
|
@ -1,516 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||
)
|
||||
|
||||
// Resource is a legacy way to identify a particular resource instance.
|
||||
//
|
||||
// New code should use addrs.ResourceInstance instead. This is still here
|
||||
// only for codepaths that haven't been updated yet.
|
||||
type Resource struct {
|
||||
// These are all used by the new EvalNode stuff.
|
||||
Name string
|
||||
Type string
|
||||
CountIndex int
|
||||
|
||||
// These aren't really used anymore anywhere, but we keep them around
|
||||
// since we haven't done a proper cleanup yet.
|
||||
Id string
|
||||
Info *InstanceInfo
|
||||
Config *ResourceConfig
|
||||
Dependencies []string
|
||||
Diff *InstanceDiff
|
||||
Provider ResourceProvider
|
||||
State *InstanceState
|
||||
Flags ResourceFlag
|
||||
}
|
||||
|
||||
// NewResource constructs a legacy Resource object from an
|
||||
// addrs.ResourceInstance value.
|
||||
//
|
||||
// This is provided to shim to old codepaths that haven't been updated away
|
||||
// from this type yet. Since this old type is not able to represent instances
|
||||
// that have string keys, this function will panic if given a resource address
|
||||
// that has a string key.
|
||||
func NewResource(addr addrs.ResourceInstance) *Resource {
|
||||
ret := &Resource{
|
||||
Name: addr.Resource.Name,
|
||||
Type: addr.Resource.Type,
|
||||
}
|
||||
|
||||
if addr.Key != addrs.NoKey {
|
||||
switch tk := addr.Key.(type) {
|
||||
case addrs.IntKey:
|
||||
ret.CountIndex = int(tk)
|
||||
default:
|
||||
panic(fmt.Errorf("resource instance with key %#v is not supported", addr.Key))
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// ResourceKind specifies what kind of instance we're working with, whether
|
||||
// its a primary instance, a tainted instance, or an orphan.
|
||||
type ResourceFlag byte
|
||||
|
||||
// InstanceInfo is used to hold information about the instance and/or
|
||||
// resource being modified.
|
||||
type InstanceInfo struct {
|
||||
// Id is a unique name to represent this instance. This is not related
|
||||
// to InstanceState.ID in any way.
|
||||
Id string
|
||||
|
||||
// ModulePath is the complete path of the module containing this
|
||||
// instance.
|
||||
ModulePath []string
|
||||
|
||||
// Type is the resource type of this instance
|
||||
Type string
|
||||
|
||||
// uniqueExtra is an internal field that can be populated to supply
|
||||
// extra metadata that is used to identify a unique instance in
|
||||
// the graph walk. This will be appended to HumanID when uniqueId
|
||||
// is called.
|
||||
uniqueExtra string
|
||||
}
|
||||
|
||||
// NewInstanceInfo constructs an InstanceInfo from an addrs.AbsResourceInstance.
|
||||
//
|
||||
// InstanceInfo is a legacy type, and uses of it should be gradually replaced
|
||||
// by direct use of addrs.AbsResource or addrs.AbsResourceInstance as
|
||||
// appropriate.
|
||||
//
|
||||
// The legacy InstanceInfo type cannot represent module instances with instance
|
||||
// keys, so this function will panic if given such a path. Uses of this type
|
||||
// should all be removed or replaced before implementing "count" and "for_each"
|
||||
// arguments on modules in order to avoid such panics.
|
||||
//
|
||||
// This legacy type also cannot represent resource instances with string
|
||||
// instance keys. It will panic if the given key is not either NoKey or an
|
||||
// IntKey.
|
||||
func NewInstanceInfo(addr addrs.AbsResourceInstance) *InstanceInfo {
|
||||
// We need an old-style []string module path for InstanceInfo.
|
||||
path := make([]string, len(addr.Module))
|
||||
for i, step := range addr.Module {
|
||||
if step.InstanceKey != addrs.NoKey {
|
||||
panic("NewInstanceInfo cannot convert module instance with key")
|
||||
}
|
||||
path[i] = step.Name
|
||||
}
|
||||
|
||||
// This is a funny old meaning of "id" that is no longer current. It should
|
||||
// not be used for anything users might see. Note that it does not include
|
||||
// a representation of the resource mode, and so it's impossible to
|
||||
// determine from an InstanceInfo alone whether it is a managed or data
|
||||
// resource that is being referred to.
|
||||
id := fmt.Sprintf("%s.%s", addr.Resource.Resource.Type, addr.Resource.Resource.Name)
|
||||
if addr.Resource.Resource.Mode == addrs.DataResourceMode {
|
||||
id = "data." + id
|
||||
}
|
||||
if addr.Resource.Key != addrs.NoKey {
|
||||
switch k := addr.Resource.Key.(type) {
|
||||
case addrs.IntKey:
|
||||
id = id + fmt.Sprintf(".%d", int(k))
|
||||
default:
|
||||
panic(fmt.Sprintf("NewInstanceInfo cannot convert resource instance with %T instance key", addr.Resource.Key))
|
||||
}
|
||||
}
|
||||
|
||||
return &InstanceInfo{
|
||||
Id: id,
|
||||
ModulePath: path,
|
||||
Type: addr.Resource.Resource.Type,
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceAddress returns the address of the resource that the receiver is describing.
|
||||
func (i *InstanceInfo) ResourceAddress() *ResourceAddress {
|
||||
// GROSS: for tainted and deposed instances, their status gets appended
|
||||
// to i.Id to create a unique id for the graph node. Historically these
|
||||
// ids were displayed to the user, so it's designed to be human-readable:
|
||||
// "aws_instance.bar.0 (deposed #0)"
|
||||
//
|
||||
// So here we detect such suffixes and try to interpret them back to
|
||||
// their original meaning so we can then produce a ResourceAddress
|
||||
// with a suitable InstanceType.
|
||||
id := i.Id
|
||||
instanceType := TypeInvalid
|
||||
if idx := strings.Index(id, " ("); idx != -1 {
|
||||
remain := id[idx:]
|
||||
id = id[:idx]
|
||||
|
||||
switch {
|
||||
case strings.Contains(remain, "tainted"):
|
||||
instanceType = TypeTainted
|
||||
case strings.Contains(remain, "deposed"):
|
||||
instanceType = TypeDeposed
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := parseResourceAddressInternal(id)
|
||||
if err != nil {
|
||||
// should never happen, since that would indicate a bug in the
|
||||
// code that constructed this InstanceInfo.
|
||||
panic(fmt.Errorf("InstanceInfo has invalid Id %s", id))
|
||||
}
|
||||
if len(i.ModulePath) > 1 {
|
||||
addr.Path = i.ModulePath[1:] // trim off "root" prefix, which is implied
|
||||
}
|
||||
if instanceType != TypeInvalid {
|
||||
addr.InstanceTypeSet = true
|
||||
addr.InstanceType = instanceType
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// ResourceConfig is a legacy type that was formerly used to represent
|
||||
// interpolatable configuration blocks. It is now only used to shim to old
|
||||
// APIs that still use this type, via NewResourceConfigShimmed.
|
||||
type ResourceConfig struct {
|
||||
ComputedKeys []string
|
||||
Raw map[string]interface{}
|
||||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
// NewResourceConfigRaw constructs a ResourceConfig whose content is exactly
|
||||
// the given value.
|
||||
//
|
||||
// The given value may contain hcl2shim.UnknownVariableValue to signal that
|
||||
// something is computed, but it must not contain unprocessed interpolation
|
||||
// sequences as we might've seen in Terraform v0.11 and prior.
|
||||
func NewResourceConfigRaw(raw map[string]interface{}) *ResourceConfig {
|
||||
v := hcl2shim.HCL2ValueFromConfigValue(raw)
|
||||
|
||||
// This is a little weird but we round-trip the value through the hcl2shim
|
||||
// package here for two reasons: firstly, because that reduces the risk
|
||||
// of it including something unlike what NewResourceConfigShimmed would
|
||||
// produce, and secondly because it creates a copy of "raw" just in case
|
||||
// something is relying on the fact that in the old world the raw and
|
||||
// config maps were always distinct, and thus you could in principle mutate
|
||||
// one without affecting the other. (I sure hope nobody was doing that, though!)
|
||||
cfg := hcl2shim.ConfigValueFromHCL2(v).(map[string]interface{})
|
||||
|
||||
return &ResourceConfig{
|
||||
Raw: raw,
|
||||
Config: cfg,
|
||||
|
||||
ComputedKeys: newResourceConfigShimmedComputedKeys(v, ""),
|
||||
}
|
||||
}
|
||||
|
||||
// NewResourceConfigShimmed wraps a cty.Value of object type in a legacy
|
||||
// ResourceConfig object, so that it can be passed to older APIs that expect
|
||||
// this wrapping.
|
||||
//
|
||||
// The returned ResourceConfig is already interpolated and cannot be
|
||||
// re-interpolated. It is, therefore, useful only to functions that expect
|
||||
// an already-populated ResourceConfig which they then treat as read-only.
|
||||
//
|
||||
// If the given value is not of an object type that conforms to the given
|
||||
// schema then this function will panic.
|
||||
func NewResourceConfigShimmed(val cty.Value, schema *configschema.Block) *ResourceConfig {
|
||||
if !val.Type().IsObjectType() {
|
||||
panic(fmt.Errorf("NewResourceConfigShimmed given %#v; an object type is required", val.Type()))
|
||||
}
|
||||
ret := &ResourceConfig{}
|
||||
|
||||
legacyVal := hcl2shim.ConfigValueFromHCL2Block(val, schema)
|
||||
if legacyVal != nil {
|
||||
ret.Config = legacyVal
|
||||
|
||||
// Now we need to walk through our structure and find any unknown values,
|
||||
// producing the separate list ComputedKeys to represent these. We use the
|
||||
// schema here so that we can preserve the expected invariant
|
||||
// that an attribute is always either wholly known or wholly unknown, while
|
||||
// a child block can be partially unknown.
|
||||
ret.ComputedKeys = newResourceConfigShimmedComputedKeys(val, "")
|
||||
} else {
|
||||
ret.Config = make(map[string]interface{})
|
||||
}
|
||||
ret.Raw = ret.Config
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Record the any config values in ComputedKeys. This field had been unused in
|
||||
// helper/schema, but in the new protocol we're using this so that the SDK can
|
||||
// now handle having an unknown collection. The legacy diff code doesn't
|
||||
// properly handle the unknown, because it can't be expressed in the same way
|
||||
// between the config and diff.
|
||||
func newResourceConfigShimmedComputedKeys(val cty.Value, path string) []string {
|
||||
var ret []string
|
||||
ty := val.Type()
|
||||
|
||||
if val.IsNull() {
|
||||
return ret
|
||||
}
|
||||
|
||||
if !val.IsKnown() {
|
||||
// we shouldn't have an entirely unknown resource, but prevent empty
|
||||
// strings just in case
|
||||
if len(path) > 0 {
|
||||
ret = append(ret, path)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
path += "."
|
||||
}
|
||||
switch {
|
||||
case ty.IsListType(), ty.IsTupleType(), ty.IsSetType():
|
||||
i := 0
|
||||
for it := val.ElementIterator(); it.Next(); i++ {
|
||||
_, subVal := it.Element()
|
||||
keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%d", path, i))
|
||||
ret = append(ret, keys...)
|
||||
}
|
||||
|
||||
case ty.IsMapType(), ty.IsObjectType():
|
||||
for it := val.ElementIterator(); it.Next(); {
|
||||
subK, subVal := it.Element()
|
||||
keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%s", path, subK.AsString()))
|
||||
ret = append(ret, keys...)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// DeepCopy performs a deep copy of the configuration. This makes it safe
|
||||
// to modify any of the structures that are part of the resource config without
|
||||
// affecting the original configuration.
|
||||
func (c *ResourceConfig) DeepCopy() *ResourceConfig {
|
||||
// DeepCopying a nil should return a nil to avoid panics
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy, this will copy all the exported attributes
|
||||
copy, err := copystructure.Config{Lock: true}.Copy(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Force the type
|
||||
result := copy.(*ResourceConfig)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Equal checks the equality of two resource configs.
|
||||
func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool {
|
||||
// If either are nil, then they're only equal if they're both nil
|
||||
if c == nil || c2 == nil {
|
||||
return c == c2
|
||||
}
|
||||
|
||||
// Sort the computed keys so they're deterministic
|
||||
sort.Strings(c.ComputedKeys)
|
||||
sort.Strings(c2.ComputedKeys)
|
||||
|
||||
// Two resource configs if their exported properties are equal.
|
||||
// We don't compare "raw" because it is never used again after
|
||||
// initialization and for all intents and purposes they are equal
|
||||
// if the exported properties are equal.
|
||||
check := [][2]interface{}{
|
||||
{c.ComputedKeys, c2.ComputedKeys},
|
||||
{c.Raw, c2.Raw},
|
||||
{c.Config, c2.Config},
|
||||
}
|
||||
for _, pair := range check {
|
||||
if !reflect.DeepEqual(pair[0], pair[1]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// CheckSet checks that the given list of configuration keys is
|
||||
// properly set. If not, errors are returned for each unset key.
|
||||
//
|
||||
// This is useful to be called in the Validate method of a ResourceProvider.
|
||||
func (c *ResourceConfig) CheckSet(keys []string) []error {
|
||||
var errs []error
|
||||
|
||||
for _, k := range keys {
|
||||
if !c.IsSet(k) {
|
||||
errs = append(errs, fmt.Errorf("%s must be set", k))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Get looks up a configuration value by key and returns the value.
|
||||
//
|
||||
// The second return value is true if the get was successful. Get will
|
||||
// return the raw value if the key is computed, so you should pair this
|
||||
// with IsComputed.
|
||||
func (c *ResourceConfig) Get(k string) (interface{}, bool) {
|
||||
// We aim to get a value from the configuration. If it is computed,
|
||||
// then we return the pure raw value.
|
||||
source := c.Config
|
||||
if c.IsComputed(k) {
|
||||
source = c.Raw
|
||||
}
|
||||
|
||||
return c.get(k, source)
|
||||
}
|
||||
|
||||
// GetRaw looks up a configuration value by key and returns the value,
|
||||
// from the raw, uninterpolated config.
|
||||
//
|
||||
// The second return value is true if the get was successful. Get will
|
||||
// not succeed if the value is being computed.
|
||||
func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) {
|
||||
return c.get(k, c.Raw)
|
||||
}
|
||||
|
||||
// IsComputed returns whether the given key is computed or not.
|
||||
func (c *ResourceConfig) IsComputed(k string) bool {
|
||||
// The next thing we do is check the config if we get a computed
|
||||
// value out of it.
|
||||
v, ok := c.get(k, c.Config)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// If value is nil, then it isn't computed
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Test if the value contains an unknown value
|
||||
var w unknownCheckWalker
|
||||
if err := reflectwalk.Walk(v, &w); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return w.Unknown
|
||||
}
|
||||
|
||||
// IsSet checks if the key in the configuration is set. A key is set if
|
||||
// it has a value or the value is being computed (is unknown currently).
|
||||
//
|
||||
// This function should be used rather than checking the keys of the
|
||||
// raw configuration itself, since a key may be omitted from the raw
|
||||
// configuration if it is being computed.
|
||||
func (c *ResourceConfig) IsSet(k string) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.IsComputed(k) {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := c.Get(k); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *ResourceConfig) get(
|
||||
k string, raw map[string]interface{}) (interface{}, bool) {
|
||||
parts := strings.Split(k, ".")
|
||||
if len(parts) == 1 && parts[0] == "" {
|
||||
parts = nil
|
||||
}
|
||||
|
||||
var current interface{} = raw
|
||||
var previous interface{} = nil
|
||||
for i, part := range parts {
|
||||
if current == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cv := reflect.ValueOf(current)
|
||||
switch cv.Kind() {
|
||||
case reflect.Map:
|
||||
previous = current
|
||||
v := cv.MapIndex(reflect.ValueOf(part))
|
||||
if !v.IsValid() {
|
||||
if i > 0 && i != (len(parts)-1) {
|
||||
tryKey := strings.Join(parts[i:], ".")
|
||||
v := cv.MapIndex(reflect.ValueOf(tryKey))
|
||||
if !v.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return v.Interface(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
current = v.Interface()
|
||||
case reflect.Slice:
|
||||
previous = current
|
||||
|
||||
if part == "#" {
|
||||
// If any value in a list is computed, this whole thing
|
||||
// is computed and we can't read any part of it.
|
||||
for i := 0; i < cv.Len(); i++ {
|
||||
if v := cv.Index(i).Interface(); v == hcl2shim.UnknownVariableValue {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
|
||||
current = cv.Len()
|
||||
} else {
|
||||
i, err := strconv.ParseInt(part, 0, 0)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if int(i) < 0 || int(i) >= cv.Len() {
|
||||
return nil, false
|
||||
}
|
||||
current = cv.Index(int(i)).Interface()
|
||||
}
|
||||
case reflect.String:
|
||||
// This happens when map keys contain "." and have a common
|
||||
// prefix so were split as path components above.
|
||||
actualKey := strings.Join(parts[i-1:], ".")
|
||||
if prevMap, ok := previous.(map[string]interface{}); ok {
|
||||
v, ok := prevMap[actualKey]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
return nil, false
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown kind: %s", cv.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
return current, true
|
||||
}
|
||||
|
||||
// unknownCheckWalker
|
||||
type unknownCheckWalker struct {
|
||||
Unknown bool
|
||||
}
|
||||
|
||||
func (w *unknownCheckWalker) Primitive(v reflect.Value) error {
|
||||
if v.Interface() == hcl2shim.UnknownVariableValue {
|
||||
w.Unknown = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,618 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
)
|
||||
|
||||
// ResourceAddress is a way of identifying an individual resource (or,
|
||||
// eventually, a subset of resources) within the state. It is used for Targets.
|
||||
type ResourceAddress struct {
|
||||
// Addresses a resource falling somewhere in the module path
|
||||
// When specified alone, addresses all resources within a module path
|
||||
Path []string
|
||||
|
||||
// Addresses a specific resource that occurs in a list
|
||||
Index int
|
||||
|
||||
InstanceType InstanceType
|
||||
InstanceTypeSet bool
|
||||
Name string
|
||||
Type string
|
||||
Mode ResourceMode // significant only if InstanceTypeSet
|
||||
}
|
||||
|
||||
// Copy returns a copy of this ResourceAddress
|
||||
func (r *ResourceAddress) Copy() *ResourceAddress {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := &ResourceAddress{
|
||||
Path: make([]string, 0, len(r.Path)),
|
||||
Index: r.Index,
|
||||
InstanceType: r.InstanceType,
|
||||
Name: r.Name,
|
||||
Type: r.Type,
|
||||
Mode: r.Mode,
|
||||
}
|
||||
|
||||
n.Path = append(n.Path, r.Path...)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// String outputs the address that parses into this address.
|
||||
func (r *ResourceAddress) String() string {
|
||||
var result []string
|
||||
for _, p := range r.Path {
|
||||
result = append(result, "module", p)
|
||||
}
|
||||
|
||||
switch r.Mode {
|
||||
case ManagedResourceMode:
|
||||
// nothing to do
|
||||
case DataResourceMode:
|
||||
result = append(result, "data")
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported resource mode %s", r.Mode))
|
||||
}
|
||||
|
||||
if r.Type != "" {
|
||||
result = append(result, r.Type)
|
||||
}
|
||||
|
||||
if r.Name != "" {
|
||||
name := r.Name
|
||||
if r.InstanceTypeSet {
|
||||
switch r.InstanceType {
|
||||
case TypePrimary:
|
||||
name += ".primary"
|
||||
case TypeDeposed:
|
||||
name += ".deposed"
|
||||
case TypeTainted:
|
||||
name += ".tainted"
|
||||
}
|
||||
}
|
||||
|
||||
if r.Index >= 0 {
|
||||
name += fmt.Sprintf("[%d]", r.Index)
|
||||
}
|
||||
result = append(result, name)
|
||||
}
|
||||
|
||||
return strings.Join(result, ".")
|
||||
}
|
||||
|
||||
// HasResourceSpec returns true if the address has a resource spec, as
|
||||
// defined in the documentation:
|
||||
// https://www.terraform.io/docs/internals/resource-addressing.html
|
||||
// In particular, this returns false if the address contains only
|
||||
// a module path, thus addressing the entire module.
|
||||
func (r *ResourceAddress) HasResourceSpec() bool {
|
||||
return r.Type != "" && r.Name != ""
|
||||
}
|
||||
|
||||
// WholeModuleAddress returns the resource address that refers to all
|
||||
// resources in the same module as the receiver address.
|
||||
func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress {
|
||||
return &ResourceAddress{
|
||||
Path: r.Path,
|
||||
Index: -1,
|
||||
InstanceTypeSet: false,
|
||||
}
|
||||
}
|
||||
|
||||
// MatchesResourceConfig returns true if the receiver matches the given
|
||||
// configuration resource within the given _static_ module path. Note that
|
||||
// the module path in a resource address is a _dynamic_ module path, and
|
||||
// multiple dynamic resource paths may map to a single static path if
|
||||
// count and for_each are in use on module calls.
|
||||
//
|
||||
// Since resource configuration blocks represent all of the instances of
|
||||
// a multi-instance resource, the index of the address (if any) is not
|
||||
// considered.
|
||||
func (r *ResourceAddress) MatchesResourceConfig(path addrs.Module, rc *configs.Resource) bool {
|
||||
if r.HasResourceSpec() {
|
||||
// FIXME: Some ugliness while we are between worlds. Functionality
|
||||
// in "addrs" should eventually replace this ResourceAddress idea
|
||||
// completely, but for now we'll need to translate to the old
|
||||
// way of representing resource modes.
|
||||
switch r.Mode {
|
||||
case ManagedResourceMode:
|
||||
if rc.Mode != addrs.ManagedResourceMode {
|
||||
return false
|
||||
}
|
||||
case DataResourceMode:
|
||||
if rc.Mode != addrs.DataResourceMode {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if r.Type != rc.Type || r.Name != rc.Name {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
addrPath := r.Path
|
||||
|
||||
// normalize
|
||||
if len(addrPath) == 0 {
|
||||
addrPath = nil
|
||||
}
|
||||
if len(path) == 0 {
|
||||
path = nil
|
||||
}
|
||||
rawPath := []string(path)
|
||||
return reflect.DeepEqual(addrPath, rawPath)
|
||||
}
|
||||
|
||||
// stateId returns the ID that this resource should be entered with
|
||||
// in the state. This is also used for diffs. In the future, we'd like to
|
||||
// move away from this string field so I don't export this.
|
||||
func (r *ResourceAddress) stateId() string {
|
||||
result := fmt.Sprintf("%s.%s", r.Type, r.Name)
|
||||
switch r.Mode {
|
||||
case ManagedResourceMode:
|
||||
// Done
|
||||
case DataResourceMode:
|
||||
result = fmt.Sprintf("data.%s", result)
|
||||
default:
|
||||
panic(fmt.Errorf("unknown resource mode: %s", r.Mode))
|
||||
}
|
||||
if r.Index >= 0 {
|
||||
result += fmt.Sprintf(".%d", r.Index)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// parseResourceAddressInternal parses the somewhat bespoke resource
|
||||
// identifier used in states and diffs, such as "instance.name.0".
|
||||
func parseResourceAddressInternal(s string) (*ResourceAddress, error) {
|
||||
// Split based on ".". Every resource address should have at least two
|
||||
// elements (type and name).
|
||||
parts := strings.Split(s, ".")
|
||||
if len(parts) < 2 || len(parts) > 4 {
|
||||
return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
|
||||
}
|
||||
|
||||
// Data resource if we have at least 3 parts and the first one is data
|
||||
mode := ManagedResourceMode
|
||||
if len(parts) > 2 && parts[0] == "data" {
|
||||
mode = DataResourceMode
|
||||
parts = parts[1:]
|
||||
}
|
||||
|
||||
// If we're not a data resource and we have more than 3, then it is an error
|
||||
if len(parts) > 3 && mode != DataResourceMode {
|
||||
return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
|
||||
}
|
||||
|
||||
// Build the parts of the resource address that are guaranteed to exist
|
||||
addr := &ResourceAddress{
|
||||
Type: parts[0],
|
||||
Name: parts[1],
|
||||
Index: -1,
|
||||
InstanceType: TypePrimary,
|
||||
Mode: mode,
|
||||
}
|
||||
|
||||
// If we have more parts, then we have an index. Parse that.
|
||||
if len(parts) > 2 {
|
||||
idx, err := strconv.ParseInt(parts[2], 0, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err)
|
||||
}
|
||||
|
||||
addr.Index = int(idx)
|
||||
}
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
func ParseResourceAddress(s string) (*ResourceAddress, error) {
|
||||
matches, err := tokenizeResourceAddress(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mode := ManagedResourceMode
|
||||
if matches["data_prefix"] != "" {
|
||||
mode = DataResourceMode
|
||||
}
|
||||
resourceIndex, err := ParseResourceIndex(matches["index"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceType, err := ParseInstanceType(matches["instance_type"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := ParseResourcePath(matches["path"])
|
||||
|
||||
// not allowed to say "data." without a type following
|
||||
if mode == DataResourceMode && matches["type"] == "" {
|
||||
return nil, fmt.Errorf(
|
||||
"invalid resource address %q: must target specific data instance",
|
||||
s,
|
||||
)
|
||||
}
|
||||
|
||||
return &ResourceAddress{
|
||||
Path: path,
|
||||
Index: resourceIndex,
|
||||
InstanceType: instanceType,
|
||||
InstanceTypeSet: matches["instance_type"] != "",
|
||||
Name: matches["name"],
|
||||
Type: matches["type"],
|
||||
Mode: mode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseResourceAddressForInstanceDiff creates a ResourceAddress for a
|
||||
// resource name as described in a module diff.
|
||||
//
|
||||
// For historical reasons a different addressing format is used in this
|
||||
// context. The internal format should not be shown in the UI and instead
|
||||
// this function should be used to translate to a ResourceAddress and
|
||||
// then, where appropriate, use the String method to produce a canonical
|
||||
// resource address string for display in the UI.
|
||||
//
|
||||
// The given path slice must be empty (or nil) for the root module, and
|
||||
// otherwise consist of a sequence of module names traversing down into
|
||||
// the module tree. If a non-nil path is provided, the caller must not
|
||||
// modify its underlying array after passing it to this function.
|
||||
func ParseResourceAddressForInstanceDiff(path []string, key string) (*ResourceAddress, error) {
|
||||
addr, err := parseResourceAddressInternal(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr.Path = path
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// NewLegacyResourceAddress creates a ResourceAddress from a new-style
|
||||
// addrs.AbsResource value.
|
||||
//
|
||||
// This is provided for shimming purposes so that we can still easily call into
|
||||
// older functions that expect the ResourceAddress type.
|
||||
func NewLegacyResourceAddress(addr addrs.AbsResource) *ResourceAddress {
|
||||
ret := &ResourceAddress{
|
||||
Type: addr.Resource.Type,
|
||||
Name: addr.Resource.Name,
|
||||
}
|
||||
|
||||
switch addr.Resource.Mode {
|
||||
case addrs.ManagedResourceMode:
|
||||
ret.Mode = ManagedResourceMode
|
||||
case addrs.DataResourceMode:
|
||||
ret.Mode = DataResourceMode
|
||||
default:
|
||||
panic(fmt.Errorf("cannot shim %s to legacy ResourceMode value", addr.Resource.Mode))
|
||||
}
|
||||
|
||||
path := make([]string, len(addr.Module))
|
||||
for i, step := range addr.Module {
|
||||
if step.InstanceKey != addrs.NoKey {
|
||||
// At the time of writing this can't happen because we don't
|
||||
// ket generate keyed module instances. This legacy codepath must
|
||||
// be removed before we can support "count" and "for_each" for
|
||||
// modules.
|
||||
panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey))
|
||||
}
|
||||
|
||||
path[i] = step.Name
|
||||
}
|
||||
ret.Path = path
|
||||
ret.Index = -1
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// NewLegacyResourceInstanceAddress creates a ResourceAddress from a new-style
|
||||
// addrs.AbsResource value.
|
||||
//
|
||||
// This is provided for shimming purposes so that we can still easily call into
|
||||
// older functions that expect the ResourceAddress type.
|
||||
func NewLegacyResourceInstanceAddress(addr addrs.AbsResourceInstance) *ResourceAddress {
|
||||
ret := &ResourceAddress{
|
||||
Type: addr.Resource.Resource.Type,
|
||||
Name: addr.Resource.Resource.Name,
|
||||
}
|
||||
|
||||
switch addr.Resource.Resource.Mode {
|
||||
case addrs.ManagedResourceMode:
|
||||
ret.Mode = ManagedResourceMode
|
||||
case addrs.DataResourceMode:
|
||||
ret.Mode = DataResourceMode
|
||||
default:
|
||||
panic(fmt.Errorf("cannot shim %s to legacy ResourceMode value", addr.Resource.Resource.Mode))
|
||||
}
|
||||
|
||||
path := make([]string, len(addr.Module))
|
||||
for i, step := range addr.Module {
|
||||
if step.InstanceKey != addrs.NoKey {
|
||||
// At the time of writing this can't happen because we don't
|
||||
// ket generate keyed module instances. This legacy codepath must
|
||||
// be removed before we can support "count" and "for_each" for
|
||||
// modules.
|
||||
panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey))
|
||||
}
|
||||
|
||||
path[i] = step.Name
|
||||
}
|
||||
ret.Path = path
|
||||
|
||||
if addr.Resource.Key == addrs.NoKey {
|
||||
ret.Index = -1
|
||||
} else if ik, ok := addr.Resource.Key.(addrs.IntKey); ok {
|
||||
ret.Index = int(ik)
|
||||
} else if _, ok := addr.Resource.Key.(addrs.StringKey); ok {
|
||||
ret.Index = -1
|
||||
} else {
|
||||
panic(fmt.Errorf("cannot shim resource instance with key %#v to legacy ResourceAddress.Index", addr.Resource.Key))
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// AbsResourceInstanceAddr converts the receiver, a legacy resource address, to
|
||||
// the new resource address type addrs.AbsResourceInstance.
|
||||
//
|
||||
// This method can be used only on an address that has a resource specification.
|
||||
// It will panic if called on a module-path-only ResourceAddress. Use
|
||||
// method HasResourceSpec to check before calling, in contexts where it is
|
||||
// unclear.
|
||||
//
|
||||
// addrs.AbsResourceInstance does not represent the "tainted" and "deposed"
|
||||
// states, and so if these are present on the receiver then they are discarded.
|
||||
//
|
||||
// This is provided for shimming purposes so that we can easily adapt functions
|
||||
// that are returning the legacy ResourceAddress type, for situations where
|
||||
// the new type is required.
|
||||
func (addr *ResourceAddress) AbsResourceInstanceAddr() addrs.AbsResourceInstance {
|
||||
if !addr.HasResourceSpec() {
|
||||
panic("AbsResourceInstanceAddr called on ResourceAddress with no resource spec")
|
||||
}
|
||||
|
||||
ret := addrs.AbsResourceInstance{
|
||||
Module: addr.ModuleInstanceAddr(),
|
||||
Resource: addrs.ResourceInstance{
|
||||
Resource: addrs.Resource{
|
||||
Type: addr.Type,
|
||||
Name: addr.Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
switch addr.Mode {
|
||||
case ManagedResourceMode:
|
||||
ret.Resource.Resource.Mode = addrs.ManagedResourceMode
|
||||
case DataResourceMode:
|
||||
ret.Resource.Resource.Mode = addrs.DataResourceMode
|
||||
default:
|
||||
panic(fmt.Errorf("cannot shim %s to addrs.ResourceMode value", addr.Mode))
|
||||
}
|
||||
|
||||
if addr.Index != -1 {
|
||||
ret.Resource.Key = addrs.IntKey(addr.Index)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// ModuleInstanceAddr returns the module path portion of the receiver as a
|
||||
// addrs.ModuleInstance value.
|
||||
func (addr *ResourceAddress) ModuleInstanceAddr() addrs.ModuleInstance {
|
||||
path := make(addrs.ModuleInstance, len(addr.Path))
|
||||
for i, name := range addr.Path {
|
||||
path[i] = addrs.ModuleInstanceStep{Name: name}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// Contains returns true if and only if the given node is contained within
|
||||
// the receiver.
|
||||
//
|
||||
// Containment is defined in terms of the module and resource heirarchy:
|
||||
// a resource is contained within its module and any ancestor modules,
|
||||
// an indexed resource instance is contained with the unindexed resource, etc.
|
||||
func (addr *ResourceAddress) Contains(other *ResourceAddress) bool {
|
||||
ourPath := addr.Path
|
||||
givenPath := other.Path
|
||||
if len(givenPath) < len(ourPath) {
|
||||
return false
|
||||
}
|
||||
for i := range ourPath {
|
||||
if ourPath[i] != givenPath[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// If the receiver is a whole-module address then the path prefix
|
||||
// matching is all we need.
|
||||
if !addr.HasResourceSpec() {
|
||||
return true
|
||||
}
|
||||
|
||||
if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode {
|
||||
return false
|
||||
}
|
||||
|
||||
if addr.Index != -1 && addr.Index != other.Index {
|
||||
return false
|
||||
}
|
||||
|
||||
if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Equals returns true if the receiver matches the given address.
|
||||
//
|
||||
// The name of this method is a misnomer, since it doesn't test for exact
|
||||
// equality. Instead, it tests that the _specified_ parts of each
|
||||
// address match, treating any unspecified parts as wildcards.
|
||||
//
|
||||
// See also Contains, which takes a more hierarchical approach to comparing
|
||||
// addresses.
|
||||
func (addr *ResourceAddress) Equals(raw interface{}) bool {
|
||||
other, ok := raw.(*ResourceAddress)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 ||
|
||||
reflect.DeepEqual(addr.Path, other.Path)
|
||||
|
||||
indexMatch := addr.Index == -1 ||
|
||||
other.Index == -1 ||
|
||||
addr.Index == other.Index
|
||||
|
||||
nameMatch := addr.Name == "" ||
|
||||
other.Name == "" ||
|
||||
addr.Name == other.Name
|
||||
|
||||
typeMatch := addr.Type == "" ||
|
||||
other.Type == "" ||
|
||||
addr.Type == other.Type
|
||||
|
||||
// mode is significant only when type is set
|
||||
modeMatch := addr.Type == "" ||
|
||||
other.Type == "" ||
|
||||
addr.Mode == other.Mode
|
||||
|
||||
return pathMatch &&
|
||||
indexMatch &&
|
||||
addr.InstanceType == other.InstanceType &&
|
||||
nameMatch &&
|
||||
typeMatch &&
|
||||
modeMatch
|
||||
}
|
||||
|
||||
// Less returns true if and only if the receiver should be sorted before
|
||||
// the given address when presenting a list of resource addresses to
|
||||
// an end-user.
|
||||
//
|
||||
// This sort uses lexicographic sorting for most components, but uses
|
||||
// numeric sort for indices, thus causing index 10 to sort after
|
||||
// index 9, rather than after index 1.
|
||||
func (addr *ResourceAddress) Less(other *ResourceAddress) bool {
|
||||
|
||||
switch {
|
||||
|
||||
case len(addr.Path) != len(other.Path):
|
||||
return len(addr.Path) < len(other.Path)
|
||||
|
||||
case !reflect.DeepEqual(addr.Path, other.Path):
|
||||
// If the two paths are the same length but don't match, we'll just
|
||||
// cheat and compare the string forms since it's easier than
|
||||
// comparing all of the path segments in turn, and lexicographic
|
||||
// comparison is correct for the module path portion.
|
||||
addrStr := addr.String()
|
||||
otherStr := other.String()
|
||||
return addrStr < otherStr
|
||||
|
||||
case addr.Mode != other.Mode:
|
||||
return addr.Mode == DataResourceMode
|
||||
|
||||
case addr.Type != other.Type:
|
||||
return addr.Type < other.Type
|
||||
|
||||
case addr.Name != other.Name:
|
||||
return addr.Name < other.Name
|
||||
|
||||
case addr.Index != other.Index:
|
||||
// Since "Index" is -1 for an un-indexed address, this also conveniently
|
||||
// sorts unindexed addresses before indexed ones, should they both
|
||||
// appear for some reason.
|
||||
return addr.Index < other.Index
|
||||
|
||||
case addr.InstanceTypeSet != other.InstanceTypeSet:
|
||||
return !addr.InstanceTypeSet
|
||||
|
||||
case addr.InstanceType != other.InstanceType:
|
||||
// InstanceType is actually an enum, so this is just an arbitrary
|
||||
// sort based on the enum numeric values, and thus not particularly
|
||||
// meaningful.
|
||||
return addr.InstanceType < other.InstanceType
|
||||
|
||||
default:
|
||||
return false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func ParseResourceIndex(s string) (int, error) {
|
||||
if s == "" {
|
||||
return -1, nil
|
||||
}
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
|
||||
func ParseResourcePath(s string) []string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(s, ".")
|
||||
path := make([]string, 0, len(parts))
|
||||
for _, s := range parts {
|
||||
// Due to the limitations of the regexp match below, the path match has
|
||||
// some noise in it we have to filter out :|
|
||||
if s == "" || s == "module" {
|
||||
continue
|
||||
}
|
||||
path = append(path, s)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func ParseInstanceType(s string) (InstanceType, error) {
|
||||
switch s {
|
||||
case "", "primary":
|
||||
return TypePrimary, nil
|
||||
case "deposed":
|
||||
return TypeDeposed, nil
|
||||
case "tainted":
|
||||
return TypeTainted, nil
|
||||
default:
|
||||
return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
func tokenizeResourceAddress(s string) (map[string]string, error) {
|
||||
// Example of portions of the regexp below using the
|
||||
// string "aws_instance.web.tainted[1]"
|
||||
re := regexp.MustCompile(`\A` +
|
||||
// "module.foo.module.bar" (optional)
|
||||
`(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` +
|
||||
// possibly "data.", if targeting is a data resource
|
||||
`(?P<data_prefix>(?:data\.)?)` +
|
||||
// "aws_instance.web" (optional when module path specified)
|
||||
`(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` +
|
||||
// "tainted" (optional, omission implies: "primary")
|
||||
`(?:\.(?P<instance_type>\w+))?` +
|
||||
// "1" (optional, omission implies: "0")
|
||||
`(?:\[(?P<index>\d+)\])?` +
|
||||
`\z`)
|
||||
|
||||
groupNames := re.SubexpNames()
|
||||
rawMatches := re.FindAllStringSubmatch(s, -1)
|
||||
if len(rawMatches) != 1 {
|
||||
return nil, fmt.Errorf("invalid resource address %q", s)
|
||||
}
|
||||
|
||||
matches := make(map[string]string)
|
||||
for i, m := range rawMatches[0] {
|
||||
matches[groupNames[i]] = m
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +0,0 @@
|
|||
package terraform
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=ResourceMode -output=resource_mode_string.go resource_mode.go
|
||||
|
||||
// ResourceMode is deprecated, use addrs.ResourceMode instead.
|
||||
// It has been preserved for backwards compatibility.
|
||||
type ResourceMode int
|
||||
|
||||
const (
|
||||
ManagedResourceMode ResourceMode = iota
|
||||
DataResourceMode
|
||||
)
|
|
@ -1,24 +0,0 @@
|
|||
// Code generated by "stringer -type=ResourceMode -output=resource_mode_string.go resource_mode.go"; DO NOT EDIT.
|
||||
|
||||
package terraform
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ManagedResourceMode-0]
|
||||
_ = x[DataResourceMode-1]
|
||||
}
|
||||
|
||||
const _ResourceMode_name = "ManagedResourceModeDataResourceMode"
|
||||
|
||||
var _ResourceMode_index = [...]uint8{0, 19, 35}
|
||||
|
||||
func (i ResourceMode) String() string {
|
||||
if i < 0 || i >= ResourceMode(len(_ResourceMode_index)-1) {
|
||||
return "ResourceMode(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _ResourceMode_name[_ResourceMode_index[i]:_ResourceMode_index[i+1]]
|
||||
}
|
|
@ -1,226 +1,5 @@
|
|||
package terraform
|
||||
|
||||
// ResourceProvider is a legacy interface for providers.
|
||||
//
|
||||
// This is retained only for compatibility with legacy code. The current
|
||||
// interface for providers is providers.Interface, in the sibling directory
|
||||
// named "providers".
|
||||
type ResourceProvider interface {
|
||||
/*********************************************************************
|
||||
* Functions related to the provider
|
||||
*********************************************************************/
|
||||
|
||||
// ProviderSchema returns the config schema for the main provider
|
||||
// configuration, as would appear in a "provider" block in the
|
||||
// configuration files.
|
||||
//
|
||||
// Currently not all providers support schema. Callers must therefore
|
||||
// first call Resources and DataSources and ensure that at least one
|
||||
// resource or data source has the SchemaAvailable flag set.
|
||||
GetSchema(*ProviderSchemaRequest) (*ProviderSchema, error)
|
||||
|
||||
// Input was used prior to v0.12 to ask the provider to prompt the user
|
||||
// for input to complete the configuration.
|
||||
//
|
||||
// From v0.12 onwards this method is never called because Terraform Core
|
||||
// is able to handle the necessary input logic itself based on the
|
||||
// schema returned from GetSchema.
|
||||
Input(UIInput, *ResourceConfig) (*ResourceConfig, error)
|
||||
|
||||
// Validate is called once at the beginning with the raw configuration
|
||||
// (no interpolation done) and can return a list of warnings and/or
|
||||
// errors.
|
||||
//
|
||||
// This is called once with the provider configuration only. It may not
|
||||
// be called at all if no provider configuration is given.
|
||||
//
|
||||
// This should not assume that any values of the configurations are valid.
|
||||
// The primary use case of this call is to check that required keys are
|
||||
// set.
|
||||
Validate(*ResourceConfig) ([]string, []error)
|
||||
|
||||
// Configure configures the provider itself with the configuration
|
||||
// given. This is useful for setting things like access keys.
|
||||
//
|
||||
// This won't be called at all if no provider configuration is given.
|
||||
//
|
||||
// Configure returns an error if it occurred.
|
||||
Configure(*ResourceConfig) error
|
||||
|
||||
// Resources returns all the available resource types that this provider
|
||||
// knows how to manage.
|
||||
Resources() []ResourceType
|
||||
|
||||
// Stop is called when the provider should halt any in-flight actions.
|
||||
//
|
||||
// This can be used to make a nicer Ctrl-C experience for Terraform.
|
||||
// Even if this isn't implemented to do anything (just returns nil),
|
||||
// Terraform will still cleanly stop after the currently executing
|
||||
// graph node is complete. However, this API can be used to make more
|
||||
// efficient halts.
|
||||
//
|
||||
// Stop doesn't have to and shouldn't block waiting for in-flight actions
|
||||
// to complete. It should take any action it wants and return immediately
|
||||
// acknowledging it has received the stop request. Terraform core will
|
||||
// automatically not make any further API calls to the provider soon
|
||||
// after Stop is called (technically exactly once the currently executing
|
||||
// graph nodes are complete).
|
||||
//
|
||||
// The error returned, if non-nil, is assumed to mean that signaling the
|
||||
// stop somehow failed and that the user should expect potentially waiting
|
||||
// a longer period of time.
|
||||
Stop() error
|
||||
|
||||
/*********************************************************************
|
||||
* Functions related to individual resources
|
||||
*********************************************************************/
|
||||
|
||||
// ValidateResource is called once at the beginning with the raw
|
||||
// configuration (no interpolation done) and can return a list of warnings
|
||||
// and/or errors.
|
||||
//
|
||||
// This is called once per resource.
|
||||
//
|
||||
// This should not assume any of the values in the resource configuration
|
||||
// are valid since it is possible they have to be interpolated still.
|
||||
// The primary use case of this call is to check that the required keys
|
||||
// are set and that the general structure is correct.
|
||||
ValidateResource(string, *ResourceConfig) ([]string, []error)
|
||||
|
||||
// Apply applies a diff to a specific resource and returns the new
|
||||
// resource state along with an error.
|
||||
//
|
||||
// If the resource state given has an empty ID, then a new resource
|
||||
// is expected to be created.
|
||||
Apply(
|
||||
*InstanceInfo,
|
||||
*InstanceState,
|
||||
*InstanceDiff) (*InstanceState, error)
|
||||
|
||||
// Diff diffs a resource versus a desired state and returns
|
||||
// a diff.
|
||||
Diff(
|
||||
*InstanceInfo,
|
||||
*InstanceState,
|
||||
*ResourceConfig) (*InstanceDiff, error)
|
||||
|
||||
// Refresh refreshes a resource and updates all of its attributes
|
||||
// with the latest information.
|
||||
Refresh(*InstanceInfo, *InstanceState) (*InstanceState, error)
|
||||
|
||||
/*********************************************************************
|
||||
* Functions related to importing
|
||||
*********************************************************************/
|
||||
|
||||
// ImportState requests that the given resource be imported.
|
||||
//
|
||||
// The returned InstanceState only requires ID be set. Importing
|
||||
// will always call Refresh after the state to complete it.
|
||||
//
|
||||
// IMPORTANT: InstanceState doesn't have the resource type attached
|
||||
// to it. A type must be specified on the state via the Ephemeral
|
||||
// field on the state.
|
||||
//
|
||||
// This function can return multiple states. Normally, an import
|
||||
// will map 1:1 to a physical resource. However, some resources map
|
||||
// to multiple. For example, an AWS security group may contain many rules.
|
||||
// Each rule is represented by a separate resource in Terraform,
|
||||
// therefore multiple states are returned.
|
||||
ImportState(*InstanceInfo, string) ([]*InstanceState, error)
|
||||
|
||||
/*********************************************************************
|
||||
* Functions related to data resources
|
||||
*********************************************************************/
|
||||
|
||||
// ValidateDataSource is called once at the beginning with the raw
|
||||
// configuration (no interpolation done) and can return a list of warnings
|
||||
// and/or errors.
|
||||
//
|
||||
// This is called once per data source instance.
|
||||
//
|
||||
// This should not assume any of the values in the resource configuration
|
||||
// are valid since it is possible they have to be interpolated still.
|
||||
// The primary use case of this call is to check that the required keys
|
||||
// are set and that the general structure is correct.
|
||||
ValidateDataSource(string, *ResourceConfig) ([]string, []error)
|
||||
|
||||
// DataSources returns all of the available data sources that this
|
||||
// provider implements.
|
||||
DataSources() []DataSource
|
||||
|
||||
// ReadDataDiff produces a diff that represents the state that will
|
||||
// be produced when the given data source is read using a later call
|
||||
// to ReadDataApply.
|
||||
ReadDataDiff(*InstanceInfo, *ResourceConfig) (*InstanceDiff, error)
|
||||
|
||||
// ReadDataApply initializes a data instance using the configuration
|
||||
// in a diff produced by ReadDataDiff.
|
||||
ReadDataApply(*InstanceInfo, *InstanceDiff) (*InstanceState, error)
|
||||
}
|
||||
|
||||
// ResourceProviderCloser is an interface that providers that can close
|
||||
// connections that aren't needed anymore must implement.
|
||||
type ResourceProviderCloser interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ResourceType is a type of resource that a resource provider can manage.
|
||||
type ResourceType struct {
|
||||
Name string // Name of the resource, example "instance" (no provider prefix)
|
||||
Importable bool // Whether this resource supports importing
|
||||
|
||||
// SchemaAvailable is set if the provider supports the ProviderSchema,
|
||||
// ResourceTypeSchema and DataSourceSchema methods. Although it is
|
||||
// included on each resource type, it's actually a provider-wide setting
|
||||
// that's smuggled here only because that avoids a breaking change to
|
||||
// the plugin protocol.
|
||||
SchemaAvailable bool
|
||||
}
|
||||
|
||||
// DataSource is a data source that a resource provider implements.
|
||||
type DataSource struct {
|
||||
Name string
|
||||
|
||||
// SchemaAvailable is set if the provider supports the ProviderSchema,
|
||||
// ResourceTypeSchema and DataSourceSchema methods. Although it is
|
||||
// included on each resource type, it's actually a provider-wide setting
|
||||
// that's smuggled here only because that avoids a breaking change to
|
||||
// the plugin protocol.
|
||||
SchemaAvailable bool
|
||||
}
|
||||
|
||||
// ResourceProviderFactory is a function type that creates a new instance
|
||||
// of a resource provider.
|
||||
type ResourceProviderFactory func() (ResourceProvider, error)
|
||||
|
||||
// ResourceProviderFactoryFixed is a helper that creates a
|
||||
// ResourceProviderFactory that just returns some fixed provider.
|
||||
func ResourceProviderFactoryFixed(p ResourceProvider) ResourceProviderFactory {
|
||||
return func() (ResourceProvider, error) {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
func ProviderHasResource(p ResourceProvider, n string) bool {
|
||||
for _, rt := range p.Resources() {
|
||||
if rt.Name == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func ProviderHasDataSource(p ResourceProvider, n string) bool {
|
||||
for _, rt := range p.DataSources() {
|
||||
if rt.Name == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const errPluginInit = `
|
||||
Plugin reinitialization required. Please run "terraform init".
|
||||
|
||||
|
|
|
@ -1,315 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MockResourceProvider implements ResourceProvider but mocks out all the
|
||||
// calls for testing purposes.
|
||||
type MockResourceProvider struct {
|
||||
sync.Mutex
|
||||
|
||||
// Anything you want, in case you need to store extra data with the mock.
|
||||
Meta interface{}
|
||||
|
||||
CloseCalled bool
|
||||
CloseError error
|
||||
GetSchemaCalled bool
|
||||
GetSchemaRequest *ProviderSchemaRequest
|
||||
GetSchemaReturn *ProviderSchema
|
||||
GetSchemaReturnError error
|
||||
InputCalled bool
|
||||
InputInput UIInput
|
||||
InputConfig *ResourceConfig
|
||||
InputReturnConfig *ResourceConfig
|
||||
InputReturnError error
|
||||
InputFn func(UIInput, *ResourceConfig) (*ResourceConfig, error)
|
||||
ApplyCalled bool
|
||||
ApplyInfo *InstanceInfo
|
||||
ApplyState *InstanceState
|
||||
ApplyDiff *InstanceDiff
|
||||
ApplyFn func(*InstanceInfo, *InstanceState, *InstanceDiff) (*InstanceState, error)
|
||||
ApplyReturn *InstanceState
|
||||
ApplyReturnError error
|
||||
ConfigureCalled bool
|
||||
ConfigureConfig *ResourceConfig
|
||||
ConfigureFn func(*ResourceConfig) error
|
||||
ConfigureReturnError error
|
||||
DiffCalled bool
|
||||
DiffInfo *InstanceInfo
|
||||
DiffState *InstanceState
|
||||
DiffDesired *ResourceConfig
|
||||
DiffFn func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error)
|
||||
DiffReturn *InstanceDiff
|
||||
DiffReturnError error
|
||||
RefreshCalled bool
|
||||
RefreshInfo *InstanceInfo
|
||||
RefreshState *InstanceState
|
||||
RefreshFn func(*InstanceInfo, *InstanceState) (*InstanceState, error)
|
||||
RefreshReturn *InstanceState
|
||||
RefreshReturnError error
|
||||
ResourcesCalled bool
|
||||
ResourcesReturn []ResourceType
|
||||
ReadDataApplyCalled bool
|
||||
ReadDataApplyInfo *InstanceInfo
|
||||
ReadDataApplyDiff *InstanceDiff
|
||||
ReadDataApplyFn func(*InstanceInfo, *InstanceDiff) (*InstanceState, error)
|
||||
ReadDataApplyReturn *InstanceState
|
||||
ReadDataApplyReturnError error
|
||||
ReadDataDiffCalled bool
|
||||
ReadDataDiffInfo *InstanceInfo
|
||||
ReadDataDiffDesired *ResourceConfig
|
||||
ReadDataDiffFn func(*InstanceInfo, *ResourceConfig) (*InstanceDiff, error)
|
||||
ReadDataDiffReturn *InstanceDiff
|
||||
ReadDataDiffReturnError error
|
||||
StopCalled bool
|
||||
StopFn func() error
|
||||
StopReturnError error
|
||||
DataSourcesCalled bool
|
||||
DataSourcesReturn []DataSource
|
||||
ValidateCalled bool
|
||||
ValidateConfig *ResourceConfig
|
||||
ValidateFn func(*ResourceConfig) ([]string, []error)
|
||||
ValidateReturnWarns []string
|
||||
ValidateReturnErrors []error
|
||||
ValidateResourceFn func(string, *ResourceConfig) ([]string, []error)
|
||||
ValidateResourceCalled bool
|
||||
ValidateResourceType string
|
||||
ValidateResourceConfig *ResourceConfig
|
||||
ValidateResourceReturnWarns []string
|
||||
ValidateResourceReturnErrors []error
|
||||
ValidateDataSourceFn func(string, *ResourceConfig) ([]string, []error)
|
||||
ValidateDataSourceCalled bool
|
||||
ValidateDataSourceType string
|
||||
ValidateDataSourceConfig *ResourceConfig
|
||||
ValidateDataSourceReturnWarns []string
|
||||
ValidateDataSourceReturnErrors []error
|
||||
|
||||
ImportStateCalled bool
|
||||
ImportStateInfo *InstanceInfo
|
||||
ImportStateID string
|
||||
ImportStateReturn []*InstanceState
|
||||
ImportStateReturnError error
|
||||
ImportStateFn func(*InstanceInfo, string) ([]*InstanceState, error)
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Close() error {
|
||||
p.CloseCalled = true
|
||||
return p.CloseError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) GetSchema(req *ProviderSchemaRequest) (*ProviderSchema, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.GetSchemaCalled = true
|
||||
p.GetSchemaRequest = req
|
||||
return p.GetSchemaReturn, p.GetSchemaReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Input(
|
||||
input UIInput, c *ResourceConfig) (*ResourceConfig, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
p.InputCalled = true
|
||||
p.InputInput = input
|
||||
p.InputConfig = c
|
||||
if p.InputFn != nil {
|
||||
return p.InputFn(input, c)
|
||||
}
|
||||
return p.InputReturnConfig, p.InputReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Validate(c *ResourceConfig) ([]string, []error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ValidateCalled = true
|
||||
p.ValidateConfig = c
|
||||
if p.ValidateFn != nil {
|
||||
return p.ValidateFn(c)
|
||||
}
|
||||
return p.ValidateReturnWarns, p.ValidateReturnErrors
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ValidateResource(t string, c *ResourceConfig) ([]string, []error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ValidateResourceCalled = true
|
||||
p.ValidateResourceType = t
|
||||
p.ValidateResourceConfig = c
|
||||
|
||||
if p.ValidateResourceFn != nil {
|
||||
return p.ValidateResourceFn(t, c)
|
||||
}
|
||||
|
||||
return p.ValidateResourceReturnWarns, p.ValidateResourceReturnErrors
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Configure(c *ResourceConfig) error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ConfigureCalled = true
|
||||
p.ConfigureConfig = c
|
||||
|
||||
if p.ConfigureFn != nil {
|
||||
return p.ConfigureFn(c)
|
||||
}
|
||||
|
||||
return p.ConfigureReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Stop() error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.StopCalled = true
|
||||
if p.StopFn != nil {
|
||||
return p.StopFn()
|
||||
}
|
||||
|
||||
return p.StopReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Apply(
|
||||
info *InstanceInfo,
|
||||
state *InstanceState,
|
||||
diff *InstanceDiff) (*InstanceState, error) {
|
||||
// We only lock while writing data. Reading is fine
|
||||
p.Lock()
|
||||
p.ApplyCalled = true
|
||||
p.ApplyInfo = info
|
||||
p.ApplyState = state
|
||||
p.ApplyDiff = diff
|
||||
p.Unlock()
|
||||
|
||||
if p.ApplyFn != nil {
|
||||
return p.ApplyFn(info, state, diff)
|
||||
}
|
||||
|
||||
return p.ApplyReturn.DeepCopy(), p.ApplyReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Diff(
|
||||
info *InstanceInfo,
|
||||
state *InstanceState,
|
||||
desired *ResourceConfig) (*InstanceDiff, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.DiffCalled = true
|
||||
p.DiffInfo = info
|
||||
p.DiffState = state
|
||||
p.DiffDesired = desired
|
||||
|
||||
if p.DiffFn != nil {
|
||||
return p.DiffFn(info, state, desired)
|
||||
}
|
||||
|
||||
return p.DiffReturn.DeepCopy(), p.DiffReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Refresh(
|
||||
info *InstanceInfo,
|
||||
s *InstanceState) (*InstanceState, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.RefreshCalled = true
|
||||
p.RefreshInfo = info
|
||||
p.RefreshState = s
|
||||
|
||||
if p.RefreshFn != nil {
|
||||
return p.RefreshFn(info, s)
|
||||
}
|
||||
|
||||
return p.RefreshReturn.DeepCopy(), p.RefreshReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Resources() []ResourceType {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ResourcesCalled = true
|
||||
return p.ResourcesReturn
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ImportState(info *InstanceInfo, id string) ([]*InstanceState, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ImportStateCalled = true
|
||||
p.ImportStateInfo = info
|
||||
p.ImportStateID = id
|
||||
if p.ImportStateFn != nil {
|
||||
return p.ImportStateFn(info, id)
|
||||
}
|
||||
|
||||
var result []*InstanceState
|
||||
if p.ImportStateReturn != nil {
|
||||
result = make([]*InstanceState, len(p.ImportStateReturn))
|
||||
for i, v := range p.ImportStateReturn {
|
||||
result[i] = v.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
return result, p.ImportStateReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ValidateDataSource(t string, c *ResourceConfig) ([]string, []error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ValidateDataSourceCalled = true
|
||||
p.ValidateDataSourceType = t
|
||||
p.ValidateDataSourceConfig = c
|
||||
|
||||
if p.ValidateDataSourceFn != nil {
|
||||
return p.ValidateDataSourceFn(t, c)
|
||||
}
|
||||
|
||||
return p.ValidateDataSourceReturnWarns, p.ValidateDataSourceReturnErrors
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ReadDataDiff(
|
||||
info *InstanceInfo,
|
||||
desired *ResourceConfig) (*InstanceDiff, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ReadDataDiffCalled = true
|
||||
p.ReadDataDiffInfo = info
|
||||
p.ReadDataDiffDesired = desired
|
||||
if p.ReadDataDiffFn != nil {
|
||||
return p.ReadDataDiffFn(info, desired)
|
||||
}
|
||||
|
||||
return p.ReadDataDiffReturn.DeepCopy(), p.ReadDataDiffReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ReadDataApply(
|
||||
info *InstanceInfo,
|
||||
d *InstanceDiff) (*InstanceState, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ReadDataApplyCalled = true
|
||||
p.ReadDataApplyInfo = info
|
||||
p.ReadDataApplyDiff = d
|
||||
|
||||
if p.ReadDataApplyFn != nil {
|
||||
return p.ReadDataApplyFn(info, d)
|
||||
}
|
||||
|
||||
return p.ReadDataApplyReturn.DeepCopy(), p.ReadDataApplyReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) DataSources() []DataSource {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.DataSourcesCalled = true
|
||||
return p.DataSourcesReturn
|
||||
}
|
|
@ -1,19 +1,12 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/hashicorp/terraform/providers"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestMockResourceProvider_impl(t *testing.T) {
|
||||
var _ ResourceProvider = new(MockResourceProvider)
|
||||
var _ ResourceProviderCloser = new(MockResourceProvider)
|
||||
}
|
||||
|
||||
// testProviderComponentFactory creates a componentFactory that contains only
|
||||
// a single given.
|
||||
func testProviderComponentFactory(name string, provider providers.Interface) *basicComponentFactory {
|
||||
|
@ -62,18 +55,6 @@ func mockProviderWithResourceTypeSchema(name string, schema *configschema.Block)
|
|||
}
|
||||
}
|
||||
|
||||
// mockProviderWithDataSourceSchema is a test helper to concisely create a mock
|
||||
// provider with a schema containing a single data source.
|
||||
func mockProviderWithDataSourceSchema(name string, schema *configschema.Block) *MockResourceProvider {
|
||||
return &MockResourceProvider{
|
||||
GetSchemaReturn: &ProviderSchema{
|
||||
DataSources: map[string]*configschema.Block{
|
||||
name: schema,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// simpleMockProvider returns a MockProvider that is pre-configured
|
||||
// with schema for its own config, for a resource type called "test_object" and
|
||||
// for a data source also called "test_object".
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/hashicorp/terraform/provisioners"
|
||||
)
|
||||
|
||||
// ResourceProvisioner is an interface that must be implemented by any
|
||||
// resource provisioner: the thing that initializes resources in
|
||||
// a Terraform configuration.
|
||||
type ResourceProvisioner interface {
|
||||
// GetConfigSchema returns the schema for the provisioner type's main
|
||||
// configuration block. This is called prior to Validate to enable some
|
||||
// basic structural validation to be performed automatically and to allow
|
||||
// the configuration to be properly extracted from potentially-ambiguous
|
||||
// configuration file formats.
|
||||
GetConfigSchema() (*configschema.Block, error)
|
||||
|
||||
// Validate is called once at the beginning with the raw
|
||||
// configuration (no interpolation done) and can return a list of warnings
|
||||
// and/or errors.
|
||||
//
|
||||
// This is called once per resource.
|
||||
//
|
||||
// This should not assume any of the values in the resource configuration
|
||||
// are valid since it is possible they have to be interpolated still.
|
||||
// The primary use case of this call is to check that the required keys
|
||||
// are set and that the general structure is correct.
|
||||
Validate(*ResourceConfig) ([]string, []error)
|
||||
|
||||
// Apply runs the provisioner on a specific resource and returns an error.
|
||||
// Instead of a diff, the ResourceConfig is provided since provisioners
|
||||
// only run after a resource has been newly created.
|
||||
Apply(UIOutput, *InstanceState, *ResourceConfig) error
|
||||
|
||||
// Stop is called when the provisioner should halt any in-flight actions.
|
||||
//
|
||||
// This can be used to make a nicer Ctrl-C experience for Terraform.
|
||||
// Even if this isn't implemented to do anything (just returns nil),
|
||||
// Terraform will still cleanly stop after the currently executing
|
||||
// graph node is complete. However, this API can be used to make more
|
||||
// efficient halts.
|
||||
//
|
||||
// Stop doesn't have to and shouldn't block waiting for in-flight actions
|
||||
// to complete. It should take any action it wants and return immediately
|
||||
// acknowledging it has received the stop request. Terraform core will
|
||||
// automatically not make any further API calls to the provider soon
|
||||
// after Stop is called (technically exactly once the currently executing
|
||||
// graph nodes are complete).
|
||||
//
|
||||
// The error returned, if non-nil, is assumed to mean that signaling the
|
||||
// stop somehow failed and that the user should expect potentially waiting
|
||||
// a longer period of time.
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// ResourceProvisionerCloser is an interface that provisioners that can close
|
||||
// connections that aren't needed anymore must implement.
|
||||
type ResourceProvisionerCloser interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ResourceProvisionerFactory is a function type that creates a new instance
|
||||
// of a resource provisioner.
|
||||
type ResourceProvisionerFactory func() (ResourceProvisioner, error)
|
||||
|
||||
// ProvisionerFactory is a function type that creates a new instance
|
||||
// of a provisioners.Interface.
|
||||
type ProvisionerFactory = provisioners.Factory
|
|
@ -1,87 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
)
|
||||
|
||||
// MockResourceProvisioner implements ResourceProvisioner but mocks out all the
|
||||
// calls for testing purposes.
|
||||
type MockResourceProvisioner struct {
|
||||
sync.Mutex
|
||||
// Anything you want, in case you need to store extra data with the mock.
|
||||
Meta interface{}
|
||||
|
||||
GetConfigSchemaCalled bool
|
||||
GetConfigSchemaReturnSchema *configschema.Block
|
||||
GetConfigSchemaReturnError error
|
||||
|
||||
ApplyCalled bool
|
||||
ApplyOutput UIOutput
|
||||
ApplyState *InstanceState
|
||||
ApplyConfig *ResourceConfig
|
||||
ApplyFn func(*InstanceState, *ResourceConfig) error
|
||||
ApplyReturnError error
|
||||
|
||||
ValidateCalled bool
|
||||
ValidateConfig *ResourceConfig
|
||||
ValidateFn func(c *ResourceConfig) ([]string, []error)
|
||||
ValidateReturnWarns []string
|
||||
ValidateReturnErrors []error
|
||||
|
||||
StopCalled bool
|
||||
StopFn func() error
|
||||
StopReturnError error
|
||||
}
|
||||
|
||||
var _ ResourceProvisioner = (*MockResourceProvisioner)(nil)
|
||||
|
||||
func (p *MockResourceProvisioner) GetConfigSchema() (*configschema.Block, error) {
|
||||
p.GetConfigSchemaCalled = true
|
||||
return p.GetConfigSchemaReturnSchema, p.GetConfigSchemaReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvisioner) Validate(c *ResourceConfig) ([]string, []error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ValidateCalled = true
|
||||
p.ValidateConfig = c
|
||||
if p.ValidateFn != nil {
|
||||
return p.ValidateFn(c)
|
||||
}
|
||||
return p.ValidateReturnWarns, p.ValidateReturnErrors
|
||||
}
|
||||
|
||||
func (p *MockResourceProvisioner) Apply(
|
||||
output UIOutput,
|
||||
state *InstanceState,
|
||||
c *ResourceConfig) error {
|
||||
p.Lock()
|
||||
|
||||
p.ApplyCalled = true
|
||||
p.ApplyOutput = output
|
||||
p.ApplyState = state
|
||||
p.ApplyConfig = c
|
||||
if p.ApplyFn != nil {
|
||||
fn := p.ApplyFn
|
||||
p.Unlock()
|
||||
return fn(state, c)
|
||||
}
|
||||
|
||||
defer p.Unlock()
|
||||
return p.ApplyReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvisioner) Stop() error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.StopCalled = true
|
||||
if p.StopFn != nil {
|
||||
return p.StopFn()
|
||||
}
|
||||
|
||||
return p.StopReturnError
|
||||
}
|
|
@ -1,674 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
)
|
||||
|
||||
func TestResourceConfigGet(t *testing.T) {
|
||||
fooStringSchema := &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true},
|
||||
},
|
||||
}
|
||||
fooListSchema := &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.List(cty.Number), Optional: true},
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
Config cty.Value
|
||||
Schema *configschema.Block
|
||||
Key string
|
||||
Value interface{}
|
||||
}{
|
||||
{
|
||||
Config: cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.StringVal("bar"),
|
||||
}),
|
||||
Schema: fooStringSchema,
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
|
||||
{
|
||||
Config: cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.UnknownVal(cty.String),
|
||||
}),
|
||||
Schema: fooStringSchema,
|
||||
Key: "foo",
|
||||
Value: hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
|
||||
{
|
||||
Config: cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.ListVal([]cty.Value{
|
||||
cty.NumberIntVal(1),
|
||||
cty.NumberIntVal(2),
|
||||
cty.NumberIntVal(5),
|
||||
}),
|
||||
}),
|
||||
Schema: fooListSchema,
|
||||
Key: "foo.0",
|
||||
Value: 1,
|
||||
},
|
||||
|
||||
{
|
||||
Config: cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.ListVal([]cty.Value{
|
||||
cty.NumberIntVal(1),
|
||||
cty.NumberIntVal(2),
|
||||
cty.NumberIntVal(5),
|
||||
}),
|
||||
}),
|
||||
Schema: fooListSchema,
|
||||
Key: "foo.5",
|
||||
Value: nil,
|
||||
},
|
||||
|
||||
{
|
||||
Config: cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.ListVal([]cty.Value{
|
||||
cty.NumberIntVal(1),
|
||||
cty.NumberIntVal(2),
|
||||
cty.NumberIntVal(5),
|
||||
}),
|
||||
}),
|
||||
Schema: fooListSchema,
|
||||
Key: "foo.-1",
|
||||
Value: nil,
|
||||
},
|
||||
|
||||
// get from map
|
||||
{
|
||||
Config: cty.ObjectVal(map[string]cty.Value{
|
||||
"mapname": cty.ListVal([]cty.Value{
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"key": cty.NumberIntVal(1),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
|
||||
},
|
||||
},
|
||||
Key: "mapname.0.key",
|
||||
Value: 1,
|
||||
},
|
||||
|
||||
// get from map with dot in key
|
||||
{
|
||||
Config: cty.ObjectVal(map[string]cty.Value{
|
||||
"mapname": cty.ListVal([]cty.Value{
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"key.name": cty.NumberIntVal(1),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
|
||||
},
|
||||
},
|
||||
Key: "mapname.0.key.name",
|
||||
Value: 1,
|
||||
},
|
||||
|
||||
// get from map with overlapping key names
|
||||
{
|
||||
Config: cty.ObjectVal(map[string]cty.Value{
|
||||
"mapname": cty.ListVal([]cty.Value{
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"key.name": cty.NumberIntVal(1),
|
||||
"key.name.2": cty.NumberIntVal(2),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
|
||||
},
|
||||
},
|
||||
Key: "mapname.0.key.name.2",
|
||||
Value: 2,
|
||||
},
|
||||
{
|
||||
Config: cty.ObjectVal(map[string]cty.Value{
|
||||
"mapname": cty.ListVal([]cty.Value{
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"key.name": cty.NumberIntVal(1),
|
||||
"key.name.foo": cty.NumberIntVal(2),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true},
|
||||
},
|
||||
},
|
||||
Key: "mapname.0.key.name",
|
||||
Value: 1,
|
||||
},
|
||||
{
|
||||
Config: cty.ObjectVal(map[string]cty.Value{
|
||||
"mapname": cty.ListVal([]cty.Value{
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"listkey": cty.ListVal([]cty.Value{
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"key": cty.NumberIntVal(3),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"mapname": {Type: cty.List(cty.Map(cty.List(cty.Map(cty.Number)))), Optional: true},
|
||||
},
|
||||
},
|
||||
Key: "mapname.0.listkey.0.key",
|
||||
Value: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
rc := NewResourceConfigShimmed(tc.Config, tc.Schema)
|
||||
|
||||
// Test getting a key
|
||||
t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) {
|
||||
v, ok := rc.Get(tc.Key)
|
||||
if ok && v == nil {
|
||||
t.Fatal("(nil, true) returned from Get")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(v, tc.Value) {
|
||||
t.Fatalf("%d bad: %#v", i, v)
|
||||
}
|
||||
})
|
||||
|
||||
// Test copying and equality
|
||||
t.Run(fmt.Sprintf("copy-and-equal-%d", i), func(t *testing.T) {
|
||||
copy := rc.DeepCopy()
|
||||
if !reflect.DeepEqual(copy, rc) {
|
||||
t.Fatalf("bad:\n\n%#v\n\n%#v", copy, rc)
|
||||
}
|
||||
|
||||
if !copy.Equal(rc) {
|
||||
t.Fatalf("copy != rc:\n\n%#v\n\n%#v", copy, rc)
|
||||
}
|
||||
if !rc.Equal(copy) {
|
||||
t.Fatalf("rc != copy:\n\n%#v\n\n%#v", copy, rc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfigDeepCopy_nil(t *testing.T) {
|
||||
var nilRc *ResourceConfig
|
||||
actual := nilRc.DeepCopy()
|
||||
if actual != nil {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfigDeepCopy_nilComputed(t *testing.T) {
|
||||
rc := &ResourceConfig{}
|
||||
actual := rc.DeepCopy()
|
||||
if actual.ComputedKeys != nil {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfigEqual_nil(t *testing.T) {
|
||||
var nilRc *ResourceConfig
|
||||
notNil := NewResourceConfigShimmed(cty.EmptyObjectVal, &configschema.Block{})
|
||||
|
||||
if nilRc.Equal(notNil) {
|
||||
t.Fatal("should not be equal")
|
||||
}
|
||||
|
||||
if notNil.Equal(nilRc) {
|
||||
t.Fatal("should not be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfigEqual_computedKeyOrder(t *testing.T) {
|
||||
v := cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.UnknownVal(cty.String),
|
||||
})
|
||||
schema := &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {Type: cty.String, Optional: true},
|
||||
},
|
||||
}
|
||||
rc := NewResourceConfigShimmed(v, schema)
|
||||
rc2 := NewResourceConfigShimmed(v, schema)
|
||||
|
||||
// Set the computed keys manually to force ordering to differ
|
||||
rc.ComputedKeys = []string{"foo", "bar"}
|
||||
rc2.ComputedKeys = []string{"bar", "foo"}
|
||||
|
||||
if !rc.Equal(rc2) {
|
||||
t.Fatal("should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownCheckWalker(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Input interface{}
|
||||
Result bool
|
||||
}{
|
||||
{
|
||||
"primitive",
|
||||
42,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"primitive computed",
|
||||
hcl2shim.UnknownVariableValue,
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"list",
|
||||
[]interface{}{"foo", hcl2shim.UnknownVariableValue},
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"nested list",
|
||||
[]interface{}{
|
||||
"foo",
|
||||
[]interface{}{hcl2shim.UnknownVariableValue},
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||
var w unknownCheckWalker
|
||||
if err := reflectwalk.Walk(tc.Input, &w); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if w.Unknown != tc.Result {
|
||||
t.Fatalf("bad: %v", w.Unknown)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewResourceConfigShimmed(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
Name string
|
||||
Val cty.Value
|
||||
Schema *configschema.Block
|
||||
Expected *ResourceConfig
|
||||
}{
|
||||
{
|
||||
Name: "empty object",
|
||||
Val: cty.NullVal(cty.EmptyObject),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
Raw: map[string]interface{}{},
|
||||
Config: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "basic",
|
||||
Val: cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.StringVal("bar"),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
Raw: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "null string",
|
||||
Val: cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.NullVal(cty.String),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
Raw: map[string]interface{}{},
|
||||
Config: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unknown string",
|
||||
Val: cty.ObjectVal(map[string]cty.Value{
|
||||
"foo": cty.UnknownVal(cty.String),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
ComputedKeys: []string{"foo"},
|
||||
Raw: map[string]interface{}{
|
||||
"foo": hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
Config: map[string]interface{}{
|
||||
"foo": hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unknown collections",
|
||||
Val: cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.UnknownVal(cty.Map(cty.String)),
|
||||
"baz": cty.UnknownVal(cty.List(cty.String)),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"bar": {
|
||||
Type: cty.Map(cty.String),
|
||||
Required: true,
|
||||
},
|
||||
"baz": {
|
||||
Type: cty.List(cty.String),
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
ComputedKeys: []string{"bar", "baz"},
|
||||
Raw: map[string]interface{}{
|
||||
"bar": hcl2shim.UnknownVariableValue,
|
||||
"baz": hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
Config: map[string]interface{}{
|
||||
"bar": hcl2shim.UnknownVariableValue,
|
||||
"baz": hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "null collections",
|
||||
Val: cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.NullVal(cty.Map(cty.String)),
|
||||
"baz": cty.NullVal(cty.List(cty.String)),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"bar": {
|
||||
Type: cty.Map(cty.String),
|
||||
Required: true,
|
||||
},
|
||||
"baz": {
|
||||
Type: cty.List(cty.String),
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
Raw: map[string]interface{}{},
|
||||
Config: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unknown blocks",
|
||||
Val: cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.UnknownVal(cty.Map(cty.String)),
|
||||
"baz": cty.UnknownVal(cty.List(cty.String)),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"bar": {
|
||||
Block: configschema.Block{},
|
||||
Nesting: configschema.NestingList,
|
||||
},
|
||||
"baz": {
|
||||
Block: configschema.Block{},
|
||||
Nesting: configschema.NestingSet,
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
ComputedKeys: []string{"bar", "baz"},
|
||||
Raw: map[string]interface{}{
|
||||
"bar": hcl2shim.UnknownVariableValue,
|
||||
"baz": hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
Config: map[string]interface{}{
|
||||
"bar": hcl2shim.UnknownVariableValue,
|
||||
"baz": hcl2shim.UnknownVariableValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unknown in nested blocks",
|
||||
Val: cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.ListVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"baz": cty.ListVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"list": cty.UnknownVal(cty.List(cty.String)),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"bar": {
|
||||
Block: configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"baz": {
|
||||
Block: configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"list": {Type: cty.List(cty.String),
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Nesting: configschema.NestingList,
|
||||
},
|
||||
},
|
||||
},
|
||||
Nesting: configschema.NestingList,
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
ComputedKeys: []string{"bar.0.baz.0.list"},
|
||||
Raw: map[string]interface{}{
|
||||
"bar": []interface{}{map[string]interface{}{
|
||||
"baz": []interface{}{map[string]interface{}{
|
||||
"list": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
Config: map[string]interface{}{
|
||||
"bar": []interface{}{map[string]interface{}{
|
||||
"baz": []interface{}{map[string]interface{}{
|
||||
"list": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unknown in set",
|
||||
Val: cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.SetVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"val": cty.UnknownVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"bar": {
|
||||
Block: configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"val": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Nesting: configschema.NestingSet,
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
ComputedKeys: []string{"bar.0.val"},
|
||||
Raw: map[string]interface{}{
|
||||
"bar": []interface{}{map[string]interface{}{
|
||||
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
||||
}},
|
||||
},
|
||||
Config: map[string]interface{}{
|
||||
"bar": []interface{}{map[string]interface{}{
|
||||
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unknown in attribute sets",
|
||||
Val: cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.SetVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"val": cty.UnknownVal(cty.String),
|
||||
}),
|
||||
}),
|
||||
"baz": cty.SetVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"obj": cty.UnknownVal(cty.Object(map[string]cty.Type{
|
||||
"attr": cty.List(cty.String),
|
||||
})),
|
||||
}),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"obj": cty.ObjectVal(map[string]cty.Value{
|
||||
"attr": cty.UnknownVal(cty.List(cty.String)),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"bar": &configschema.Attribute{
|
||||
Type: cty.Set(cty.Object(map[string]cty.Type{
|
||||
"val": cty.String,
|
||||
})),
|
||||
},
|
||||
"baz": &configschema.Attribute{
|
||||
Type: cty.Set(cty.Object(map[string]cty.Type{
|
||||
"obj": cty.Object(map[string]cty.Type{
|
||||
"attr": cty.List(cty.String),
|
||||
}),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
ComputedKeys: []string{"bar.0.val", "baz.0.obj.attr", "baz.1.obj"},
|
||||
Raw: map[string]interface{}{
|
||||
"bar": []interface{}{map[string]interface{}{
|
||||
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
||||
}},
|
||||
"baz": []interface{}{
|
||||
map[string]interface{}{
|
||||
"obj": map[string]interface{}{
|
||||
"attr": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"obj": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
||||
},
|
||||
},
|
||||
},
|
||||
Config: map[string]interface{}{
|
||||
"bar": []interface{}{map[string]interface{}{
|
||||
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
||||
}},
|
||||
"baz": []interface{}{
|
||||
map[string]interface{}{
|
||||
"obj": map[string]interface{}{
|
||||
"attr": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"obj": "74D93920-ED26-11E3-AC10-0800200C9A66",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "null blocks",
|
||||
Val: cty.ObjectVal(map[string]cty.Value{
|
||||
"bar": cty.NullVal(cty.Map(cty.String)),
|
||||
"baz": cty.NullVal(cty.List(cty.String)),
|
||||
}),
|
||||
Schema: &configschema.Block{
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"bar": {
|
||||
Block: configschema.Block{},
|
||||
Nesting: configschema.NestingMap,
|
||||
},
|
||||
"baz": {
|
||||
Block: configschema.Block{},
|
||||
Nesting: configschema.NestingSingle,
|
||||
},
|
||||
},
|
||||
},
|
||||
Expected: &ResourceConfig{
|
||||
Raw: map[string]interface{}{},
|
||||
Config: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.Name, func(*testing.T) {
|
||||
cfg := NewResourceConfigShimmed(tc.Val, tc.Schema)
|
||||
if !tc.Expected.Equal(cfg) {
|
||||
t.Fatalf("expected:\n%#v\ngot:\n%#v", tc.Expected, cfg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -205,9 +205,7 @@ func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *conf
|
|||
return
|
||||
}
|
||||
defer func() {
|
||||
if closer, ok := provisioner.(ResourceProvisionerCloser); ok {
|
||||
closer.Close()
|
||||
}
|
||||
provisioner.Close()
|
||||
}()
|
||||
|
||||
resp := provisioner.GetSchema()
|
||||
|
|
2255
terraform/state.go
2255
terraform/state.go
File diff suppressed because it is too large
Load Diff
|
@ -1,267 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// StateFilter is responsible for filtering and searching a state.
|
||||
//
|
||||
// This is a separate struct from State rather than a method on State
|
||||
// because StateFilter might create sidecar data structures to optimize
|
||||
// filtering on the state.
|
||||
//
|
||||
// If you change the State, the filter created is invalid and either
|
||||
// Reset should be called or a new one should be allocated. StateFilter
|
||||
// will not watch State for changes and do this for you. If you filter after
|
||||
// changing the State without calling Reset, the behavior is not defined.
|
||||
type StateFilter struct {
|
||||
State *State
|
||||
}
|
||||
|
||||
// Filter takes the addresses specified by fs and finds all the matches.
|
||||
// The values of fs are resource addressing syntax that can be parsed by
|
||||
// ParseResourceAddress.
|
||||
func (f *StateFilter) Filter(fs ...string) ([]*StateFilterResult, error) {
|
||||
// Parse all the addresses
|
||||
as := make([]*ResourceAddress, len(fs))
|
||||
for i, v := range fs {
|
||||
a, err := ParseResourceAddress(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing address '%s': %s", v, err)
|
||||
}
|
||||
|
||||
as[i] = a
|
||||
}
|
||||
|
||||
// If we weren't given any filters, then we list all
|
||||
if len(fs) == 0 {
|
||||
as = append(as, &ResourceAddress{Index: -1})
|
||||
}
|
||||
|
||||
// Filter each of the address. We keep track of this in a map to
|
||||
// strip duplicates.
|
||||
resultSet := make(map[string]*StateFilterResult)
|
||||
for _, a := range as {
|
||||
for _, r := range f.filterSingle(a) {
|
||||
resultSet[r.String()] = r
|
||||
}
|
||||
}
|
||||
|
||||
// Make the result list
|
||||
results := make([]*StateFilterResult, 0, len(resultSet))
|
||||
for _, v := range resultSet {
|
||||
results = append(results, v)
|
||||
}
|
||||
|
||||
// Sort them and return
|
||||
sort.Sort(StateFilterResultSlice(results))
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
||||
// The slice to keep track of results
|
||||
var results []*StateFilterResult
|
||||
|
||||
// Go through modules first.
|
||||
modules := make([]*ModuleState, 0, len(f.State.Modules))
|
||||
for _, m := range f.State.Modules {
|
||||
if f.relevant(a, m) {
|
||||
modules = append(modules, m)
|
||||
|
||||
// Only add the module to the results if we haven't specified a type.
|
||||
// We also ignore the root module.
|
||||
if a.Type == "" && len(m.Path) > 1 {
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: m.Path[1:],
|
||||
Address: (&ResourceAddress{Path: m.Path[1:]}).String(),
|
||||
Value: m,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With the modules set, go through all the resources within
|
||||
// the modules to find relevant resources.
|
||||
for _, m := range modules {
|
||||
for n, r := range m.Resources {
|
||||
// The name in the state contains valuable information. Parse.
|
||||
key, err := ParseResourceStateKey(n)
|
||||
if err != nil {
|
||||
// If we get an error parsing, then just ignore it
|
||||
// out of the state.
|
||||
continue
|
||||
}
|
||||
|
||||
// Older states and test fixtures often don't contain the
|
||||
// type directly on the ResourceState. We add this so StateFilter
|
||||
// is a bit more robust.
|
||||
if r.Type == "" {
|
||||
r.Type = key.Type
|
||||
}
|
||||
|
||||
if f.relevant(a, r) {
|
||||
if a.Name != "" && a.Name != key.Name {
|
||||
// Name doesn't match
|
||||
continue
|
||||
}
|
||||
|
||||
if a.Index >= 0 && key.Index != a.Index {
|
||||
// Index doesn't match
|
||||
continue
|
||||
}
|
||||
|
||||
if a.Name != "" && a.Name != key.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
// Build the address for this resource
|
||||
addr := &ResourceAddress{
|
||||
Path: m.Path[1:],
|
||||
Name: key.Name,
|
||||
Type: key.Type,
|
||||
Index: key.Index,
|
||||
}
|
||||
|
||||
// Add the resource level result
|
||||
resourceResult := &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
Address: addr.String(),
|
||||
Value: r,
|
||||
}
|
||||
if !a.InstanceTypeSet {
|
||||
results = append(results, resourceResult)
|
||||
}
|
||||
|
||||
// Add the instances
|
||||
if r.Primary != nil {
|
||||
addr.InstanceType = TypePrimary
|
||||
addr.InstanceTypeSet = false
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
Address: addr.String(),
|
||||
Parent: resourceResult,
|
||||
Value: r.Primary,
|
||||
})
|
||||
}
|
||||
|
||||
for _, instance := range r.Deposed {
|
||||
if f.relevant(a, instance) {
|
||||
addr.InstanceType = TypeDeposed
|
||||
addr.InstanceTypeSet = true
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
Address: addr.String(),
|
||||
Parent: resourceResult,
|
||||
Value: instance,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// relevant checks for relevance of this address against the given value.
|
||||
func (f *StateFilter) relevant(addr *ResourceAddress, raw interface{}) bool {
|
||||
switch v := raw.(type) {
|
||||
case *ModuleState:
|
||||
path := v.Path[1:]
|
||||
|
||||
if len(addr.Path) > len(path) {
|
||||
// Longer path in address means there is no way we match.
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for a prefix match
|
||||
for i, p := range addr.Path {
|
||||
if path[i] != p {
|
||||
// Any mismatches don't match.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
case *ResourceState:
|
||||
if addr.Type == "" {
|
||||
// If we have no resource type, then we're interested in all!
|
||||
return true
|
||||
}
|
||||
|
||||
// If the type doesn't match we fail immediately
|
||||
if v.Type != addr.Type {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
default:
|
||||
// If we don't know about it, let's just say no
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// StateFilterResult is a single result from a filter operation. Filter
|
||||
// can match multiple things within a state (module, resource, instance, etc.)
|
||||
// and this unifies that.
|
||||
type StateFilterResult struct {
|
||||
// Module path of the result
|
||||
Path []string
|
||||
|
||||
// Address is the address that can be used to reference this exact result.
|
||||
Address string
|
||||
|
||||
// Parent, if non-nil, is a parent of this result. For instances, the
|
||||
// parent would be a resource. For resources, the parent would be
|
||||
// a module. For modules, this is currently nil.
|
||||
Parent *StateFilterResult
|
||||
|
||||
// Value is the actual value. This must be type switched on. It can be
|
||||
// any data structures that `State` can hold: `ModuleState`,
|
||||
// `ResourceState`, `InstanceState`.
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (r *StateFilterResult) String() string {
|
||||
return fmt.Sprintf("%T: %s", r.Value, r.Address)
|
||||
}
|
||||
|
||||
func (r *StateFilterResult) sortedType() int {
|
||||
switch r.Value.(type) {
|
||||
case *ModuleState:
|
||||
return 0
|
||||
case *ResourceState:
|
||||
return 1
|
||||
case *InstanceState:
|
||||
return 2
|
||||
default:
|
||||
return 50
|
||||
}
|
||||
}
|
||||
|
||||
// StateFilterResultSlice is a slice of results that implements
|
||||
// sort.Interface. The sorting goal is what is most appealing to
|
||||
// human output.
|
||||
type StateFilterResultSlice []*StateFilterResult
|
||||
|
||||
func (s StateFilterResultSlice) Len() int { return len(s) }
|
||||
func (s StateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s StateFilterResultSlice) Less(i, j int) bool {
|
||||
a, b := s[i], s[j]
|
||||
|
||||
// if these address contain an index, we want to sort by index rather than name
|
||||
addrA, errA := ParseResourceAddress(a.Address)
|
||||
addrB, errB := ParseResourceAddress(b.Address)
|
||||
if errA == nil && errB == nil && addrA.Name == addrB.Name && addrA.Index != addrB.Index {
|
||||
return addrA.Index < addrB.Index
|
||||
}
|
||||
|
||||
// If the addresses are different it is just lexographic sorting
|
||||
if a.Address != b.Address {
|
||||
return a.Address < b.Address
|
||||
}
|
||||
|
||||
// Addresses are the same, which means it matters on the type
|
||||
return a.sortedType() < b.sortedType()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,189 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/copystructure"
|
||||
)
|
||||
|
||||
// upgradeStateV1ToV2 is used to upgrade a V1 state representation
|
||||
// into a V2 state representation
|
||||
func upgradeStateV1ToV2(old *stateV1) (*State, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
remote, err := old.Remote.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading State V1: %v", err)
|
||||
}
|
||||
|
||||
modules := make([]*ModuleState, len(old.Modules))
|
||||
for i, module := range old.Modules {
|
||||
upgraded, err := module.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading State V1: %v", err)
|
||||
}
|
||||
modules[i] = upgraded
|
||||
}
|
||||
if len(modules) == 0 {
|
||||
modules = nil
|
||||
}
|
||||
|
||||
newState := &State{
|
||||
Version: 2,
|
||||
Serial: old.Serial,
|
||||
Remote: remote,
|
||||
Modules: modules,
|
||||
}
|
||||
|
||||
newState.sort()
|
||||
newState.init()
|
||||
|
||||
return newState, nil
|
||||
}
|
||||
|
||||
func (old *remoteStateV1) upgradeToV2() (*RemoteState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
config, err := copystructure.Copy(old.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading RemoteState V1: %v", err)
|
||||
}
|
||||
|
||||
return &RemoteState{
|
||||
Type: old.Type,
|
||||
Config: config.(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (old *moduleStateV1) upgradeToV2() (*ModuleState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pathRaw, err := copystructure.Copy(old.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err)
|
||||
}
|
||||
path, ok := pathRaw.([]string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: path is not a list of strings")
|
||||
}
|
||||
if len(path) == 0 {
|
||||
// We found some V1 states with a nil path. Assume root and catch
|
||||
// duplicate path errors later (as part of Validate).
|
||||
path = rootModulePath
|
||||
}
|
||||
|
||||
// Outputs needs upgrading to use the new structure
|
||||
outputs := make(map[string]*OutputState)
|
||||
for key, output := range old.Outputs {
|
||||
outputs[key] = &OutputState{
|
||||
Type: "string",
|
||||
Value: output,
|
||||
Sensitive: false,
|
||||
}
|
||||
}
|
||||
|
||||
resources := make(map[string]*ResourceState)
|
||||
for key, oldResource := range old.Resources {
|
||||
upgraded, err := oldResource.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err)
|
||||
}
|
||||
resources[key] = upgraded
|
||||
}
|
||||
|
||||
dependencies, err := copystructure.Copy(old.Dependencies)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err)
|
||||
}
|
||||
|
||||
return &ModuleState{
|
||||
Path: path,
|
||||
Outputs: outputs,
|
||||
Resources: resources,
|
||||
Dependencies: dependencies.([]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (old *resourceStateV1) upgradeToV2() (*ResourceState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
dependencies, err := copystructure.Copy(old.Dependencies)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err)
|
||||
}
|
||||
|
||||
primary, err := old.Primary.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err)
|
||||
}
|
||||
|
||||
deposed := make([]*InstanceState, len(old.Deposed))
|
||||
for i, v := range old.Deposed {
|
||||
upgraded, err := v.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err)
|
||||
}
|
||||
deposed[i] = upgraded
|
||||
}
|
||||
if len(deposed) == 0 {
|
||||
deposed = nil
|
||||
}
|
||||
|
||||
return &ResourceState{
|
||||
Type: old.Type,
|
||||
Dependencies: dependencies.([]string),
|
||||
Primary: primary,
|
||||
Deposed: deposed,
|
||||
Provider: old.Provider,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (old *instanceStateV1) upgradeToV2() (*InstanceState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
attributes, err := copystructure.Copy(old.Attributes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
|
||||
}
|
||||
ephemeral, err := old.Ephemeral.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
|
||||
}
|
||||
|
||||
meta, err := copystructure.Copy(old.Meta)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
|
||||
}
|
||||
|
||||
newMeta := make(map[string]interface{})
|
||||
for k, v := range meta.(map[string]string) {
|
||||
newMeta[k] = v
|
||||
}
|
||||
|
||||
return &InstanceState{
|
||||
ID: old.ID,
|
||||
Attributes: attributes.(map[string]string),
|
||||
Ephemeral: *ephemeral,
|
||||
Meta: newMeta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (old *ephemeralStateV1) upgradeToV2() (*EphemeralState, error) {
|
||||
connInfo, err := copystructure.Copy(old.ConnInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading EphemeralState V1: %v", err)
|
||||
}
|
||||
return &EphemeralState{
|
||||
ConnInfo: connInfo.(map[string]string),
|
||||
}, nil
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The upgrade process from V2 to V3 state does not affect the structure,
|
||||
// so we do not need to redeclare all of the structs involved - we just
|
||||
// take a deep copy of the old structure and assert the version number is
|
||||
// as we expect.
|
||||
func upgradeStateV2ToV3(old *State) (*State, error) {
|
||||
new := old.DeepCopy()
|
||||
|
||||
// Ensure the copied version is v2 before attempting to upgrade
|
||||
if new.Version != 2 {
|
||||
return nil, fmt.Errorf("Cannot apply v2->v3 state upgrade to " +
|
||||
"a state which is not version 2.")
|
||||
}
|
||||
|
||||
// Set the new version number
|
||||
new.Version = 3
|
||||
|
||||
// Change the counts for things which look like maps to use the %
|
||||
// syntax. Remove counts for empty collections - they will be added
|
||||
// back in later.
|
||||
for _, module := range new.Modules {
|
||||
for _, resource := range module.Resources {
|
||||
// Upgrade Primary
|
||||
if resource.Primary != nil {
|
||||
upgradeAttributesV2ToV3(resource.Primary)
|
||||
}
|
||||
|
||||
// Upgrade Deposed
|
||||
if resource.Deposed != nil {
|
||||
for _, deposed := range resource.Deposed {
|
||||
upgradeAttributesV2ToV3(deposed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new, nil
|
||||
}
|
||||
|
||||
func upgradeAttributesV2ToV3(instanceState *InstanceState) error {
|
||||
collectionKeyRegexp := regexp.MustCompile(`^(.*\.)#$`)
|
||||
collectionSubkeyRegexp := regexp.MustCompile(`^([^\.]+)\..*`)
|
||||
|
||||
// Identify the key prefix of anything which is a collection
|
||||
var collectionKeyPrefixes []string
|
||||
for key := range instanceState.Attributes {
|
||||
if submatches := collectionKeyRegexp.FindAllStringSubmatch(key, -1); len(submatches) > 0 {
|
||||
collectionKeyPrefixes = append(collectionKeyPrefixes, submatches[0][1])
|
||||
}
|
||||
}
|
||||
sort.Strings(collectionKeyPrefixes)
|
||||
|
||||
log.Printf("[STATE UPGRADE] Detected the following collections in state: %v", collectionKeyPrefixes)
|
||||
|
||||
// This could be rolled into fewer loops, but it is somewhat clearer this way, and will not
|
||||
// run very often.
|
||||
for _, prefix := range collectionKeyPrefixes {
|
||||
// First get the actual keys that belong to this prefix
|
||||
var potentialKeysMatching []string
|
||||
for key := range instanceState.Attributes {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
potentialKeysMatching = append(potentialKeysMatching, strings.TrimPrefix(key, prefix))
|
||||
}
|
||||
}
|
||||
sort.Strings(potentialKeysMatching)
|
||||
|
||||
var actualKeysMatching []string
|
||||
for _, key := range potentialKeysMatching {
|
||||
if submatches := collectionSubkeyRegexp.FindAllStringSubmatch(key, -1); len(submatches) > 0 {
|
||||
actualKeysMatching = append(actualKeysMatching, submatches[0][1])
|
||||
} else {
|
||||
if key != "#" {
|
||||
actualKeysMatching = append(actualKeysMatching, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
actualKeysMatching = uniqueSortedStrings(actualKeysMatching)
|
||||
|
||||
// Now inspect the keys in order to determine whether this is most likely to be
|
||||
// a map, list or set. There is room for error here, so we log in each case. If
|
||||
// there is no method of telling, we remove the key from the InstanceState in
|
||||
// order that it will be recreated. Again, this could be rolled into fewer loops
|
||||
// but we prefer clarity.
|
||||
|
||||
oldCountKey := fmt.Sprintf("%s#", prefix)
|
||||
|
||||
// First, detect "obvious" maps - which have non-numeric keys (mostly).
|
||||
hasNonNumericKeys := false
|
||||
for _, key := range actualKeysMatching {
|
||||
if _, err := strconv.Atoi(key); err != nil {
|
||||
hasNonNumericKeys = true
|
||||
}
|
||||
}
|
||||
if hasNonNumericKeys {
|
||||
newCountKey := fmt.Sprintf("%s%%", prefix)
|
||||
|
||||
instanceState.Attributes[newCountKey] = instanceState.Attributes[oldCountKey]
|
||||
delete(instanceState.Attributes, oldCountKey)
|
||||
log.Printf("[STATE UPGRADE] Detected %s as a map. Replaced count = %s",
|
||||
strings.TrimSuffix(prefix, "."), instanceState.Attributes[newCountKey])
|
||||
}
|
||||
|
||||
// Now detect empty collections and remove them from state.
|
||||
if len(actualKeysMatching) == 0 {
|
||||
delete(instanceState.Attributes, oldCountKey)
|
||||
log.Printf("[STATE UPGRADE] Detected %s as an empty collection. Removed from state.",
|
||||
strings.TrimSuffix(prefix, "."))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// uniqueSortedStrings removes duplicates from a slice of strings and returns
|
||||
// a sorted slice of the unique strings.
|
||||
func uniqueSortedStrings(input []string) []string {
|
||||
uniquemap := make(map[string]struct{})
|
||||
for _, str := range input {
|
||||
uniquemap[str] = struct{}{}
|
||||
}
|
||||
|
||||
output := make([]string, len(uniquemap))
|
||||
|
||||
i := 0
|
||||
for key := range uniquemap {
|
||||
output[i] = key
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
sort.Strings(output)
|
||||
return output
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
package terraform
|
||||
|
||||
// stateV1 keeps track of a snapshot state-of-the-world that Terraform
|
||||
// can use to keep track of what real world resources it is actually
|
||||
// managing.
|
||||
//
|
||||
// stateV1 is _only used for the purposes of backwards compatibility
|
||||
// and is no longer used in Terraform.
|
||||
//
|
||||
// For the upgrade process, see state_upgrade_v1_to_v2.go
|
||||
type stateV1 struct {
|
||||
// Version is the protocol version. "1" for a StateV1.
|
||||
Version int `json:"version"`
|
||||
|
||||
// Serial is incremented on any operation that modifies
|
||||
// the State file. It is used to detect potentially conflicting
|
||||
// updates.
|
||||
Serial int64 `json:"serial"`
|
||||
|
||||
// Remote is used to track the metadata required to
|
||||
// pull and push state files from a remote storage endpoint.
|
||||
Remote *remoteStateV1 `json:"remote,omitempty"`
|
||||
|
||||
// Modules contains all the modules in a breadth-first order
|
||||
Modules []*moduleStateV1 `json:"modules"`
|
||||
}
|
||||
|
||||
type remoteStateV1 struct {
|
||||
// Type controls the client we use for the remote state
|
||||
Type string `json:"type"`
|
||||
|
||||
// Config is used to store arbitrary configuration that
|
||||
// is type specific
|
||||
Config map[string]string `json:"config"`
|
||||
}
|
||||
|
||||
type moduleStateV1 struct {
|
||||
// Path is the import path from the root module. Modules imports are
|
||||
// always disjoint, so the path represents amodule tree
|
||||
Path []string `json:"path"`
|
||||
|
||||
// Outputs declared by the module and maintained for each module
|
||||
// even though only the root module technically needs to be kept.
|
||||
// This allows operators to inspect values at the boundaries.
|
||||
Outputs map[string]string `json:"outputs"`
|
||||
|
||||
// Resources is a mapping of the logically named resource to
|
||||
// the state of the resource. Each resource may actually have
|
||||
// N instances underneath, although a user only needs to think
|
||||
// about the 1:1 case.
|
||||
Resources map[string]*resourceStateV1 `json:"resources"`
|
||||
|
||||
// Dependencies are a list of things that this module relies on
|
||||
// existing to remain intact. For example: an module may depend
|
||||
// on a VPC ID given by an aws_vpc resource.
|
||||
//
|
||||
// Terraform uses this information to build valid destruction
|
||||
// orders and to warn the user if they're destroying a module that
|
||||
// another resource depends on.
|
||||
//
|
||||
// Things can be put into this list that may not be managed by
|
||||
// Terraform. If Terraform doesn't find a matching ID in the
|
||||
// overall state, then it assumes it isn't managed and doesn't
|
||||
// worry about it.
|
||||
Dependencies []string `json:"depends_on,omitempty"`
|
||||
}
|
||||
|
||||
type resourceStateV1 struct {
|
||||
// This is filled in and managed by Terraform, and is the resource
|
||||
// type itself such as "mycloud_instance". If a resource provider sets
|
||||
// this value, it won't be persisted.
|
||||
Type string `json:"type"`
|
||||
|
||||
// Dependencies are a list of things that this resource relies on
|
||||
// existing to remain intact. For example: an AWS instance might
|
||||
// depend on a subnet (which itself might depend on a VPC, and so
|
||||
// on).
|
||||
//
|
||||
// Terraform uses this information to build valid destruction
|
||||
// orders and to warn the user if they're destroying a resource that
|
||||
// another resource depends on.
|
||||
//
|
||||
// Things can be put into this list that may not be managed by
|
||||
// Terraform. If Terraform doesn't find a matching ID in the
|
||||
// overall state, then it assumes it isn't managed and doesn't
|
||||
// worry about it.
|
||||
Dependencies []string `json:"depends_on,omitempty"`
|
||||
|
||||
// Primary is the current active instance for this resource.
|
||||
// It can be replaced but only after a successful creation.
|
||||
// This is the instances on which providers will act.
|
||||
Primary *instanceStateV1 `json:"primary"`
|
||||
|
||||
// Tainted is used to track any underlying instances that
|
||||
// have been created but are in a bad or unknown state and
|
||||
// need to be cleaned up subsequently. In the
|
||||
// standard case, there is only at most a single instance.
|
||||
// However, in pathological cases, it is possible for the number
|
||||
// of instances to accumulate.
|
||||
Tainted []*instanceStateV1 `json:"tainted,omitempty"`
|
||||
|
||||
// Deposed is used in the mechanics of CreateBeforeDestroy: the existing
|
||||
// Primary is Deposed to get it out of the way for the replacement Primary to
|
||||
// be created by Apply. If the replacement Primary creates successfully, the
|
||||
// Deposed instance is cleaned up. If there were problems creating the
|
||||
// replacement, the instance remains in the Deposed list so it can be
|
||||
// destroyed in a future run. Functionally, Deposed instances are very
|
||||
// similar to Tainted instances in that Terraform is only tracking them in
|
||||
// order to remember to destroy them.
|
||||
Deposed []*instanceStateV1 `json:"deposed,omitempty"`
|
||||
|
||||
// Provider is used when a resource is connected to a provider with an alias.
|
||||
// If this string is empty, the resource is connected to the default provider,
|
||||
// e.g. "aws_instance" goes with the "aws" provider.
|
||||
// If the resource block contained a "provider" key, that value will be set here.
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type instanceStateV1 struct {
|
||||
// A unique ID for this resource. This is opaque to Terraform
|
||||
// and is only meant as a lookup mechanism for the providers.
|
||||
ID string `json:"id"`
|
||||
|
||||
// Attributes are basic information about the resource. Any keys here
|
||||
// are accessible in variable format within Terraform configurations:
|
||||
// ${resourcetype.name.attribute}.
|
||||
Attributes map[string]string `json:"attributes,omitempty"`
|
||||
|
||||
// Ephemeral is used to store any state associated with this instance
|
||||
// that is necessary for the Terraform run to complete, but is not
|
||||
// persisted to a state file.
|
||||
Ephemeral ephemeralStateV1 `json:"-"`
|
||||
|
||||
// Meta is a simple K/V map that is persisted to the State but otherwise
|
||||
// ignored by Terraform core. It's meant to be used for accounting by
|
||||
// external client code.
|
||||
Meta map[string]string `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
type ephemeralStateV1 struct {
|
||||
// ConnInfo is used for the providers to export information which is
|
||||
// used to connect to the resource for provisioning. For example,
|
||||
// this could contain SSH or WinRM credentials.
|
||||
ConnInfo map[string]string `json:"-"`
|
||||
}
|
|
@ -228,7 +228,7 @@ func testProviderFuncFixed(rp providers.Interface) providers.Factory {
|
|||
}
|
||||
}
|
||||
|
||||
func testProvisionerFuncFixed(rp provisioners.Interface) ProvisionerFactory {
|
||||
func testProvisionerFuncFixed(rp provisioners.Interface) provisioners.Factory {
|
||||
return func() (provisioners.Interface, error) {
|
||||
return rp, nil
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestStateFile writes the given state to the path.
|
||||
func TestStateFile(t *testing.T, path string, state *State) {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := WriteState(state, f); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
|
@ -14,12 +14,17 @@ import (
|
|||
func TestGraphNodeImportStateExecute(t *testing.T) {
|
||||
state := states.NewState()
|
||||
provider := testProvider("aws")
|
||||
provider.ImportStateReturn = []*InstanceState{
|
||||
&InstanceState{
|
||||
ID: "bar",
|
||||
Ephemeral: EphemeralState{Type: "aws_instance"},
|
||||
provider.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "aws_instance",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("bar"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := &MockEvalContext{
|
||||
StateState: state.SyncWrapper(),
|
||||
ProviderProvider: provider,
|
||||
|
|
|
@ -1,60 +1,51 @@
|
|||
package terraform
|
||||
|
||||
// FIXME: Update these tests for the new OrphanResourceCountTransformer
|
||||
// interface that expects to be given a list of instance addresses that
|
||||
// exist in config.
|
||||
|
||||
/*
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestOrphanResourceCountTransformer(t *testing.T) {
|
||||
state := MustShimLegacyState(&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.2": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
state := states.NewState()
|
||||
root := state.RootModule()
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.web").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"foo"}`),
|
||||
},
|
||||
})
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"foo"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.foo[2]").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"foo"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
|
||||
g := Graph{Path: addrs.RootModuleInstance}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
tf := &OrphanResourceInstanceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 1,
|
||||
Addr: addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo",
|
||||
),
|
||||
State: state,
|
||||
InstanceAddrs: []addrs.AbsResourceInstance{mustResourceInstanceAddr("aws_instance.foo[0]")},
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -69,46 +60,43 @@ func TestOrphanResourceCountTransformer(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOrphanResourceCountTransformer_zero(t *testing.T) {
|
||||
state := MustShimLegacyState(&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.2": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
state := states.NewState()
|
||||
root := state.RootModule()
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.web").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"foo"}`),
|
||||
},
|
||||
})
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"foo"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.foo[2]").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"foo"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
|
||||
g := Graph{Path: addrs.RootModuleInstance}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
tf := &OrphanResourceInstanceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 0,
|
||||
Addr: addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo",
|
||||
),
|
||||
State: state,
|
||||
InstanceAddrs: []addrs.AbsResourceInstance{},
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -122,101 +110,44 @@ func TestOrphanResourceCountTransformer_zero(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceCountTransformer_oneNoIndex(t *testing.T) {
|
||||
state := MustShimLegacyState(&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.2": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
g := Graph{Path: addrs.RootModuleInstance}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 1,
|
||||
Addr: addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo",
|
||||
),
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountOneNoIndexStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceCountTransformer_oneIndex(t *testing.T) {
|
||||
state := MustShimLegacyState(&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.1": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
state := states.NewState()
|
||||
root := state.RootModule()
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.web").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"foo"}`),
|
||||
},
|
||||
})
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.foo[0]").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"foo"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("aws_instance.foo[1]").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"foo"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
|
||||
g := Graph{Path: addrs.RootModuleInstance}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
tf := &OrphanResourceInstanceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 1,
|
||||
Addr: addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo",
|
||||
),
|
||||
State: state,
|
||||
InstanceAddrs: []addrs.AbsResourceInstance{mustResourceInstanceAddr("aws_instance.foo[0]")},
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -230,114 +161,6 @@ func TestOrphanResourceCountTransformer_oneIndex(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceCountTransformer_zeroAndNone(t *testing.T) {
|
||||
state := MustShimLegacyState(&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
g := Graph{Path: addrs.RootModuleInstance}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: -1,
|
||||
Addr: addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo",
|
||||
),
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountZeroAndNoneStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanResourceCountTransformer_zeroAndNoneCount(t *testing.T) {
|
||||
state := MustShimLegacyState(&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
"aws_instance.foo.0": &ResourceState{
|
||||
Type: "aws_instance",
|
||||
Primary: &InstanceState{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
g := Graph{Path: addrs.RootModuleInstance}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
Count: 2,
|
||||
Addr: addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo",
|
||||
),
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformOrphanResourceCountZeroAndNoneCountStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
// When converting from a NoEach mode to an EachMap via a switch to for_each,
|
||||
// an edge is necessary to ensure that the map-key'd instances
|
||||
// are evaluated after the NoKey resource, because the final instance evaluated
|
||||
|
@ -357,10 +180,7 @@ func TestOrphanResourceCountTransformer_ForEachEdgesAdded(t *testing.T) {
|
|||
},
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("aws"),
|
||||
Module: addrs.RootModuleInstance,
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
|
||||
// NoKey'd resource
|
||||
|
@ -376,25 +196,20 @@ func TestOrphanResourceCountTransformer_ForEachEdgesAdded(t *testing.T) {
|
|||
},
|
||||
Status: states.ObjectReady,
|
||||
},
|
||||
addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("aws"),
|
||||
Module: addrs.RootModuleInstance,
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
|
||||
)
|
||||
})
|
||||
|
||||
g := Graph{Path: addrs.RootModuleInstance}
|
||||
|
||||
{
|
||||
tf := &OrphanResourceCountTransformer{
|
||||
tf := &OrphanResourceInstanceCountTransformer{
|
||||
Concrete: testOrphanResourceConcreteFunc,
|
||||
// No keys in this ForEach ensure both our resources end
|
||||
// up orphaned in this test
|
||||
ForEach: map[string]cty.Value{},
|
||||
Addr: addrs.RootModuleInstance.Resource(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo",
|
||||
),
|
||||
State: state,
|
||||
InstanceAddrs: []addrs.AbsResourceInstance{},
|
||||
State: state,
|
||||
}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
|
@ -413,11 +228,7 @@ aws_instance.foo[2] (orphan)
|
|||
`
|
||||
|
||||
const testTransformOrphanResourceCountZeroStr = `
|
||||
aws_instance.foo (orphan)
|
||||
aws_instance.foo[2] (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountOneNoIndexStr = `
|
||||
aws_instance.foo[0] (orphan)
|
||||
aws_instance.foo[2] (orphan)
|
||||
`
|
||||
|
||||
|
@ -425,17 +236,7 @@ const testTransformOrphanResourceCountOneIndexStr = `
|
|||
aws_instance.foo[1] (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountZeroAndNoneStr = `
|
||||
aws_instance.foo[0] (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceCountZeroAndNoneCountStr = `
|
||||
aws_instance.foo (orphan)
|
||||
`
|
||||
|
||||
const testTransformOrphanResourceForEachStr = `
|
||||
aws_instance.foo (orphan)
|
||||
aws_instance.foo["bar"] (orphan)
|
||||
aws_instance.foo (orphan)
|
||||
`
|
||||
*/
|
||||
|
|
|
@ -68,12 +68,12 @@ func TestReferenceTransformer_path(t *testing.T) {
|
|||
})
|
||||
g.Add(&graphNodeRefParentTest{
|
||||
NameValue: "child.A",
|
||||
PathValue: []string{"root", "child"},
|
||||
PathValue: addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "child"}},
|
||||
Names: []string{"A"},
|
||||
})
|
||||
g.Add(&graphNodeRefChildTest{
|
||||
NameValue: "child.B",
|
||||
PathValue: []string{"root", "child"},
|
||||
PathValue: addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "child"}},
|
||||
Refs: []string{"A"},
|
||||
})
|
||||
|
||||
|
@ -214,7 +214,7 @@ func TestReferenceMapReferences(t *testing.T) {
|
|||
|
||||
type graphNodeRefParentTest struct {
|
||||
NameValue string
|
||||
PathValue []string
|
||||
PathValue addrs.ModuleInstance
|
||||
Names []string
|
||||
}
|
||||
|
||||
|
@ -233,16 +233,16 @@ func (n *graphNodeRefParentTest) ReferenceableAddrs() []addrs.Referenceable {
|
|||
}
|
||||
|
||||
func (n *graphNodeRefParentTest) Path() addrs.ModuleInstance {
|
||||
return normalizeModulePath(n.PathValue)
|
||||
return n.PathValue
|
||||
}
|
||||
|
||||
func (n *graphNodeRefParentTest) ModulePath() addrs.Module {
|
||||
return normalizeModulePath(n.PathValue).Module()
|
||||
return n.PathValue.Module()
|
||||
}
|
||||
|
||||
type graphNodeRefChildTest struct {
|
||||
NameValue string
|
||||
PathValue []string
|
||||
PathValue addrs.ModuleInstance
|
||||
Refs []string
|
||||
}
|
||||
|
||||
|
@ -263,11 +263,11 @@ func (n *graphNodeRefChildTest) References() []*addrs.Reference {
|
|||
}
|
||||
|
||||
func (n *graphNodeRefChildTest) Path() addrs.ModuleInstance {
|
||||
return normalizeModulePath(n.PathValue)
|
||||
return n.PathValue
|
||||
}
|
||||
|
||||
func (n *graphNodeRefChildTest) ModulePath() addrs.Module {
|
||||
return normalizeModulePath(n.PathValue).Module()
|
||||
return n.PathValue.Module()
|
||||
}
|
||||
|
||||
type graphNodeFakeResourceInstance struct {
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// TestReadUpgradeStateV1toV3 tests the state upgrade process from the V1 state
|
||||
// to the current version, and needs editing each time. This means it tests the
|
||||
// entire pipeline of upgrades (which migrate version to version).
|
||||
func TestReadUpgradeStateV1toV3(t *testing.T) {
|
||||
// ReadState should transparently detect the old version but will upgrade
|
||||
// it on Write.
|
||||
actual, err := ReadState(strings.NewReader(testV1State))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := WriteState(actual, buf); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if actual.Version != 3 {
|
||||
t.Fatalf("bad: State version not incremented; is %d", actual.Version)
|
||||
}
|
||||
|
||||
roundTripped, err := ReadState(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, roundTripped) {
|
||||
t.Logf("actual:\n%#v", actual)
|
||||
t.Fatalf("roundTripped:\n%#v", roundTripped)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadUpgradeStateV1toV3_outputs(t *testing.T) {
|
||||
// ReadState should transparently detect the old version but will upgrade
|
||||
// it on Write.
|
||||
actual, err := ReadState(strings.NewReader(testV1StateWithOutputs))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := WriteState(actual, buf); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if actual.Version != 3 {
|
||||
t.Fatalf("bad: State version not incremented; is %d", actual.Version)
|
||||
}
|
||||
|
||||
roundTripped, err := ReadState(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, roundTripped) {
|
||||
spew.Config.DisableMethods = true
|
||||
t.Fatalf("bad:\n%s\n\nround tripped:\n%s\n", spew.Sdump(actual), spew.Sdump(roundTripped))
|
||||
spew.Config.DisableMethods = false
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrading the state should not lose empty module Outputs and Resources maps
|
||||
// during upgrade. The init for a new module initializes new maps, so we may not
|
||||
// be expecting to check for a nil map.
|
||||
func TestReadUpgradeStateV1toV3_emptyState(t *testing.T) {
|
||||
// ReadState should transparently detect the old version but will upgrade
|
||||
// it on Write.
|
||||
orig, err := ReadStateV1([]byte(testV1EmptyState))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
stateV2, err := upgradeStateV1ToV2(orig)
|
||||
if err != nil {
|
||||
t.Fatalf("error attempting upgradeStateV1ToV2: %s", err)
|
||||
}
|
||||
|
||||
for _, m := range stateV2.Modules {
|
||||
if m.Resources == nil {
|
||||
t.Fatal("V1 to V2 upgrade lost module.Resources")
|
||||
}
|
||||
if m.Outputs == nil {
|
||||
t.Fatal("V1 to V2 upgrade lost module.Outputs")
|
||||
}
|
||||
}
|
||||
|
||||
stateV3, err := upgradeStateV2ToV3(stateV2)
|
||||
if err != nil {
|
||||
t.Fatalf("error attempting to upgradeStateV2ToV3: %s", err)
|
||||
}
|
||||
for _, m := range stateV3.Modules {
|
||||
if m.Resources == nil {
|
||||
t.Fatal("V2 to V3 upgrade lost module.Resources")
|
||||
}
|
||||
if m.Outputs == nil {
|
||||
t.Fatal("V2 to V3 upgrade lost module.Outputs")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const testV1EmptyState = `{
|
||||
"version": 1,
|
||||
"serial": 0,
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": {},
|
||||
"resources": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
const testV1State = `{
|
||||
"version": 1,
|
||||
"serial": 9,
|
||||
"remote": {
|
||||
"type": "http",
|
||||
"config": {
|
||||
"url": "http://my-cool-server.com/"
|
||||
}
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": null,
|
||||
"resources": {
|
||||
"foo": {
|
||||
"type": "",
|
||||
"primary": {
|
||||
"id": "bar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"depends_on": [
|
||||
"aws_instance.bar"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
const testV1StateWithOutputs = `{
|
||||
"version": 1,
|
||||
"serial": 9,
|
||||
"remote": {
|
||||
"type": "http",
|
||||
"config": {
|
||||
"url": "http://my-cool-server.com/"
|
||||
}
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": {
|
||||
"foo": "bar",
|
||||
"baz": "foo"
|
||||
},
|
||||
"resources": {
|
||||
"foo": {
|
||||
"type": "",
|
||||
"primary": {
|
||||
"id": "bar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"depends_on": [
|
||||
"aws_instance.bar"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
|
@ -1,202 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestReadUpgradeStateV2toV3 tests the state upgrade process from the V2 state
|
||||
// to the current version, and needs editing each time. This means it tests the
|
||||
// entire pipeline of upgrades (which migrate version to version).
|
||||
func TestReadUpgradeStateV2toV3(t *testing.T) {
|
||||
// ReadState should transparently detect the old version but will upgrade
|
||||
// it on Write.
|
||||
upgraded, err := ReadState(strings.NewReader(testV2State))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := WriteState(upgraded, buf); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if upgraded.Version != 3 {
|
||||
t.Fatalf("bad: State version not incremented; is %d", upgraded.Version)
|
||||
}
|
||||
|
||||
// For this test we cannot assert that we match the round trip because an
|
||||
// empty map has been removed from state. Instead we make assertions against
|
||||
// some of the key fields in the _upgraded_ state.
|
||||
instanceState, ok := upgraded.RootModule().Resources["test_resource.main"]
|
||||
if !ok {
|
||||
t.Fatalf("Instance state for test_resource.main was removed from state during upgrade")
|
||||
}
|
||||
|
||||
primary := instanceState.Primary
|
||||
if primary == nil {
|
||||
t.Fatalf("Primary instance was removed from state for test_resource.main")
|
||||
}
|
||||
|
||||
// Non-empty computed map is moved from .# to .%
|
||||
if _, ok := primary.Attributes["computed_map.#"]; ok {
|
||||
t.Fatalf("Count was not upgraded from .# to .%% for computed_map")
|
||||
}
|
||||
if count, ok := primary.Attributes["computed_map.%"]; !ok || count != "1" {
|
||||
t.Fatalf("Count was not in .%% or was not 2 for computed_map")
|
||||
}
|
||||
|
||||
// list_of_map top level retains .#
|
||||
if count, ok := primary.Attributes["list_of_map.#"]; !ok || count != "2" {
|
||||
t.Fatal("Count for list_of_map was migrated incorrectly")
|
||||
}
|
||||
|
||||
// list_of_map.0 is moved from .# to .%
|
||||
if _, ok := primary.Attributes["list_of_map.0.#"]; ok {
|
||||
t.Fatalf("Count was not upgraded from .# to .%% for list_of_map.0")
|
||||
}
|
||||
if count, ok := primary.Attributes["list_of_map.0.%"]; !ok || count != "2" {
|
||||
t.Fatalf("Count was not in .%% or was not 2 for list_of_map.0")
|
||||
}
|
||||
|
||||
// list_of_map.1 is moved from .# to .%
|
||||
if _, ok := primary.Attributes["list_of_map.1.#"]; ok {
|
||||
t.Fatalf("Count was not upgraded from .# to .%% for list_of_map.1")
|
||||
}
|
||||
if count, ok := primary.Attributes["list_of_map.1.%"]; !ok || count != "2" {
|
||||
t.Fatalf("Count was not in .%% or was not 2 for list_of_map.1")
|
||||
}
|
||||
|
||||
// map is moved from .# to .%
|
||||
if _, ok := primary.Attributes["map.#"]; ok {
|
||||
t.Fatalf("Count was not upgraded from .# to .%% for map")
|
||||
}
|
||||
if count, ok := primary.Attributes["map.%"]; !ok || count != "2" {
|
||||
t.Fatalf("Count was not in .%% or was not 2 for map")
|
||||
}
|
||||
|
||||
// optional_computed_map should be removed from state
|
||||
if _, ok := primary.Attributes["optional_computed_map"]; ok {
|
||||
t.Fatal("optional_computed_map was not removed from state")
|
||||
}
|
||||
|
||||
// required_map is moved from .# to .%
|
||||
if _, ok := primary.Attributes["required_map.#"]; ok {
|
||||
t.Fatalf("Count was not upgraded from .# to .%% for required_map")
|
||||
}
|
||||
if count, ok := primary.Attributes["required_map.%"]; !ok || count != "3" {
|
||||
t.Fatalf("Count was not in .%% or was not 3 for map")
|
||||
}
|
||||
|
||||
// computed_list keeps .#
|
||||
if count, ok := primary.Attributes["computed_list.#"]; !ok || count != "2" {
|
||||
t.Fatal("Count was migrated incorrectly for computed_list")
|
||||
}
|
||||
|
||||
// computed_set keeps .#
|
||||
if count, ok := primary.Attributes["computed_set.#"]; !ok || count != "2" {
|
||||
t.Fatal("Count was migrated incorrectly for computed_set")
|
||||
}
|
||||
if val, ok := primary.Attributes["computed_set.2337322984"]; !ok || val != "setval1" {
|
||||
t.Fatal("Set item for computed_set.2337322984 changed or moved")
|
||||
}
|
||||
if val, ok := primary.Attributes["computed_set.307881554"]; !ok || val != "setval2" {
|
||||
t.Fatal("Set item for computed_set.307881554 changed or moved")
|
||||
}
|
||||
|
||||
// string properties are unaffected
|
||||
if val, ok := primary.Attributes["id"]; !ok || val != "testId" {
|
||||
t.Fatal("id was not set correctly after migration")
|
||||
}
|
||||
}
|
||||
|
||||
const testV2State = `{
|
||||
"version": 2,
|
||||
"terraform_version": "0.7.0",
|
||||
"serial": 2,
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": {
|
||||
"computed_map": {
|
||||
"sensitive": false,
|
||||
"type": "map",
|
||||
"value": {
|
||||
"key1": "value1"
|
||||
}
|
||||
},
|
||||
"computed_set": {
|
||||
"sensitive": false,
|
||||
"type": "list",
|
||||
"value": [
|
||||
"setval1",
|
||||
"setval2"
|
||||
]
|
||||
},
|
||||
"map": {
|
||||
"sensitive": false,
|
||||
"type": "map",
|
||||
"value": {
|
||||
"key": "test",
|
||||
"test": "test"
|
||||
}
|
||||
},
|
||||
"set": {
|
||||
"sensitive": false,
|
||||
"type": "list",
|
||||
"value": [
|
||||
"test1",
|
||||
"test2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"test_resource.main": {
|
||||
"type": "test_resource",
|
||||
"primary": {
|
||||
"id": "testId",
|
||||
"attributes": {
|
||||
"computed_list.#": "2",
|
||||
"computed_list.0": "listval1",
|
||||
"computed_list.1": "listval2",
|
||||
"computed_map.#": "1",
|
||||
"computed_map.key1": "value1",
|
||||
"computed_read_only": "value_from_api",
|
||||
"computed_read_only_force_new": "value_from_api",
|
||||
"computed_set.#": "2",
|
||||
"computed_set.2337322984": "setval1",
|
||||
"computed_set.307881554": "setval2",
|
||||
"id": "testId",
|
||||
"list_of_map.#": "2",
|
||||
"list_of_map.0.#": "2",
|
||||
"list_of_map.0.key1": "value1",
|
||||
"list_of_map.0.key2": "value2",
|
||||
"list_of_map.1.#": "2",
|
||||
"list_of_map.1.key3": "value3",
|
||||
"list_of_map.1.key4": "value4",
|
||||
"map.#": "2",
|
||||
"map.key": "test",
|
||||
"map.test": "test",
|
||||
"map_that_look_like_set.#": "2",
|
||||
"map_that_look_like_set.12352223": "hello",
|
||||
"map_that_look_like_set.36234341": "world",
|
||||
"optional_computed_map.#": "0",
|
||||
"required": "Hello World",
|
||||
"required_map.#": "3",
|
||||
"required_map.key1": "value1",
|
||||
"required_map.key2": "value2",
|
||||
"required_map.key3": "value3",
|
||||
"set.#": "2",
|
||||
"set.2326977762": "test1",
|
||||
"set.331058520": "test2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
Loading…
Reference in New Issue