Merge pull request #27076 from hashicorp/jbardin/legacy-cleanup

Legacy types cleanup
This commit is contained in:
James Bardin 2020-12-02 13:22:41 -05:00 committed by GitHub
commit f2c5aa6d28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 525 additions and 12771 deletions

View File

@ -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 {

View File

@ -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.

View File

@ -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{

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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{

View File

@ -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 {

View File

@ -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
},

View File

@ -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

View File

@ -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{
{

View File

@ -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),
},
})

View File

@ -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()) {

View File

@ -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,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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
)

View File

@ -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]]
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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.

View File

@ -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
}

View File

@ -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

View File

@ -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
)

View File

@ -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]]
}

View File

@ -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".

View File

@ -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
}

View File

@ -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".

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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()

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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:"-"`
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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)
`
*/

View File

@ -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 {

View File

@ -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"
]
}
]
}
`

View File

@ -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"
}
}
}
}
}
]
}
`