remove legacy state types
This commit is contained in:
parent
943ef51d67
commit
14dedf295b
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -103,18 +103,6 @@ func (d *Diff) AddModule(path addrs.ModuleInstance) *ModuleDiff {
|
|||
// This should be the preferred lookup mechanism as it allows for future
|
||||
// lookup optimizations.
|
||||
func (d *Diff) ModuleByPath(path addrs.ModuleInstance) *ModuleDiff {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
for _, mod := range d.Modules {
|
||||
if mod.Path == nil {
|
||||
panic("missing module path")
|
||||
}
|
||||
modPath := normalizeModulePath(mod.Path)
|
||||
if modPath.String() == path.String() {
|
||||
return mod
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -190,7 +178,7 @@ func (d *Diff) String() string {
|
|||
keys := make([]string, 0, len(d.Modules))
|
||||
lookup := make(map[string]*ModuleDiff)
|
||||
for _, m := range d.Modules {
|
||||
addr := normalizeModulePath(m.Path)
|
||||
addr := addrs.ModuleInstance{} //normalizeModulePath(m.Path)
|
||||
key := addr.String()
|
||||
keys = append(keys, key)
|
||||
lookup[key] = m
|
||||
|
@ -201,11 +189,11 @@ func (d *Diff) String() string {
|
|||
m := lookup[key]
|
||||
mStr := m.String()
|
||||
|
||||
// If we're the root module, we just write the output directly.
|
||||
if reflect.DeepEqual(m.Path, rootModulePath) {
|
||||
buf.WriteString(mStr + "\n")
|
||||
continue
|
||||
}
|
||||
//// If we're the root module, we just write the output directly.
|
||||
//if reflect.DeepEqual(m.Path, rootModulePath) {
|
||||
// buf.WriteString(mStr + "\n")
|
||||
// continue
|
||||
//}
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%s:\n", key))
|
||||
|
||||
|
@ -219,13 +207,6 @@ func (d *Diff) String() string {
|
|||
}
|
||||
|
||||
func (d *Diff) init() {
|
||||
if d.Modules == nil {
|
||||
rootDiff := &ModuleDiff{Path: rootModulePath}
|
||||
d.Modules = []*ModuleDiff{rootDiff}
|
||||
}
|
||||
for _, m := range d.Modules {
|
||||
m.init()
|
||||
}
|
||||
}
|
||||
|
||||
// ModuleDiff tracks the differences between resources to apply within
|
||||
|
@ -304,7 +285,7 @@ func (d *ModuleDiff) Instances(id string) []*InstanceDiff {
|
|||
|
||||
// IsRoot says whether or not this module diff is for the root module.
|
||||
func (d *ModuleDiff) IsRoot() bool {
|
||||
return reflect.DeepEqual(d.Path, rootModulePath)
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// String outputs the diff in a long but command-line friendly output
|
||||
|
|
|
@ -26,18 +26,28 @@ func TestEvalReadState(t *testing.T) {
|
|||
provider := providers.Interface(mockProvider)
|
||||
|
||||
cases := map[string]struct {
|
||||
Resources map[string]*ResourceState
|
||||
State *states.State
|
||||
Node *EvalReadState
|
||||
ExpectedInstanceId string
|
||||
}{
|
||||
"ReadState gets primary instance state": {
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.bar": &ResourceState{
|
||||
Primary: &InstanceState{
|
||||
ID: "i-abc123",
|
||||
},
|
||||
},
|
||||
},
|
||||
State: states.BuildState(func(s *states.SyncState) {
|
||||
providerAddr := addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("aws"),
|
||||
Module: addrs.RootModule,
|
||||
}
|
||||
oneAddr := addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "bar",
|
||||
}.Absolute(addrs.RootModuleInstance)
|
||||
s.SetResourceProvider(oneAddr, providerAddr)
|
||||
s.SetResourceInstanceCurrent(oneAddr.Instance(addrs.NoKey), &states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
||||
}, providerAddr)
|
||||
}),
|
||||
|
||||
Node: &EvalReadState{
|
||||
Addr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
|
@ -56,15 +66,7 @@ func TestEvalReadState(t *testing.T) {
|
|||
for k, c := range cases {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
ctx := new(MockEvalContext)
|
||||
state := MustShimLegacyState(&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: c.Resources,
|
||||
},
|
||||
},
|
||||
})
|
||||
ctx.StateState = state.SyncWrapper()
|
||||
ctx.StateState = c.State.SyncWrapper()
|
||||
ctx.PathPath = addrs.RootModuleInstance
|
||||
|
||||
diags := c.Node.Eval(ctx)
|
||||
|
@ -97,18 +99,28 @@ func TestEvalReadStateDeposed(t *testing.T) {
|
|||
provider := providers.Interface(mockProvider)
|
||||
|
||||
cases := map[string]struct {
|
||||
Resources map[string]*ResourceState
|
||||
State *states.State
|
||||
Node *EvalReadStateDeposed
|
||||
ExpectedInstanceId string
|
||||
}{
|
||||
"ReadStateDeposed gets deposed instance": {
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.bar": &ResourceState{
|
||||
Deposed: []*InstanceState{
|
||||
&InstanceState{ID: "i-abc123"},
|
||||
},
|
||||
},
|
||||
},
|
||||
State: states.BuildState(func(s *states.SyncState) {
|
||||
providerAddr := addrs.AbsProviderConfig{
|
||||
Provider: addrs.NewDefaultProvider("aws"),
|
||||
Module: addrs.RootModule,
|
||||
}
|
||||
oneAddr := addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "bar",
|
||||
}.Absolute(addrs.RootModuleInstance)
|
||||
s.SetResourceProvider(oneAddr, providerAddr)
|
||||
s.SetResourceInstanceDeposed(oneAddr.Instance(addrs.NoKey), states.DeposedKey("00000001"), &states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"id":"i-abc123"}`),
|
||||
}, providerAddr)
|
||||
}),
|
||||
|
||||
Node: &EvalReadStateDeposed{
|
||||
Addr: addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
|
@ -127,15 +139,7 @@ func TestEvalReadStateDeposed(t *testing.T) {
|
|||
for k, c := range cases {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
ctx := new(MockEvalContext)
|
||||
state := MustShimLegacyState(&State{
|
||||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Resources: c.Resources,
|
||||
},
|
||||
},
|
||||
})
|
||||
ctx.StateState = state.SyncWrapper()
|
||||
ctx.StateState = c.State.SyncWrapper()
|
||||
ctx.PathPath = addrs.RootModuleInstance
|
||||
|
||||
diags := c.Node.Eval(ctx)
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(make([]interface{}, 0))
|
||||
gob.Register(make([]map[string]interface{}, 0))
|
||||
gob.Register(make(map[string]interface{}))
|
||||
gob.Register(make(map[string]string))
|
||||
}
|
||||
|
||||
// Plan represents a single Terraform execution plan, which contains
|
||||
// all the information necessary to make an infrastructure change.
|
||||
//
|
||||
// A plan has to contain basically the entire state of the world
|
||||
// necessary to make a change: the state, diff, config, backend config, etc.
|
||||
// This is so that it can run alone without any other data.
|
||||
type Plan struct {
|
||||
// Diff describes the resource actions that must be taken when this
|
||||
// plan is applied.
|
||||
Diff *Diff
|
||||
|
||||
// Config represents the entire configuration that was present when this
|
||||
// plan was created.
|
||||
Config *configs.Config
|
||||
|
||||
// State is the Terraform state that was current when this plan was
|
||||
// created.
|
||||
//
|
||||
// It is not allowed to apply a plan that has a stale state, since its
|
||||
// diff could be outdated.
|
||||
State *State
|
||||
|
||||
// Vars retains the variables that were set when creating the plan, so
|
||||
// that the same variables can be applied during apply.
|
||||
Vars map[string]cty.Value
|
||||
|
||||
// Targets, if non-empty, contains a set of resource address strings that
|
||||
// identify graph nodes that were selected as targets for plan.
|
||||
//
|
||||
// When targets are set, any graph node that is not directly targeted or
|
||||
// indirectly targeted via dependencies is excluded from the graph.
|
||||
Targets []string
|
||||
|
||||
// TerraformVersion is the version of Terraform that was used to create
|
||||
// this plan.
|
||||
//
|
||||
// It is not allowed to apply a plan created with a different version of
|
||||
// Terraform, since the other fields of this structure may be interpreted
|
||||
// in different ways between versions.
|
||||
TerraformVersion string
|
||||
|
||||
// ProviderSHA256s is a map giving the SHA256 hashes of the exact binaries
|
||||
// used as plugins for each provider during plan.
|
||||
//
|
||||
// These must match between plan and apply to ensure that the diff is
|
||||
// correctly interpreted, since different provider versions may have
|
||||
// different attributes or attribute value constraints.
|
||||
ProviderSHA256s map[string][]byte
|
||||
|
||||
// Backend is the backend that this plan should use and store data with.
|
||||
Backend *BackendState
|
||||
|
||||
// Destroy indicates that this plan was created for a full destroy operation
|
||||
Destroy bool
|
||||
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (p *Plan) String() string {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("DIFF:\n\n")
|
||||
buf.WriteString(p.Diff.String())
|
||||
buf.WriteString("\n\nSTATE:\n\n")
|
||||
buf.WriteString(p.State.String())
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (p *Plan) init() {
|
||||
p.once.Do(func() {
|
||||
if p.Diff == nil {
|
||||
p.Diff = new(Diff)
|
||||
p.Diff.init()
|
||||
}
|
||||
|
||||
if p.State == nil {
|
||||
p.State = new(State)
|
||||
p.State.init()
|
||||
}
|
||||
|
||||
if p.Vars == nil {
|
||||
p.Vars = make(map[string]cty.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// The format byte is prefixed into the plan file format so that we have
|
||||
// the ability in the future to change the file format if we want for any
|
||||
// reason.
|
||||
const planFormatMagic = "tfplan"
|
||||
const planFormatVersion byte = 2
|
||||
|
||||
// ReadPlan reads a plan structure out of a reader in the format that
|
||||
// was written by WritePlan.
|
||||
func ReadPlan(src io.Reader) (*Plan, error) {
|
||||
return nil, fmt.Errorf("terraform.ReadPlan is no longer in use; use planfile.Open instead")
|
||||
}
|
||||
|
||||
// WritePlan writes a plan somewhere in a binary format.
|
||||
func WritePlan(d *Plan, dst io.Writer) error {
|
||||
return fmt.Errorf("terraform.WritePlan is no longer in use; use planfile.Create instead")
|
||||
}
|
|
@ -1,516 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/mitchellh/reflectwalk"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||
)
|
||||
|
||||
// Resource is a legacy way to identify a particular resource instance.
|
||||
//
|
||||
// New code should use addrs.ResourceInstance instead. This is still here
|
||||
// only for codepaths that haven't been updated yet.
|
||||
type Resource struct {
|
||||
// These are all used by the new EvalNode stuff.
|
||||
Name string
|
||||
Type string
|
||||
CountIndex int
|
||||
|
||||
// These aren't really used anymore anywhere, but we keep them around
|
||||
// since we haven't done a proper cleanup yet.
|
||||
Id string
|
||||
Info *InstanceInfo
|
||||
Config *ResourceConfig
|
||||
Dependencies []string
|
||||
Diff *InstanceDiff
|
||||
Provider ResourceProvider
|
||||
State *InstanceState
|
||||
Flags ResourceFlag
|
||||
}
|
||||
|
||||
// NewResource constructs a legacy Resource object from an
|
||||
// addrs.ResourceInstance value.
|
||||
//
|
||||
// This is provided to shim to old codepaths that haven't been updated away
|
||||
// from this type yet. Since this old type is not able to represent instances
|
||||
// that have string keys, this function will panic if given a resource address
|
||||
// that has a string key.
|
||||
func NewResource(addr addrs.ResourceInstance) *Resource {
|
||||
ret := &Resource{
|
||||
Name: addr.Resource.Name,
|
||||
Type: addr.Resource.Type,
|
||||
}
|
||||
|
||||
if addr.Key != addrs.NoKey {
|
||||
switch tk := addr.Key.(type) {
|
||||
case addrs.IntKey:
|
||||
ret.CountIndex = int(tk)
|
||||
default:
|
||||
panic(fmt.Errorf("resource instance with key %#v is not supported", addr.Key))
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// ResourceKind specifies what kind of instance we're working with, whether
|
||||
// its a primary instance, a tainted instance, or an orphan.
|
||||
type ResourceFlag byte
|
||||
|
||||
// InstanceInfo is used to hold information about the instance and/or
|
||||
// resource being modified.
|
||||
type InstanceInfo struct {
|
||||
// Id is a unique name to represent this instance. This is not related
|
||||
// to InstanceState.ID in any way.
|
||||
Id string
|
||||
|
||||
// ModulePath is the complete path of the module containing this
|
||||
// instance.
|
||||
ModulePath []string
|
||||
|
||||
// Type is the resource type of this instance
|
||||
Type string
|
||||
|
||||
// uniqueExtra is an internal field that can be populated to supply
|
||||
// extra metadata that is used to identify a unique instance in
|
||||
// the graph walk. This will be appended to HumanID when uniqueId
|
||||
// is called.
|
||||
uniqueExtra string
|
||||
}
|
||||
|
||||
// NewInstanceInfo constructs an InstanceInfo from an addrs.AbsResourceInstance.
|
||||
//
|
||||
// InstanceInfo is a legacy type, and uses of it should be gradually replaced
|
||||
// by direct use of addrs.AbsResource or addrs.AbsResourceInstance as
|
||||
// appropriate.
|
||||
//
|
||||
// The legacy InstanceInfo type cannot represent module instances with instance
|
||||
// keys, so this function will panic if given such a path. Uses of this type
|
||||
// should all be removed or replaced before implementing "count" and "for_each"
|
||||
// arguments on modules in order to avoid such panics.
|
||||
//
|
||||
// This legacy type also cannot represent resource instances with string
|
||||
// instance keys. It will panic if the given key is not either NoKey or an
|
||||
// IntKey.
|
||||
func NewInstanceInfo(addr addrs.AbsResourceInstance) *InstanceInfo {
|
||||
// We need an old-style []string module path for InstanceInfo.
|
||||
path := make([]string, len(addr.Module))
|
||||
for i, step := range addr.Module {
|
||||
if step.InstanceKey != addrs.NoKey {
|
||||
panic("NewInstanceInfo cannot convert module instance with key")
|
||||
}
|
||||
path[i] = step.Name
|
||||
}
|
||||
|
||||
// This is a funny old meaning of "id" that is no longer current. It should
|
||||
// not be used for anything users might see. Note that it does not include
|
||||
// a representation of the resource mode, and so it's impossible to
|
||||
// determine from an InstanceInfo alone whether it is a managed or data
|
||||
// resource that is being referred to.
|
||||
id := fmt.Sprintf("%s.%s", addr.Resource.Resource.Type, addr.Resource.Resource.Name)
|
||||
if addr.Resource.Resource.Mode == addrs.DataResourceMode {
|
||||
id = "data." + id
|
||||
}
|
||||
if addr.Resource.Key != addrs.NoKey {
|
||||
switch k := addr.Resource.Key.(type) {
|
||||
case addrs.IntKey:
|
||||
id = id + fmt.Sprintf(".%d", int(k))
|
||||
default:
|
||||
panic(fmt.Sprintf("NewInstanceInfo cannot convert resource instance with %T instance key", addr.Resource.Key))
|
||||
}
|
||||
}
|
||||
|
||||
return &InstanceInfo{
|
||||
Id: id,
|
||||
ModulePath: path,
|
||||
Type: addr.Resource.Resource.Type,
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceAddress returns the address of the resource that the receiver is describing.
|
||||
func (i *InstanceInfo) ResourceAddress() *ResourceAddress {
|
||||
// GROSS: for tainted and deposed instances, their status gets appended
|
||||
// to i.Id to create a unique id for the graph node. Historically these
|
||||
// ids were displayed to the user, so it's designed to be human-readable:
|
||||
// "aws_instance.bar.0 (deposed #0)"
|
||||
//
|
||||
// So here we detect such suffixes and try to interpret them back to
|
||||
// their original meaning so we can then produce a ResourceAddress
|
||||
// with a suitable InstanceType.
|
||||
id := i.Id
|
||||
instanceType := TypeInvalid
|
||||
if idx := strings.Index(id, " ("); idx != -1 {
|
||||
remain := id[idx:]
|
||||
id = id[:idx]
|
||||
|
||||
switch {
|
||||
case strings.Contains(remain, "tainted"):
|
||||
instanceType = TypeTainted
|
||||
case strings.Contains(remain, "deposed"):
|
||||
instanceType = TypeDeposed
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := parseResourceAddressInternal(id)
|
||||
if err != nil {
|
||||
// should never happen, since that would indicate a bug in the
|
||||
// code that constructed this InstanceInfo.
|
||||
panic(fmt.Errorf("InstanceInfo has invalid Id %s", id))
|
||||
}
|
||||
if len(i.ModulePath) > 1 {
|
||||
addr.Path = i.ModulePath[1:] // trim off "root" prefix, which is implied
|
||||
}
|
||||
if instanceType != TypeInvalid {
|
||||
addr.InstanceTypeSet = true
|
||||
addr.InstanceType = instanceType
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// ResourceConfig is a legacy type that was formerly used to represent
|
||||
// interpolatable configuration blocks. It is now only used to shim to old
|
||||
// APIs that still use this type, via NewResourceConfigShimmed.
|
||||
type ResourceConfig struct {
|
||||
ComputedKeys []string
|
||||
Raw map[string]interface{}
|
||||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
// NewResourceConfigRaw constructs a ResourceConfig whose content is exactly
|
||||
// the given value.
|
||||
//
|
||||
// The given value may contain hcl2shim.UnknownVariableValue to signal that
|
||||
// something is computed, but it must not contain unprocessed interpolation
|
||||
// sequences as we might've seen in Terraform v0.11 and prior.
|
||||
func NewResourceConfigRaw(raw map[string]interface{}) *ResourceConfig {
|
||||
v := hcl2shim.HCL2ValueFromConfigValue(raw)
|
||||
|
||||
// This is a little weird but we round-trip the value through the hcl2shim
|
||||
// package here for two reasons: firstly, because that reduces the risk
|
||||
// of it including something unlike what NewResourceConfigShimmed would
|
||||
// produce, and secondly because it creates a copy of "raw" just in case
|
||||
// something is relying on the fact that in the old world the raw and
|
||||
// config maps were always distinct, and thus you could in principle mutate
|
||||
// one without affecting the other. (I sure hope nobody was doing that, though!)
|
||||
cfg := hcl2shim.ConfigValueFromHCL2(v).(map[string]interface{})
|
||||
|
||||
return &ResourceConfig{
|
||||
Raw: raw,
|
||||
Config: cfg,
|
||||
|
||||
ComputedKeys: newResourceConfigShimmedComputedKeys(v, ""),
|
||||
}
|
||||
}
|
||||
|
||||
// NewResourceConfigShimmed wraps a cty.Value of object type in a legacy
|
||||
// ResourceConfig object, so that it can be passed to older APIs that expect
|
||||
// this wrapping.
|
||||
//
|
||||
// The returned ResourceConfig is already interpolated and cannot be
|
||||
// re-interpolated. It is, therefore, useful only to functions that expect
|
||||
// an already-populated ResourceConfig which they then treat as read-only.
|
||||
//
|
||||
// If the given value is not of an object type that conforms to the given
|
||||
// schema then this function will panic.
|
||||
func NewResourceConfigShimmed(val cty.Value, schema *configschema.Block) *ResourceConfig {
|
||||
if !val.Type().IsObjectType() {
|
||||
panic(fmt.Errorf("NewResourceConfigShimmed given %#v; an object type is required", val.Type()))
|
||||
}
|
||||
ret := &ResourceConfig{}
|
||||
|
||||
legacyVal := hcl2shim.ConfigValueFromHCL2Block(val, schema)
|
||||
if legacyVal != nil {
|
||||
ret.Config = legacyVal
|
||||
|
||||
// Now we need to walk through our structure and find any unknown values,
|
||||
// producing the separate list ComputedKeys to represent these. We use the
|
||||
// schema here so that we can preserve the expected invariant
|
||||
// that an attribute is always either wholly known or wholly unknown, while
|
||||
// a child block can be partially unknown.
|
||||
ret.ComputedKeys = newResourceConfigShimmedComputedKeys(val, "")
|
||||
} else {
|
||||
ret.Config = make(map[string]interface{})
|
||||
}
|
||||
ret.Raw = ret.Config
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Record the any config values in ComputedKeys. This field had been unused in
|
||||
// helper/schema, but in the new protocol we're using this so that the SDK can
|
||||
// now handle having an unknown collection. The legacy diff code doesn't
|
||||
// properly handle the unknown, because it can't be expressed in the same way
|
||||
// between the config and diff.
|
||||
func newResourceConfigShimmedComputedKeys(val cty.Value, path string) []string {
|
||||
var ret []string
|
||||
ty := val.Type()
|
||||
|
||||
if val.IsNull() {
|
||||
return ret
|
||||
}
|
||||
|
||||
if !val.IsKnown() {
|
||||
// we shouldn't have an entirely unknown resource, but prevent empty
|
||||
// strings just in case
|
||||
if len(path) > 0 {
|
||||
ret = append(ret, path)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
path += "."
|
||||
}
|
||||
switch {
|
||||
case ty.IsListType(), ty.IsTupleType(), ty.IsSetType():
|
||||
i := 0
|
||||
for it := val.ElementIterator(); it.Next(); i++ {
|
||||
_, subVal := it.Element()
|
||||
keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%d", path, i))
|
||||
ret = append(ret, keys...)
|
||||
}
|
||||
|
||||
case ty.IsMapType(), ty.IsObjectType():
|
||||
for it := val.ElementIterator(); it.Next(); {
|
||||
subK, subVal := it.Element()
|
||||
keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%s", path, subK.AsString()))
|
||||
ret = append(ret, keys...)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// DeepCopy performs a deep copy of the configuration. This makes it safe
|
||||
// to modify any of the structures that are part of the resource config without
|
||||
// affecting the original configuration.
|
||||
func (c *ResourceConfig) DeepCopy() *ResourceConfig {
|
||||
// DeepCopying a nil should return a nil to avoid panics
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy, this will copy all the exported attributes
|
||||
copy, err := copystructure.Config{Lock: true}.Copy(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Force the type
|
||||
result := copy.(*ResourceConfig)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Equal checks the equality of two resource configs.
|
||||
func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool {
|
||||
// If either are nil, then they're only equal if they're both nil
|
||||
if c == nil || c2 == nil {
|
||||
return c == c2
|
||||
}
|
||||
|
||||
// Sort the computed keys so they're deterministic
|
||||
sort.Strings(c.ComputedKeys)
|
||||
sort.Strings(c2.ComputedKeys)
|
||||
|
||||
// Two resource configs if their exported properties are equal.
|
||||
// We don't compare "raw" because it is never used again after
|
||||
// initialization and for all intents and purposes they are equal
|
||||
// if the exported properties are equal.
|
||||
check := [][2]interface{}{
|
||||
{c.ComputedKeys, c2.ComputedKeys},
|
||||
{c.Raw, c2.Raw},
|
||||
{c.Config, c2.Config},
|
||||
}
|
||||
for _, pair := range check {
|
||||
if !reflect.DeepEqual(pair[0], pair[1]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// CheckSet checks that the given list of configuration keys is
|
||||
// properly set. If not, errors are returned for each unset key.
|
||||
//
|
||||
// This is useful to be called in the Validate method of a ResourceProvider.
|
||||
func (c *ResourceConfig) CheckSet(keys []string) []error {
|
||||
var errs []error
|
||||
|
||||
for _, k := range keys {
|
||||
if !c.IsSet(k) {
|
||||
errs = append(errs, fmt.Errorf("%s must be set", k))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Get looks up a configuration value by key and returns the value.
|
||||
//
|
||||
// The second return value is true if the get was successful. Get will
|
||||
// return the raw value if the key is computed, so you should pair this
|
||||
// with IsComputed.
|
||||
func (c *ResourceConfig) Get(k string) (interface{}, bool) {
|
||||
// We aim to get a value from the configuration. If it is computed,
|
||||
// then we return the pure raw value.
|
||||
source := c.Config
|
||||
if c.IsComputed(k) {
|
||||
source = c.Raw
|
||||
}
|
||||
|
||||
return c.get(k, source)
|
||||
}
|
||||
|
||||
// GetRaw looks up a configuration value by key and returns the value,
|
||||
// from the raw, uninterpolated config.
|
||||
//
|
||||
// The second return value is true if the get was successful. Get will
|
||||
// not succeed if the value is being computed.
|
||||
func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) {
|
||||
return c.get(k, c.Raw)
|
||||
}
|
||||
|
||||
// IsComputed returns whether the given key is computed or not.
|
||||
func (c *ResourceConfig) IsComputed(k string) bool {
|
||||
// The next thing we do is check the config if we get a computed
|
||||
// value out of it.
|
||||
v, ok := c.get(k, c.Config)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// If value is nil, then it isn't computed
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Test if the value contains an unknown value
|
||||
var w unknownCheckWalker
|
||||
if err := reflectwalk.Walk(v, &w); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return w.Unknown
|
||||
}
|
||||
|
||||
// IsSet checks if the key in the configuration is set. A key is set if
|
||||
// it has a value or the value is being computed (is unknown currently).
|
||||
//
|
||||
// This function should be used rather than checking the keys of the
|
||||
// raw configuration itself, since a key may be omitted from the raw
|
||||
// configuration if it is being computed.
|
||||
func (c *ResourceConfig) IsSet(k string) bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.IsComputed(k) {
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := c.Get(k); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *ResourceConfig) get(
|
||||
k string, raw map[string]interface{}) (interface{}, bool) {
|
||||
parts := strings.Split(k, ".")
|
||||
if len(parts) == 1 && parts[0] == "" {
|
||||
parts = nil
|
||||
}
|
||||
|
||||
var current interface{} = raw
|
||||
var previous interface{} = nil
|
||||
for i, part := range parts {
|
||||
if current == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cv := reflect.ValueOf(current)
|
||||
switch cv.Kind() {
|
||||
case reflect.Map:
|
||||
previous = current
|
||||
v := cv.MapIndex(reflect.ValueOf(part))
|
||||
if !v.IsValid() {
|
||||
if i > 0 && i != (len(parts)-1) {
|
||||
tryKey := strings.Join(parts[i:], ".")
|
||||
v := cv.MapIndex(reflect.ValueOf(tryKey))
|
||||
if !v.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return v.Interface(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
current = v.Interface()
|
||||
case reflect.Slice:
|
||||
previous = current
|
||||
|
||||
if part == "#" {
|
||||
// If any value in a list is computed, this whole thing
|
||||
// is computed and we can't read any part of it.
|
||||
for i := 0; i < cv.Len(); i++ {
|
||||
if v := cv.Index(i).Interface(); v == hcl2shim.UnknownVariableValue {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
|
||||
current = cv.Len()
|
||||
} else {
|
||||
i, err := strconv.ParseInt(part, 0, 0)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if int(i) < 0 || int(i) >= cv.Len() {
|
||||
return nil, false
|
||||
}
|
||||
current = cv.Index(int(i)).Interface()
|
||||
}
|
||||
case reflect.String:
|
||||
// This happens when map keys contain "." and have a common
|
||||
// prefix so were split as path components above.
|
||||
actualKey := strings.Join(parts[i-1:], ".")
|
||||
if prevMap, ok := previous.(map[string]interface{}); ok {
|
||||
v, ok := prevMap[actualKey]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
return nil, false
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown kind: %s", cv.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
return current, true
|
||||
}
|
||||
|
||||
// unknownCheckWalker
|
||||
type unknownCheckWalker struct {
|
||||
Unknown bool
|
||||
}
|
||||
|
||||
func (w *unknownCheckWalker) Primitive(v reflect.Value) error {
|
||||
if v.Interface() == hcl2shim.UnknownVariableValue {
|
||||
w.Unknown = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,226 +1,5 @@
|
|||
package terraform
|
||||
|
||||
// ResourceProvider is a legacy interface for providers.
|
||||
//
|
||||
// This is retained only for compatibility with legacy code. The current
|
||||
// interface for providers is providers.Interface, in the sibling directory
|
||||
// named "providers".
|
||||
type ResourceProvider interface {
|
||||
/*********************************************************************
|
||||
* Functions related to the provider
|
||||
*********************************************************************/
|
||||
|
||||
// ProviderSchema returns the config schema for the main provider
|
||||
// configuration, as would appear in a "provider" block in the
|
||||
// configuration files.
|
||||
//
|
||||
// Currently not all providers support schema. Callers must therefore
|
||||
// first call Resources and DataSources and ensure that at least one
|
||||
// resource or data source has the SchemaAvailable flag set.
|
||||
GetSchema(*ProviderSchemaRequest) (*ProviderSchema, error)
|
||||
|
||||
// Input was used prior to v0.12 to ask the provider to prompt the user
|
||||
// for input to complete the configuration.
|
||||
//
|
||||
// From v0.12 onwards this method is never called because Terraform Core
|
||||
// is able to handle the necessary input logic itself based on the
|
||||
// schema returned from GetSchema.
|
||||
Input(UIInput, *ResourceConfig) (*ResourceConfig, error)
|
||||
|
||||
// Validate is called once at the beginning with the raw configuration
|
||||
// (no interpolation done) and can return a list of warnings and/or
|
||||
// errors.
|
||||
//
|
||||
// This is called once with the provider configuration only. It may not
|
||||
// be called at all if no provider configuration is given.
|
||||
//
|
||||
// This should not assume that any values of the configurations are valid.
|
||||
// The primary use case of this call is to check that required keys are
|
||||
// set.
|
||||
Validate(*ResourceConfig) ([]string, []error)
|
||||
|
||||
// Configure configures the provider itself with the configuration
|
||||
// given. This is useful for setting things like access keys.
|
||||
//
|
||||
// This won't be called at all if no provider configuration is given.
|
||||
//
|
||||
// Configure returns an error if it occurred.
|
||||
Configure(*ResourceConfig) error
|
||||
|
||||
// Resources returns all the available resource types that this provider
|
||||
// knows how to manage.
|
||||
Resources() []ResourceType
|
||||
|
||||
// Stop is called when the provider should halt any in-flight actions.
|
||||
//
|
||||
// This can be used to make a nicer Ctrl-C experience for Terraform.
|
||||
// Even if this isn't implemented to do anything (just returns nil),
|
||||
// Terraform will still cleanly stop after the currently executing
|
||||
// graph node is complete. However, this API can be used to make more
|
||||
// efficient halts.
|
||||
//
|
||||
// Stop doesn't have to and shouldn't block waiting for in-flight actions
|
||||
// to complete. It should take any action it wants and return immediately
|
||||
// acknowledging it has received the stop request. Terraform core will
|
||||
// automatically not make any further API calls to the provider soon
|
||||
// after Stop is called (technically exactly once the currently executing
|
||||
// graph nodes are complete).
|
||||
//
|
||||
// The error returned, if non-nil, is assumed to mean that signaling the
|
||||
// stop somehow failed and that the user should expect potentially waiting
|
||||
// a longer period of time.
|
||||
Stop() error
|
||||
|
||||
/*********************************************************************
|
||||
* Functions related to individual resources
|
||||
*********************************************************************/
|
||||
|
||||
// ValidateResource is called once at the beginning with the raw
|
||||
// configuration (no interpolation done) and can return a list of warnings
|
||||
// and/or errors.
|
||||
//
|
||||
// This is called once per resource.
|
||||
//
|
||||
// This should not assume any of the values in the resource configuration
|
||||
// are valid since it is possible they have to be interpolated still.
|
||||
// The primary use case of this call is to check that the required keys
|
||||
// are set and that the general structure is correct.
|
||||
ValidateResource(string, *ResourceConfig) ([]string, []error)
|
||||
|
||||
// Apply applies a diff to a specific resource and returns the new
|
||||
// resource state along with an error.
|
||||
//
|
||||
// If the resource state given has an empty ID, then a new resource
|
||||
// is expected to be created.
|
||||
Apply(
|
||||
*InstanceInfo,
|
||||
*InstanceState,
|
||||
*InstanceDiff) (*InstanceState, error)
|
||||
|
||||
// Diff diffs a resource versus a desired state and returns
|
||||
// a diff.
|
||||
Diff(
|
||||
*InstanceInfo,
|
||||
*InstanceState,
|
||||
*ResourceConfig) (*InstanceDiff, error)
|
||||
|
||||
// Refresh refreshes a resource and updates all of its attributes
|
||||
// with the latest information.
|
||||
Refresh(*InstanceInfo, *InstanceState) (*InstanceState, error)
|
||||
|
||||
/*********************************************************************
|
||||
* Functions related to importing
|
||||
*********************************************************************/
|
||||
|
||||
// ImportState requests that the given resource be imported.
|
||||
//
|
||||
// The returned InstanceState only requires ID be set. Importing
|
||||
// will always call Refresh after the state to complete it.
|
||||
//
|
||||
// IMPORTANT: InstanceState doesn't have the resource type attached
|
||||
// to it. A type must be specified on the state via the Ephemeral
|
||||
// field on the state.
|
||||
//
|
||||
// This function can return multiple states. Normally, an import
|
||||
// will map 1:1 to a physical resource. However, some resources map
|
||||
// to multiple. For example, an AWS security group may contain many rules.
|
||||
// Each rule is represented by a separate resource in Terraform,
|
||||
// therefore multiple states are returned.
|
||||
ImportState(*InstanceInfo, string) ([]*InstanceState, error)
|
||||
|
||||
/*********************************************************************
|
||||
* Functions related to data resources
|
||||
*********************************************************************/
|
||||
|
||||
// ValidateDataSource is called once at the beginning with the raw
|
||||
// configuration (no interpolation done) and can return a list of warnings
|
||||
// and/or errors.
|
||||
//
|
||||
// This is called once per data source instance.
|
||||
//
|
||||
// This should not assume any of the values in the resource configuration
|
||||
// are valid since it is possible they have to be interpolated still.
|
||||
// The primary use case of this call is to check that the required keys
|
||||
// are set and that the general structure is correct.
|
||||
ValidateDataSource(string, *ResourceConfig) ([]string, []error)
|
||||
|
||||
// DataSources returns all of the available data sources that this
|
||||
// provider implements.
|
||||
DataSources() []DataSource
|
||||
|
||||
// ReadDataDiff produces a diff that represents the state that will
|
||||
// be produced when the given data source is read using a later call
|
||||
// to ReadDataApply.
|
||||
ReadDataDiff(*InstanceInfo, *ResourceConfig) (*InstanceDiff, error)
|
||||
|
||||
// ReadDataApply initializes a data instance using the configuration
|
||||
// in a diff produced by ReadDataDiff.
|
||||
ReadDataApply(*InstanceInfo, *InstanceDiff) (*InstanceState, error)
|
||||
}
|
||||
|
||||
// ResourceProviderCloser is an interface that providers that can close
|
||||
// connections that aren't needed anymore must implement.
|
||||
type ResourceProviderCloser interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ResourceType is a type of resource that a resource provider can manage.
|
||||
type ResourceType struct {
|
||||
Name string // Name of the resource, example "instance" (no provider prefix)
|
||||
Importable bool // Whether this resource supports importing
|
||||
|
||||
// SchemaAvailable is set if the provider supports the ProviderSchema,
|
||||
// ResourceTypeSchema and DataSourceSchema methods. Although it is
|
||||
// included on each resource type, it's actually a provider-wide setting
|
||||
// that's smuggled here only because that avoids a breaking change to
|
||||
// the plugin protocol.
|
||||
SchemaAvailable bool
|
||||
}
|
||||
|
||||
// DataSource is a data source that a resource provider implements.
|
||||
type DataSource struct {
|
||||
Name string
|
||||
|
||||
// SchemaAvailable is set if the provider supports the ProviderSchema,
|
||||
// ResourceTypeSchema and DataSourceSchema methods. Although it is
|
||||
// included on each resource type, it's actually a provider-wide setting
|
||||
// that's smuggled here only because that avoids a breaking change to
|
||||
// the plugin protocol.
|
||||
SchemaAvailable bool
|
||||
}
|
||||
|
||||
// ResourceProviderFactory is a function type that creates a new instance
|
||||
// of a resource provider.
|
||||
type ResourceProviderFactory func() (ResourceProvider, error)
|
||||
|
||||
// ResourceProviderFactoryFixed is a helper that creates a
|
||||
// ResourceProviderFactory that just returns some fixed provider.
|
||||
func ResourceProviderFactoryFixed(p ResourceProvider) ResourceProviderFactory {
|
||||
return func() (ResourceProvider, error) {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
func ProviderHasResource(p ResourceProvider, n string) bool {
|
||||
for _, rt := range p.Resources() {
|
||||
if rt.Name == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func ProviderHasDataSource(p ResourceProvider, n string) bool {
|
||||
for _, rt := range p.DataSources() {
|
||||
if rt.Name == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const errPluginInit = `
|
||||
Plugin reinitialization required. Please run "terraform init".
|
||||
|
||||
|
|
|
@ -1,315 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MockResourceProvider implements ResourceProvider but mocks out all the
|
||||
// calls for testing purposes.
|
||||
type MockResourceProvider struct {
|
||||
sync.Mutex
|
||||
|
||||
// Anything you want, in case you need to store extra data with the mock.
|
||||
Meta interface{}
|
||||
|
||||
CloseCalled bool
|
||||
CloseError error
|
||||
GetSchemaCalled bool
|
||||
GetSchemaRequest *ProviderSchemaRequest
|
||||
GetSchemaReturn *ProviderSchema
|
||||
GetSchemaReturnError error
|
||||
InputCalled bool
|
||||
InputInput UIInput
|
||||
InputConfig *ResourceConfig
|
||||
InputReturnConfig *ResourceConfig
|
||||
InputReturnError error
|
||||
InputFn func(UIInput, *ResourceConfig) (*ResourceConfig, error)
|
||||
ApplyCalled bool
|
||||
ApplyInfo *InstanceInfo
|
||||
ApplyState *InstanceState
|
||||
ApplyDiff *InstanceDiff
|
||||
ApplyFn func(*InstanceInfo, *InstanceState, *InstanceDiff) (*InstanceState, error)
|
||||
ApplyReturn *InstanceState
|
||||
ApplyReturnError error
|
||||
ConfigureCalled bool
|
||||
ConfigureConfig *ResourceConfig
|
||||
ConfigureFn func(*ResourceConfig) error
|
||||
ConfigureReturnError error
|
||||
DiffCalled bool
|
||||
DiffInfo *InstanceInfo
|
||||
DiffState *InstanceState
|
||||
DiffDesired *ResourceConfig
|
||||
DiffFn func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error)
|
||||
DiffReturn *InstanceDiff
|
||||
DiffReturnError error
|
||||
RefreshCalled bool
|
||||
RefreshInfo *InstanceInfo
|
||||
RefreshState *InstanceState
|
||||
RefreshFn func(*InstanceInfo, *InstanceState) (*InstanceState, error)
|
||||
RefreshReturn *InstanceState
|
||||
RefreshReturnError error
|
||||
ResourcesCalled bool
|
||||
ResourcesReturn []ResourceType
|
||||
ReadDataApplyCalled bool
|
||||
ReadDataApplyInfo *InstanceInfo
|
||||
ReadDataApplyDiff *InstanceDiff
|
||||
ReadDataApplyFn func(*InstanceInfo, *InstanceDiff) (*InstanceState, error)
|
||||
ReadDataApplyReturn *InstanceState
|
||||
ReadDataApplyReturnError error
|
||||
ReadDataDiffCalled bool
|
||||
ReadDataDiffInfo *InstanceInfo
|
||||
ReadDataDiffDesired *ResourceConfig
|
||||
ReadDataDiffFn func(*InstanceInfo, *ResourceConfig) (*InstanceDiff, error)
|
||||
ReadDataDiffReturn *InstanceDiff
|
||||
ReadDataDiffReturnError error
|
||||
StopCalled bool
|
||||
StopFn func() error
|
||||
StopReturnError error
|
||||
DataSourcesCalled bool
|
||||
DataSourcesReturn []DataSource
|
||||
ValidateCalled bool
|
||||
ValidateConfig *ResourceConfig
|
||||
ValidateFn func(*ResourceConfig) ([]string, []error)
|
||||
ValidateReturnWarns []string
|
||||
ValidateReturnErrors []error
|
||||
ValidateResourceFn func(string, *ResourceConfig) ([]string, []error)
|
||||
ValidateResourceCalled bool
|
||||
ValidateResourceType string
|
||||
ValidateResourceConfig *ResourceConfig
|
||||
ValidateResourceReturnWarns []string
|
||||
ValidateResourceReturnErrors []error
|
||||
ValidateDataSourceFn func(string, *ResourceConfig) ([]string, []error)
|
||||
ValidateDataSourceCalled bool
|
||||
ValidateDataSourceType string
|
||||
ValidateDataSourceConfig *ResourceConfig
|
||||
ValidateDataSourceReturnWarns []string
|
||||
ValidateDataSourceReturnErrors []error
|
||||
|
||||
ImportStateCalled bool
|
||||
ImportStateInfo *InstanceInfo
|
||||
ImportStateID string
|
||||
ImportStateReturn []*InstanceState
|
||||
ImportStateReturnError error
|
||||
ImportStateFn func(*InstanceInfo, string) ([]*InstanceState, error)
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Close() error {
|
||||
p.CloseCalled = true
|
||||
return p.CloseError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) GetSchema(req *ProviderSchemaRequest) (*ProviderSchema, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.GetSchemaCalled = true
|
||||
p.GetSchemaRequest = req
|
||||
return p.GetSchemaReturn, p.GetSchemaReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Input(
|
||||
input UIInput, c *ResourceConfig) (*ResourceConfig, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
p.InputCalled = true
|
||||
p.InputInput = input
|
||||
p.InputConfig = c
|
||||
if p.InputFn != nil {
|
||||
return p.InputFn(input, c)
|
||||
}
|
||||
return p.InputReturnConfig, p.InputReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Validate(c *ResourceConfig) ([]string, []error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ValidateCalled = true
|
||||
p.ValidateConfig = c
|
||||
if p.ValidateFn != nil {
|
||||
return p.ValidateFn(c)
|
||||
}
|
||||
return p.ValidateReturnWarns, p.ValidateReturnErrors
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ValidateResource(t string, c *ResourceConfig) ([]string, []error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ValidateResourceCalled = true
|
||||
p.ValidateResourceType = t
|
||||
p.ValidateResourceConfig = c
|
||||
|
||||
if p.ValidateResourceFn != nil {
|
||||
return p.ValidateResourceFn(t, c)
|
||||
}
|
||||
|
||||
return p.ValidateResourceReturnWarns, p.ValidateResourceReturnErrors
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Configure(c *ResourceConfig) error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ConfigureCalled = true
|
||||
p.ConfigureConfig = c
|
||||
|
||||
if p.ConfigureFn != nil {
|
||||
return p.ConfigureFn(c)
|
||||
}
|
||||
|
||||
return p.ConfigureReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Stop() error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.StopCalled = true
|
||||
if p.StopFn != nil {
|
||||
return p.StopFn()
|
||||
}
|
||||
|
||||
return p.StopReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Apply(
|
||||
info *InstanceInfo,
|
||||
state *InstanceState,
|
||||
diff *InstanceDiff) (*InstanceState, error) {
|
||||
// We only lock while writing data. Reading is fine
|
||||
p.Lock()
|
||||
p.ApplyCalled = true
|
||||
p.ApplyInfo = info
|
||||
p.ApplyState = state
|
||||
p.ApplyDiff = diff
|
||||
p.Unlock()
|
||||
|
||||
if p.ApplyFn != nil {
|
||||
return p.ApplyFn(info, state, diff)
|
||||
}
|
||||
|
||||
return p.ApplyReturn.DeepCopy(), p.ApplyReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Diff(
|
||||
info *InstanceInfo,
|
||||
state *InstanceState,
|
||||
desired *ResourceConfig) (*InstanceDiff, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.DiffCalled = true
|
||||
p.DiffInfo = info
|
||||
p.DiffState = state
|
||||
p.DiffDesired = desired
|
||||
|
||||
if p.DiffFn != nil {
|
||||
return p.DiffFn(info, state, desired)
|
||||
}
|
||||
|
||||
return p.DiffReturn.DeepCopy(), p.DiffReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Refresh(
|
||||
info *InstanceInfo,
|
||||
s *InstanceState) (*InstanceState, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.RefreshCalled = true
|
||||
p.RefreshInfo = info
|
||||
p.RefreshState = s
|
||||
|
||||
if p.RefreshFn != nil {
|
||||
return p.RefreshFn(info, s)
|
||||
}
|
||||
|
||||
return p.RefreshReturn.DeepCopy(), p.RefreshReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Resources() []ResourceType {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ResourcesCalled = true
|
||||
return p.ResourcesReturn
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ImportState(info *InstanceInfo, id string) ([]*InstanceState, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ImportStateCalled = true
|
||||
p.ImportStateInfo = info
|
||||
p.ImportStateID = id
|
||||
if p.ImportStateFn != nil {
|
||||
return p.ImportStateFn(info, id)
|
||||
}
|
||||
|
||||
var result []*InstanceState
|
||||
if p.ImportStateReturn != nil {
|
||||
result = make([]*InstanceState, len(p.ImportStateReturn))
|
||||
for i, v := range p.ImportStateReturn {
|
||||
result[i] = v.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
return result, p.ImportStateReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ValidateDataSource(t string, c *ResourceConfig) ([]string, []error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ValidateDataSourceCalled = true
|
||||
p.ValidateDataSourceType = t
|
||||
p.ValidateDataSourceConfig = c
|
||||
|
||||
if p.ValidateDataSourceFn != nil {
|
||||
return p.ValidateDataSourceFn(t, c)
|
||||
}
|
||||
|
||||
return p.ValidateDataSourceReturnWarns, p.ValidateDataSourceReturnErrors
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ReadDataDiff(
|
||||
info *InstanceInfo,
|
||||
desired *ResourceConfig) (*InstanceDiff, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ReadDataDiffCalled = true
|
||||
p.ReadDataDiffInfo = info
|
||||
p.ReadDataDiffDesired = desired
|
||||
if p.ReadDataDiffFn != nil {
|
||||
return p.ReadDataDiffFn(info, desired)
|
||||
}
|
||||
|
||||
return p.ReadDataDiffReturn.DeepCopy(), p.ReadDataDiffReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ReadDataApply(
|
||||
info *InstanceInfo,
|
||||
d *InstanceDiff) (*InstanceState, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ReadDataApplyCalled = true
|
||||
p.ReadDataApplyInfo = info
|
||||
p.ReadDataApplyDiff = d
|
||||
|
||||
if p.ReadDataApplyFn != nil {
|
||||
return p.ReadDataApplyFn(info, d)
|
||||
}
|
||||
|
||||
return p.ReadDataApplyReturn.DeepCopy(), p.ReadDataApplyReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) DataSources() []DataSource {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.DataSourcesCalled = true
|
||||
return p.DataSourcesReturn
|
||||
}
|
|
@ -1,19 +1,12 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/hashicorp/terraform/providers"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestMockResourceProvider_impl(t *testing.T) {
|
||||
var _ ResourceProvider = new(MockResourceProvider)
|
||||
var _ ResourceProviderCloser = new(MockResourceProvider)
|
||||
}
|
||||
|
||||
// testProviderComponentFactory creates a componentFactory that contains only
|
||||
// a single given.
|
||||
func testProviderComponentFactory(name string, provider providers.Interface) *basicComponentFactory {
|
||||
|
@ -62,18 +55,6 @@ func mockProviderWithResourceTypeSchema(name string, schema *configschema.Block)
|
|||
}
|
||||
}
|
||||
|
||||
// mockProviderWithDataSourceSchema is a test helper to concisely create a mock
|
||||
// provider with a schema containing a single data source.
|
||||
func mockProviderWithDataSourceSchema(name string, schema *configschema.Block) *MockResourceProvider {
|
||||
return &MockResourceProvider{
|
||||
GetSchemaReturn: &ProviderSchema{
|
||||
DataSources: map[string]*configschema.Block{
|
||||
name: schema,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// simpleMockProvider returns a MockProvider that is pre-configured
|
||||
// with schema for its own config, for a resource type called "test_object" and
|
||||
// for a data source also called "test_object".
|
||||
|
|
|
@ -1,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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
2255
terraform/state.go
2255
terraform/state.go
File diff suppressed because it is too large
Load Diff
|
@ -1,267 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// StateFilter is responsible for filtering and searching a state.
|
||||
//
|
||||
// This is a separate struct from State rather than a method on State
|
||||
// because StateFilter might create sidecar data structures to optimize
|
||||
// filtering on the state.
|
||||
//
|
||||
// If you change the State, the filter created is invalid and either
|
||||
// Reset should be called or a new one should be allocated. StateFilter
|
||||
// will not watch State for changes and do this for you. If you filter after
|
||||
// changing the State without calling Reset, the behavior is not defined.
|
||||
type StateFilter struct {
|
||||
State *State
|
||||
}
|
||||
|
||||
// Filter takes the addresses specified by fs and finds all the matches.
|
||||
// The values of fs are resource addressing syntax that can be parsed by
|
||||
// ParseResourceAddress.
|
||||
func (f *StateFilter) Filter(fs ...string) ([]*StateFilterResult, error) {
|
||||
// Parse all the addresses
|
||||
as := make([]*ResourceAddress, len(fs))
|
||||
for i, v := range fs {
|
||||
a, err := ParseResourceAddress(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing address '%s': %s", v, err)
|
||||
}
|
||||
|
||||
as[i] = a
|
||||
}
|
||||
|
||||
// If we weren't given any filters, then we list all
|
||||
if len(fs) == 0 {
|
||||
as = append(as, &ResourceAddress{Index: -1})
|
||||
}
|
||||
|
||||
// Filter each of the address. We keep track of this in a map to
|
||||
// strip duplicates.
|
||||
resultSet := make(map[string]*StateFilterResult)
|
||||
for _, a := range as {
|
||||
for _, r := range f.filterSingle(a) {
|
||||
resultSet[r.String()] = r
|
||||
}
|
||||
}
|
||||
|
||||
// Make the result list
|
||||
results := make([]*StateFilterResult, 0, len(resultSet))
|
||||
for _, v := range resultSet {
|
||||
results = append(results, v)
|
||||
}
|
||||
|
||||
// Sort them and return
|
||||
sort.Sort(StateFilterResultSlice(results))
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
||||
// The slice to keep track of results
|
||||
var results []*StateFilterResult
|
||||
|
||||
// Go through modules first.
|
||||
modules := make([]*ModuleState, 0, len(f.State.Modules))
|
||||
for _, m := range f.State.Modules {
|
||||
if f.relevant(a, m) {
|
||||
modules = append(modules, m)
|
||||
|
||||
// Only add the module to the results if we haven't specified a type.
|
||||
// We also ignore the root module.
|
||||
if a.Type == "" && len(m.Path) > 1 {
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: m.Path[1:],
|
||||
Address: (&ResourceAddress{Path: m.Path[1:]}).String(),
|
||||
Value: m,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With the modules set, go through all the resources within
|
||||
// the modules to find relevant resources.
|
||||
for _, m := range modules {
|
||||
for n, r := range m.Resources {
|
||||
// The name in the state contains valuable information. Parse.
|
||||
key, err := ParseResourceStateKey(n)
|
||||
if err != nil {
|
||||
// If we get an error parsing, then just ignore it
|
||||
// out of the state.
|
||||
continue
|
||||
}
|
||||
|
||||
// Older states and test fixtures often don't contain the
|
||||
// type directly on the ResourceState. We add this so StateFilter
|
||||
// is a bit more robust.
|
||||
if r.Type == "" {
|
||||
r.Type = key.Type
|
||||
}
|
||||
|
||||
if f.relevant(a, r) {
|
||||
if a.Name != "" && a.Name != key.Name {
|
||||
// Name doesn't match
|
||||
continue
|
||||
}
|
||||
|
||||
if a.Index >= 0 && key.Index != a.Index {
|
||||
// Index doesn't match
|
||||
continue
|
||||
}
|
||||
|
||||
if a.Name != "" && a.Name != key.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
// Build the address for this resource
|
||||
addr := &ResourceAddress{
|
||||
Path: m.Path[1:],
|
||||
Name: key.Name,
|
||||
Type: key.Type,
|
||||
Index: key.Index,
|
||||
}
|
||||
|
||||
// Add the resource level result
|
||||
resourceResult := &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
Address: addr.String(),
|
||||
Value: r,
|
||||
}
|
||||
if !a.InstanceTypeSet {
|
||||
results = append(results, resourceResult)
|
||||
}
|
||||
|
||||
// Add the instances
|
||||
if r.Primary != nil {
|
||||
addr.InstanceType = TypePrimary
|
||||
addr.InstanceTypeSet = false
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
Address: addr.String(),
|
||||
Parent: resourceResult,
|
||||
Value: r.Primary,
|
||||
})
|
||||
}
|
||||
|
||||
for _, instance := range r.Deposed {
|
||||
if f.relevant(a, instance) {
|
||||
addr.InstanceType = TypeDeposed
|
||||
addr.InstanceTypeSet = true
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
Address: addr.String(),
|
||||
Parent: resourceResult,
|
||||
Value: instance,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// relevant checks for relevance of this address against the given value.
|
||||
func (f *StateFilter) relevant(addr *ResourceAddress, raw interface{}) bool {
|
||||
switch v := raw.(type) {
|
||||
case *ModuleState:
|
||||
path := v.Path[1:]
|
||||
|
||||
if len(addr.Path) > len(path) {
|
||||
// Longer path in address means there is no way we match.
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for a prefix match
|
||||
for i, p := range addr.Path {
|
||||
if path[i] != p {
|
||||
// Any mismatches don't match.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
case *ResourceState:
|
||||
if addr.Type == "" {
|
||||
// If we have no resource type, then we're interested in all!
|
||||
return true
|
||||
}
|
||||
|
||||
// If the type doesn't match we fail immediately
|
||||
if v.Type != addr.Type {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
default:
|
||||
// If we don't know about it, let's just say no
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// StateFilterResult is a single result from a filter operation. Filter
|
||||
// can match multiple things within a state (module, resource, instance, etc.)
|
||||
// and this unifies that.
|
||||
type StateFilterResult struct {
|
||||
// Module path of the result
|
||||
Path []string
|
||||
|
||||
// Address is the address that can be used to reference this exact result.
|
||||
Address string
|
||||
|
||||
// Parent, if non-nil, is a parent of this result. For instances, the
|
||||
// parent would be a resource. For resources, the parent would be
|
||||
// a module. For modules, this is currently nil.
|
||||
Parent *StateFilterResult
|
||||
|
||||
// Value is the actual value. This must be type switched on. It can be
|
||||
// any data structures that `State` can hold: `ModuleState`,
|
||||
// `ResourceState`, `InstanceState`.
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (r *StateFilterResult) String() string {
|
||||
return fmt.Sprintf("%T: %s", r.Value, r.Address)
|
||||
}
|
||||
|
||||
func (r *StateFilterResult) sortedType() int {
|
||||
switch r.Value.(type) {
|
||||
case *ModuleState:
|
||||
return 0
|
||||
case *ResourceState:
|
||||
return 1
|
||||
case *InstanceState:
|
||||
return 2
|
||||
default:
|
||||
return 50
|
||||
}
|
||||
}
|
||||
|
||||
// StateFilterResultSlice is a slice of results that implements
|
||||
// sort.Interface. The sorting goal is what is most appealing to
|
||||
// human output.
|
||||
type StateFilterResultSlice []*StateFilterResult
|
||||
|
||||
func (s StateFilterResultSlice) Len() int { return len(s) }
|
||||
func (s StateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s StateFilterResultSlice) Less(i, j int) bool {
|
||||
a, b := s[i], s[j]
|
||||
|
||||
// if these address contain an index, we want to sort by index rather than name
|
||||
addrA, errA := ParseResourceAddress(a.Address)
|
||||
addrB, errB := ParseResourceAddress(b.Address)
|
||||
if errA == nil && errB == nil && addrA.Name == addrB.Name && addrA.Index != addrB.Index {
|
||||
return addrA.Index < addrB.Index
|
||||
}
|
||||
|
||||
// If the addresses are different it is just lexographic sorting
|
||||
if a.Address != b.Address {
|
||||
return a.Address < b.Address
|
||||
}
|
||||
|
||||
// Addresses are the same, which means it matters on the type
|
||||
return a.sortedType() < b.sortedType()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,189 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/copystructure"
|
||||
)
|
||||
|
||||
// upgradeStateV1ToV2 is used to upgrade a V1 state representation
|
||||
// into a V2 state representation
|
||||
func upgradeStateV1ToV2(old *stateV1) (*State, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
remote, err := old.Remote.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading State V1: %v", err)
|
||||
}
|
||||
|
||||
modules := make([]*ModuleState, len(old.Modules))
|
||||
for i, module := range old.Modules {
|
||||
upgraded, err := module.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading State V1: %v", err)
|
||||
}
|
||||
modules[i] = upgraded
|
||||
}
|
||||
if len(modules) == 0 {
|
||||
modules = nil
|
||||
}
|
||||
|
||||
newState := &State{
|
||||
Version: 2,
|
||||
Serial: old.Serial,
|
||||
Remote: remote,
|
||||
Modules: modules,
|
||||
}
|
||||
|
||||
newState.sort()
|
||||
newState.init()
|
||||
|
||||
return newState, nil
|
||||
}
|
||||
|
||||
func (old *remoteStateV1) upgradeToV2() (*RemoteState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
config, err := copystructure.Copy(old.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading RemoteState V1: %v", err)
|
||||
}
|
||||
|
||||
return &RemoteState{
|
||||
Type: old.Type,
|
||||
Config: config.(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (old *moduleStateV1) upgradeToV2() (*ModuleState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pathRaw, err := copystructure.Copy(old.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err)
|
||||
}
|
||||
path, ok := pathRaw.([]string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: path is not a list of strings")
|
||||
}
|
||||
if len(path) == 0 {
|
||||
// We found some V1 states with a nil path. Assume root and catch
|
||||
// duplicate path errors later (as part of Validate).
|
||||
path = rootModulePath
|
||||
}
|
||||
|
||||
// Outputs needs upgrading to use the new structure
|
||||
outputs := make(map[string]*OutputState)
|
||||
for key, output := range old.Outputs {
|
||||
outputs[key] = &OutputState{
|
||||
Type: "string",
|
||||
Value: output,
|
||||
Sensitive: false,
|
||||
}
|
||||
}
|
||||
|
||||
resources := make(map[string]*ResourceState)
|
||||
for key, oldResource := range old.Resources {
|
||||
upgraded, err := oldResource.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err)
|
||||
}
|
||||
resources[key] = upgraded
|
||||
}
|
||||
|
||||
dependencies, err := copystructure.Copy(old.Dependencies)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err)
|
||||
}
|
||||
|
||||
return &ModuleState{
|
||||
Path: path,
|
||||
Outputs: outputs,
|
||||
Resources: resources,
|
||||
Dependencies: dependencies.([]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (old *resourceStateV1) upgradeToV2() (*ResourceState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
dependencies, err := copystructure.Copy(old.Dependencies)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err)
|
||||
}
|
||||
|
||||
primary, err := old.Primary.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err)
|
||||
}
|
||||
|
||||
deposed := make([]*InstanceState, len(old.Deposed))
|
||||
for i, v := range old.Deposed {
|
||||
upgraded, err := v.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err)
|
||||
}
|
||||
deposed[i] = upgraded
|
||||
}
|
||||
if len(deposed) == 0 {
|
||||
deposed = nil
|
||||
}
|
||||
|
||||
return &ResourceState{
|
||||
Type: old.Type,
|
||||
Dependencies: dependencies.([]string),
|
||||
Primary: primary,
|
||||
Deposed: deposed,
|
||||
Provider: old.Provider,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (old *instanceStateV1) upgradeToV2() (*InstanceState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
attributes, err := copystructure.Copy(old.Attributes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
|
||||
}
|
||||
ephemeral, err := old.Ephemeral.upgradeToV2()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
|
||||
}
|
||||
|
||||
meta, err := copystructure.Copy(old.Meta)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
|
||||
}
|
||||
|
||||
newMeta := make(map[string]interface{})
|
||||
for k, v := range meta.(map[string]string) {
|
||||
newMeta[k] = v
|
||||
}
|
||||
|
||||
return &InstanceState{
|
||||
ID: old.ID,
|
||||
Attributes: attributes.(map[string]string),
|
||||
Ephemeral: *ephemeral,
|
||||
Meta: newMeta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (old *ephemeralStateV1) upgradeToV2() (*EphemeralState, error) {
|
||||
connInfo, err := copystructure.Copy(old.ConnInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading EphemeralState V1: %v", err)
|
||||
}
|
||||
return &EphemeralState{
|
||||
ConnInfo: connInfo.(map[string]string),
|
||||
}, nil
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The upgrade process from V2 to V3 state does not affect the structure,
|
||||
// so we do not need to redeclare all of the structs involved - we just
|
||||
// take a deep copy of the old structure and assert the version number is
|
||||
// as we expect.
|
||||
func upgradeStateV2ToV3(old *State) (*State, error) {
|
||||
new := old.DeepCopy()
|
||||
|
||||
// Ensure the copied version is v2 before attempting to upgrade
|
||||
if new.Version != 2 {
|
||||
return nil, fmt.Errorf("Cannot apply v2->v3 state upgrade to " +
|
||||
"a state which is not version 2.")
|
||||
}
|
||||
|
||||
// Set the new version number
|
||||
new.Version = 3
|
||||
|
||||
// Change the counts for things which look like maps to use the %
|
||||
// syntax. Remove counts for empty collections - they will be added
|
||||
// back in later.
|
||||
for _, module := range new.Modules {
|
||||
for _, resource := range module.Resources {
|
||||
// Upgrade Primary
|
||||
if resource.Primary != nil {
|
||||
upgradeAttributesV2ToV3(resource.Primary)
|
||||
}
|
||||
|
||||
// Upgrade Deposed
|
||||
if resource.Deposed != nil {
|
||||
for _, deposed := range resource.Deposed {
|
||||
upgradeAttributesV2ToV3(deposed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new, nil
|
||||
}
|
||||
|
||||
func upgradeAttributesV2ToV3(instanceState *InstanceState) error {
|
||||
collectionKeyRegexp := regexp.MustCompile(`^(.*\.)#$`)
|
||||
collectionSubkeyRegexp := regexp.MustCompile(`^([^\.]+)\..*`)
|
||||
|
||||
// Identify the key prefix of anything which is a collection
|
||||
var collectionKeyPrefixes []string
|
||||
for key := range instanceState.Attributes {
|
||||
if submatches := collectionKeyRegexp.FindAllStringSubmatch(key, -1); len(submatches) > 0 {
|
||||
collectionKeyPrefixes = append(collectionKeyPrefixes, submatches[0][1])
|
||||
}
|
||||
}
|
||||
sort.Strings(collectionKeyPrefixes)
|
||||
|
||||
log.Printf("[STATE UPGRADE] Detected the following collections in state: %v", collectionKeyPrefixes)
|
||||
|
||||
// This could be rolled into fewer loops, but it is somewhat clearer this way, and will not
|
||||
// run very often.
|
||||
for _, prefix := range collectionKeyPrefixes {
|
||||
// First get the actual keys that belong to this prefix
|
||||
var potentialKeysMatching []string
|
||||
for key := range instanceState.Attributes {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
potentialKeysMatching = append(potentialKeysMatching, strings.TrimPrefix(key, prefix))
|
||||
}
|
||||
}
|
||||
sort.Strings(potentialKeysMatching)
|
||||
|
||||
var actualKeysMatching []string
|
||||
for _, key := range potentialKeysMatching {
|
||||
if submatches := collectionSubkeyRegexp.FindAllStringSubmatch(key, -1); len(submatches) > 0 {
|
||||
actualKeysMatching = append(actualKeysMatching, submatches[0][1])
|
||||
} else {
|
||||
if key != "#" {
|
||||
actualKeysMatching = append(actualKeysMatching, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
actualKeysMatching = uniqueSortedStrings(actualKeysMatching)
|
||||
|
||||
// Now inspect the keys in order to determine whether this is most likely to be
|
||||
// a map, list or set. There is room for error here, so we log in each case. If
|
||||
// there is no method of telling, we remove the key from the InstanceState in
|
||||
// order that it will be recreated. Again, this could be rolled into fewer loops
|
||||
// but we prefer clarity.
|
||||
|
||||
oldCountKey := fmt.Sprintf("%s#", prefix)
|
||||
|
||||
// First, detect "obvious" maps - which have non-numeric keys (mostly).
|
||||
hasNonNumericKeys := false
|
||||
for _, key := range actualKeysMatching {
|
||||
if _, err := strconv.Atoi(key); err != nil {
|
||||
hasNonNumericKeys = true
|
||||
}
|
||||
}
|
||||
if hasNonNumericKeys {
|
||||
newCountKey := fmt.Sprintf("%s%%", prefix)
|
||||
|
||||
instanceState.Attributes[newCountKey] = instanceState.Attributes[oldCountKey]
|
||||
delete(instanceState.Attributes, oldCountKey)
|
||||
log.Printf("[STATE UPGRADE] Detected %s as a map. Replaced count = %s",
|
||||
strings.TrimSuffix(prefix, "."), instanceState.Attributes[newCountKey])
|
||||
}
|
||||
|
||||
// Now detect empty collections and remove them from state.
|
||||
if len(actualKeysMatching) == 0 {
|
||||
delete(instanceState.Attributes, oldCountKey)
|
||||
log.Printf("[STATE UPGRADE] Detected %s as an empty collection. Removed from state.",
|
||||
strings.TrimSuffix(prefix, "."))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// uniqueSortedStrings removes duplicates from a slice of strings and returns
|
||||
// a sorted slice of the unique strings.
|
||||
func uniqueSortedStrings(input []string) []string {
|
||||
uniquemap := make(map[string]struct{})
|
||||
for _, str := range input {
|
||||
uniquemap[str] = struct{}{}
|
||||
}
|
||||
|
||||
output := make([]string, len(uniquemap))
|
||||
|
||||
i := 0
|
||||
for key := range uniquemap {
|
||||
output[i] = key
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
sort.Strings(output)
|
||||
return output
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
package terraform
|
||||
|
||||
// stateV1 keeps track of a snapshot state-of-the-world that Terraform
|
||||
// can use to keep track of what real world resources it is actually
|
||||
// managing.
|
||||
//
|
||||
// stateV1 is _only used for the purposes of backwards compatibility
|
||||
// and is no longer used in Terraform.
|
||||
//
|
||||
// For the upgrade process, see state_upgrade_v1_to_v2.go
|
||||
type stateV1 struct {
|
||||
// Version is the protocol version. "1" for a StateV1.
|
||||
Version int `json:"version"`
|
||||
|
||||
// Serial is incremented on any operation that modifies
|
||||
// the State file. It is used to detect potentially conflicting
|
||||
// updates.
|
||||
Serial int64 `json:"serial"`
|
||||
|
||||
// Remote is used to track the metadata required to
|
||||
// pull and push state files from a remote storage endpoint.
|
||||
Remote *remoteStateV1 `json:"remote,omitempty"`
|
||||
|
||||
// Modules contains all the modules in a breadth-first order
|
||||
Modules []*moduleStateV1 `json:"modules"`
|
||||
}
|
||||
|
||||
type remoteStateV1 struct {
|
||||
// Type controls the client we use for the remote state
|
||||
Type string `json:"type"`
|
||||
|
||||
// Config is used to store arbitrary configuration that
|
||||
// is type specific
|
||||
Config map[string]string `json:"config"`
|
||||
}
|
||||
|
||||
type moduleStateV1 struct {
|
||||
// Path is the import path from the root module. Modules imports are
|
||||
// always disjoint, so the path represents amodule tree
|
||||
Path []string `json:"path"`
|
||||
|
||||
// Outputs declared by the module and maintained for each module
|
||||
// even though only the root module technically needs to be kept.
|
||||
// This allows operators to inspect values at the boundaries.
|
||||
Outputs map[string]string `json:"outputs"`
|
||||
|
||||
// Resources is a mapping of the logically named resource to
|
||||
// the state of the resource. Each resource may actually have
|
||||
// N instances underneath, although a user only needs to think
|
||||
// about the 1:1 case.
|
||||
Resources map[string]*resourceStateV1 `json:"resources"`
|
||||
|
||||
// Dependencies are a list of things that this module relies on
|
||||
// existing to remain intact. For example: an module may depend
|
||||
// on a VPC ID given by an aws_vpc resource.
|
||||
//
|
||||
// Terraform uses this information to build valid destruction
|
||||
// orders and to warn the user if they're destroying a module that
|
||||
// another resource depends on.
|
||||
//
|
||||
// Things can be put into this list that may not be managed by
|
||||
// Terraform. If Terraform doesn't find a matching ID in the
|
||||
// overall state, then it assumes it isn't managed and doesn't
|
||||
// worry about it.
|
||||
Dependencies []string `json:"depends_on,omitempty"`
|
||||
}
|
||||
|
||||
type resourceStateV1 struct {
|
||||
// This is filled in and managed by Terraform, and is the resource
|
||||
// type itself such as "mycloud_instance". If a resource provider sets
|
||||
// this value, it won't be persisted.
|
||||
Type string `json:"type"`
|
||||
|
||||
// Dependencies are a list of things that this resource relies on
|
||||
// existing to remain intact. For example: an AWS instance might
|
||||
// depend on a subnet (which itself might depend on a VPC, and so
|
||||
// on).
|
||||
//
|
||||
// Terraform uses this information to build valid destruction
|
||||
// orders and to warn the user if they're destroying a resource that
|
||||
// another resource depends on.
|
||||
//
|
||||
// Things can be put into this list that may not be managed by
|
||||
// Terraform. If Terraform doesn't find a matching ID in the
|
||||
// overall state, then it assumes it isn't managed and doesn't
|
||||
// worry about it.
|
||||
Dependencies []string `json:"depends_on,omitempty"`
|
||||
|
||||
// Primary is the current active instance for this resource.
|
||||
// It can be replaced but only after a successful creation.
|
||||
// This is the instances on which providers will act.
|
||||
Primary *instanceStateV1 `json:"primary"`
|
||||
|
||||
// Tainted is used to track any underlying instances that
|
||||
// have been created but are in a bad or unknown state and
|
||||
// need to be cleaned up subsequently. In the
|
||||
// standard case, there is only at most a single instance.
|
||||
// However, in pathological cases, it is possible for the number
|
||||
// of instances to accumulate.
|
||||
Tainted []*instanceStateV1 `json:"tainted,omitempty"`
|
||||
|
||||
// Deposed is used in the mechanics of CreateBeforeDestroy: the existing
|
||||
// Primary is Deposed to get it out of the way for the replacement Primary to
|
||||
// be created by Apply. If the replacement Primary creates successfully, the
|
||||
// Deposed instance is cleaned up. If there were problems creating the
|
||||
// replacement, the instance remains in the Deposed list so it can be
|
||||
// destroyed in a future run. Functionally, Deposed instances are very
|
||||
// similar to Tainted instances in that Terraform is only tracking them in
|
||||
// order to remember to destroy them.
|
||||
Deposed []*instanceStateV1 `json:"deposed,omitempty"`
|
||||
|
||||
// Provider is used when a resource is connected to a provider with an alias.
|
||||
// If this string is empty, the resource is connected to the default provider,
|
||||
// e.g. "aws_instance" goes with the "aws" provider.
|
||||
// If the resource block contained a "provider" key, that value will be set here.
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
type instanceStateV1 struct {
|
||||
// A unique ID for this resource. This is opaque to Terraform
|
||||
// and is only meant as a lookup mechanism for the providers.
|
||||
ID string `json:"id"`
|
||||
|
||||
// Attributes are basic information about the resource. Any keys here
|
||||
// are accessible in variable format within Terraform configurations:
|
||||
// ${resourcetype.name.attribute}.
|
||||
Attributes map[string]string `json:"attributes,omitempty"`
|
||||
|
||||
// Ephemeral is used to store any state associated with this instance
|
||||
// that is necessary for the Terraform run to complete, but is not
|
||||
// persisted to a state file.
|
||||
Ephemeral ephemeralStateV1 `json:"-"`
|
||||
|
||||
// Meta is a simple K/V map that is persisted to the State but otherwise
|
||||
// ignored by Terraform core. It's meant to be used for accounting by
|
||||
// external client code.
|
||||
Meta map[string]string `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
type ephemeralStateV1 struct {
|
||||
// ConnInfo is used for the providers to export information which is
|
||||
// used to connect to the resource for provisioning. For example,
|
||||
// this could contain SSH or WinRM credentials.
|
||||
ConnInfo map[string]string `json:"-"`
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -68,12 +68,12 @@ func TestReferenceTransformer_path(t *testing.T) {
|
|||
})
|
||||
g.Add(&graphNodeRefParentTest{
|
||||
NameValue: "child.A",
|
||||
PathValue: []string{"root", "child"},
|
||||
PathValue: addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "child"}},
|
||||
Names: []string{"A"},
|
||||
})
|
||||
g.Add(&graphNodeRefChildTest{
|
||||
NameValue: "child.B",
|
||||
PathValue: []string{"root", "child"},
|
||||
PathValue: addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "child"}},
|
||||
Refs: []string{"A"},
|
||||
})
|
||||
|
||||
|
@ -214,7 +214,7 @@ func TestReferenceMapReferences(t *testing.T) {
|
|||
|
||||
type graphNodeRefParentTest struct {
|
||||
NameValue string
|
||||
PathValue []string
|
||||
PathValue addrs.ModuleInstance
|
||||
Names []string
|
||||
}
|
||||
|
||||
|
@ -233,16 +233,16 @@ func (n *graphNodeRefParentTest) ReferenceableAddrs() []addrs.Referenceable {
|
|||
}
|
||||
|
||||
func (n *graphNodeRefParentTest) Path() addrs.ModuleInstance {
|
||||
return normalizeModulePath(n.PathValue)
|
||||
return n.PathValue
|
||||
}
|
||||
|
||||
func (n *graphNodeRefParentTest) ModulePath() addrs.Module {
|
||||
return normalizeModulePath(n.PathValue).Module()
|
||||
return n.PathValue.Module()
|
||||
}
|
||||
|
||||
type graphNodeRefChildTest struct {
|
||||
NameValue string
|
||||
PathValue []string
|
||||
PathValue addrs.ModuleInstance
|
||||
Refs []string
|
||||
}
|
||||
|
||||
|
@ -263,11 +263,11 @@ func (n *graphNodeRefChildTest) References() []*addrs.Reference {
|
|||
}
|
||||
|
||||
func (n *graphNodeRefChildTest) Path() addrs.ModuleInstance {
|
||||
return normalizeModulePath(n.PathValue)
|
||||
return n.PathValue
|
||||
}
|
||||
|
||||
func (n *graphNodeRefChildTest) ModulePath() addrs.Module {
|
||||
return normalizeModulePath(n.PathValue).Module()
|
||||
return n.PathValue.Module()
|
||||
}
|
||||
|
||||
type graphNodeFakeResourceInstance struct {
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// TestReadUpgradeStateV1toV3 tests the state upgrade process from the V1 state
|
||||
// to the current version, and needs editing each time. This means it tests the
|
||||
// entire pipeline of upgrades (which migrate version to version).
|
||||
func TestReadUpgradeStateV1toV3(t *testing.T) {
|
||||
// ReadState should transparently detect the old version but will upgrade
|
||||
// it on Write.
|
||||
actual, err := ReadState(strings.NewReader(testV1State))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := WriteState(actual, buf); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if actual.Version != 3 {
|
||||
t.Fatalf("bad: State version not incremented; is %d", actual.Version)
|
||||
}
|
||||
|
||||
roundTripped, err := ReadState(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, roundTripped) {
|
||||
t.Logf("actual:\n%#v", actual)
|
||||
t.Fatalf("roundTripped:\n%#v", roundTripped)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadUpgradeStateV1toV3_outputs(t *testing.T) {
|
||||
// ReadState should transparently detect the old version but will upgrade
|
||||
// it on Write.
|
||||
actual, err := ReadState(strings.NewReader(testV1StateWithOutputs))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := WriteState(actual, buf); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if actual.Version != 3 {
|
||||
t.Fatalf("bad: State version not incremented; is %d", actual.Version)
|
||||
}
|
||||
|
||||
roundTripped, err := ReadState(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, roundTripped) {
|
||||
spew.Config.DisableMethods = true
|
||||
t.Fatalf("bad:\n%s\n\nround tripped:\n%s\n", spew.Sdump(actual), spew.Sdump(roundTripped))
|
||||
spew.Config.DisableMethods = false
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrading the state should not lose empty module Outputs and Resources maps
|
||||
// during upgrade. The init for a new module initializes new maps, so we may not
|
||||
// be expecting to check for a nil map.
|
||||
func TestReadUpgradeStateV1toV3_emptyState(t *testing.T) {
|
||||
// ReadState should transparently detect the old version but will upgrade
|
||||
// it on Write.
|
||||
orig, err := ReadStateV1([]byte(testV1EmptyState))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
stateV2, err := upgradeStateV1ToV2(orig)
|
||||
if err != nil {
|
||||
t.Fatalf("error attempting upgradeStateV1ToV2: %s", err)
|
||||
}
|
||||
|
||||
for _, m := range stateV2.Modules {
|
||||
if m.Resources == nil {
|
||||
t.Fatal("V1 to V2 upgrade lost module.Resources")
|
||||
}
|
||||
if m.Outputs == nil {
|
||||
t.Fatal("V1 to V2 upgrade lost module.Outputs")
|
||||
}
|
||||
}
|
||||
|
||||
stateV3, err := upgradeStateV2ToV3(stateV2)
|
||||
if err != nil {
|
||||
t.Fatalf("error attempting to upgradeStateV2ToV3: %s", err)
|
||||
}
|
||||
for _, m := range stateV3.Modules {
|
||||
if m.Resources == nil {
|
||||
t.Fatal("V2 to V3 upgrade lost module.Resources")
|
||||
}
|
||||
if m.Outputs == nil {
|
||||
t.Fatal("V2 to V3 upgrade lost module.Outputs")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const testV1EmptyState = `{
|
||||
"version": 1,
|
||||
"serial": 0,
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": {},
|
||||
"resources": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
const testV1State = `{
|
||||
"version": 1,
|
||||
"serial": 9,
|
||||
"remote": {
|
||||
"type": "http",
|
||||
"config": {
|
||||
"url": "http://my-cool-server.com/"
|
||||
}
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": null,
|
||||
"resources": {
|
||||
"foo": {
|
||||
"type": "",
|
||||
"primary": {
|
||||
"id": "bar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"depends_on": [
|
||||
"aws_instance.bar"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
const testV1StateWithOutputs = `{
|
||||
"version": 1,
|
||||
"serial": 9,
|
||||
"remote": {
|
||||
"type": "http",
|
||||
"config": {
|
||||
"url": "http://my-cool-server.com/"
|
||||
}
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": {
|
||||
"foo": "bar",
|
||||
"baz": "foo"
|
||||
},
|
||||
"resources": {
|
||||
"foo": {
|
||||
"type": "",
|
||||
"primary": {
|
||||
"id": "bar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"depends_on": [
|
||||
"aws_instance.bar"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
|
@ -1,202 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestReadUpgradeStateV2toV3 tests the state upgrade process from the V2 state
|
||||
// to the current version, and needs editing each time. This means it tests the
|
||||
// entire pipeline of upgrades (which migrate version to version).
|
||||
func TestReadUpgradeStateV2toV3(t *testing.T) {
|
||||
// ReadState should transparently detect the old version but will upgrade
|
||||
// it on Write.
|
||||
upgraded, err := ReadState(strings.NewReader(testV2State))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := WriteState(upgraded, buf); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if upgraded.Version != 3 {
|
||||
t.Fatalf("bad: State version not incremented; is %d", upgraded.Version)
|
||||
}
|
||||
|
||||
// For this test we cannot assert that we match the round trip because an
|
||||
// empty map has been removed from state. Instead we make assertions against
|
||||
// some of the key fields in the _upgraded_ state.
|
||||
instanceState, ok := upgraded.RootModule().Resources["test_resource.main"]
|
||||
if !ok {
|
||||
t.Fatalf("Instance state for test_resource.main was removed from state during upgrade")
|
||||
}
|
||||
|
||||
primary := instanceState.Primary
|
||||
if primary == nil {
|
||||
t.Fatalf("Primary instance was removed from state for test_resource.main")
|
||||
}
|
||||
|
||||
// Non-empty computed map is moved from .# to .%
|
||||
if _, ok := primary.Attributes["computed_map.#"]; ok {
|
||||
t.Fatalf("Count was not upgraded from .# to .%% for computed_map")
|
||||
}
|
||||
if count, ok := primary.Attributes["computed_map.%"]; !ok || count != "1" {
|
||||
t.Fatalf("Count was not in .%% or was not 2 for computed_map")
|
||||
}
|
||||
|
||||
// list_of_map top level retains .#
|
||||
if count, ok := primary.Attributes["list_of_map.#"]; !ok || count != "2" {
|
||||
t.Fatal("Count for list_of_map was migrated incorrectly")
|
||||
}
|
||||
|
||||
// list_of_map.0 is moved from .# to .%
|
||||
if _, ok := primary.Attributes["list_of_map.0.#"]; ok {
|
||||
t.Fatalf("Count was not upgraded from .# to .%% for list_of_map.0")
|
||||
}
|
||||
if count, ok := primary.Attributes["list_of_map.0.%"]; !ok || count != "2" {
|
||||
t.Fatalf("Count was not in .%% or was not 2 for list_of_map.0")
|
||||
}
|
||||
|
||||
// list_of_map.1 is moved from .# to .%
|
||||
if _, ok := primary.Attributes["list_of_map.1.#"]; ok {
|
||||
t.Fatalf("Count was not upgraded from .# to .%% for list_of_map.1")
|
||||
}
|
||||
if count, ok := primary.Attributes["list_of_map.1.%"]; !ok || count != "2" {
|
||||
t.Fatalf("Count was not in .%% or was not 2 for list_of_map.1")
|
||||
}
|
||||
|
||||
// map is moved from .# to .%
|
||||
if _, ok := primary.Attributes["map.#"]; ok {
|
||||
t.Fatalf("Count was not upgraded from .# to .%% for map")
|
||||
}
|
||||
if count, ok := primary.Attributes["map.%"]; !ok || count != "2" {
|
||||
t.Fatalf("Count was not in .%% or was not 2 for map")
|
||||
}
|
||||
|
||||
// optional_computed_map should be removed from state
|
||||
if _, ok := primary.Attributes["optional_computed_map"]; ok {
|
||||
t.Fatal("optional_computed_map was not removed from state")
|
||||
}
|
||||
|
||||
// required_map is moved from .# to .%
|
||||
if _, ok := primary.Attributes["required_map.#"]; ok {
|
||||
t.Fatalf("Count was not upgraded from .# to .%% for required_map")
|
||||
}
|
||||
if count, ok := primary.Attributes["required_map.%"]; !ok || count != "3" {
|
||||
t.Fatalf("Count was not in .%% or was not 3 for map")
|
||||
}
|
||||
|
||||
// computed_list keeps .#
|
||||
if count, ok := primary.Attributes["computed_list.#"]; !ok || count != "2" {
|
||||
t.Fatal("Count was migrated incorrectly for computed_list")
|
||||
}
|
||||
|
||||
// computed_set keeps .#
|
||||
if count, ok := primary.Attributes["computed_set.#"]; !ok || count != "2" {
|
||||
t.Fatal("Count was migrated incorrectly for computed_set")
|
||||
}
|
||||
if val, ok := primary.Attributes["computed_set.2337322984"]; !ok || val != "setval1" {
|
||||
t.Fatal("Set item for computed_set.2337322984 changed or moved")
|
||||
}
|
||||
if val, ok := primary.Attributes["computed_set.307881554"]; !ok || val != "setval2" {
|
||||
t.Fatal("Set item for computed_set.307881554 changed or moved")
|
||||
}
|
||||
|
||||
// string properties are unaffected
|
||||
if val, ok := primary.Attributes["id"]; !ok || val != "testId" {
|
||||
t.Fatal("id was not set correctly after migration")
|
||||
}
|
||||
}
|
||||
|
||||
const testV2State = `{
|
||||
"version": 2,
|
||||
"terraform_version": "0.7.0",
|
||||
"serial": 2,
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": {
|
||||
"computed_map": {
|
||||
"sensitive": false,
|
||||
"type": "map",
|
||||
"value": {
|
||||
"key1": "value1"
|
||||
}
|
||||
},
|
||||
"computed_set": {
|
||||
"sensitive": false,
|
||||
"type": "list",
|
||||
"value": [
|
||||
"setval1",
|
||||
"setval2"
|
||||
]
|
||||
},
|
||||
"map": {
|
||||
"sensitive": false,
|
||||
"type": "map",
|
||||
"value": {
|
||||
"key": "test",
|
||||
"test": "test"
|
||||
}
|
||||
},
|
||||
"set": {
|
||||
"sensitive": false,
|
||||
"type": "list",
|
||||
"value": [
|
||||
"test1",
|
||||
"test2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"test_resource.main": {
|
||||
"type": "test_resource",
|
||||
"primary": {
|
||||
"id": "testId",
|
||||
"attributes": {
|
||||
"computed_list.#": "2",
|
||||
"computed_list.0": "listval1",
|
||||
"computed_list.1": "listval2",
|
||||
"computed_map.#": "1",
|
||||
"computed_map.key1": "value1",
|
||||
"computed_read_only": "value_from_api",
|
||||
"computed_read_only_force_new": "value_from_api",
|
||||
"computed_set.#": "2",
|
||||
"computed_set.2337322984": "setval1",
|
||||
"computed_set.307881554": "setval2",
|
||||
"id": "testId",
|
||||
"list_of_map.#": "2",
|
||||
"list_of_map.0.#": "2",
|
||||
"list_of_map.0.key1": "value1",
|
||||
"list_of_map.0.key2": "value2",
|
||||
"list_of_map.1.#": "2",
|
||||
"list_of_map.1.key3": "value3",
|
||||
"list_of_map.1.key4": "value4",
|
||||
"map.#": "2",
|
||||
"map.key": "test",
|
||||
"map.test": "test",
|
||||
"map_that_look_like_set.#": "2",
|
||||
"map_that_look_like_set.12352223": "hello",
|
||||
"map_that_look_like_set.36234341": "world",
|
||||
"optional_computed_map.#": "0",
|
||||
"required": "Hello World",
|
||||
"required_map.#": "3",
|
||||
"required_map.key1": "value1",
|
||||
"required_map.key2": "value2",
|
||||
"required_map.key3": "value3",
|
||||
"set.#": "2",
|
||||
"set.2326977762": "test1",
|
||||
"set.331058520": "test2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
Loading…
Reference in New Issue