From a14fd0344cf7a784482b400171910e681deafc54 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 1 Nov 2017 18:34:18 -0400 Subject: [PATCH] WIP reference providers by full name This turned out to be a big messy commit, since the way providers are referenced is tightly coupled throughout the code. That starts to unify how providers are referenced, using the format output node Name method. Add a new field to the internal resource data types called ResolvedProvider. This is set by a new setter method SetProvider when a resource is connected to a provider during graph creation. This allows us to later lookup the provider instance a resource is connected to, without requiring it to have the same module path. The InitProvider context method now takes 2 arguments, one if the provider type and the second is the full name of the provider. While the provider type could still be parsed from the full name, this makes it more explicit and, and changes to the name format won't effect this code. --- terraform/context_apply_test.go | 2 +- terraform/context_plan_test.go | 7 +- terraform/eval_context.go | 4 +- terraform/eval_context_builtin.go | 12 +- terraform/eval_context_mock.go | 2 +- terraform/eval_provider.go | 6 +- terraform/evaltree_provider.go | 27 ++-- terraform/graph_builder_import.go | 7 + terraform/graph_builder_refresh.go | 7 + terraform/node_data_refresh.go | 3 +- terraform/node_provider.go | 2 +- terraform/node_provider_abstract.go | 14 +- terraform/node_resource_abstract.go | 7 + terraform/node_resource_apply.go | 6 +- terraform/node_resource_destroy.go | 7 +- terraform/node_resource_plan.go | 2 + terraform/node_resource_plan_instance.go | 4 +- terraform/node_resource_refresh.go | 5 +- terraform/node_resource_validate.go | 3 +- terraform/transform_deposed.go | 36 +++-- terraform/transform_import_state.go | 43 +++--- terraform/transform_provider.go | 170 +++++++++++------------ terraform/transform_provider_test.go | 2 +- terraform/transform_reference.go | 7 +- terraform/transform_resource_count.go | 4 +- 25 files changed, 218 insertions(+), 171 deletions(-) diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 22d7d6db3..0f1fd47a6 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -861,7 +861,7 @@ func TestContext2Apply_createBeforeDestroy(t *testing.T) { actual := strings.TrimSpace(state.String()) expected := strings.TrimSpace(testTerraformApplyCreateBeforeStr) if actual != expected { - t.Fatalf("bad: \n%s", actual) + t.Fatalf("expected:\n%s\ngot:\n%s", expected, actual) } } diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 7dc860bb5..3eb213028 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -687,13 +687,10 @@ func TestContext2Plan_moduleProviderDefaultsVar(t *testing.T) { } expected := []string{ - "root\n", - // this test originally verified that a parent provider config can - // partially override a child. That's no longer the case, so the child - // config is used in its entirety here. - //"root\nchild\n", "child\nchild\n", + "root\n", } + sort.Strings(calls) if !reflect.DeepEqual(calls, expected) { t.Fatalf("expected:\n%#v\ngot:\n%#v\n", expected, calls) } diff --git a/terraform/eval_context.go b/terraform/eval_context.go index 655916fc3..86481dedb 100644 --- a/terraform/eval_context.go +++ b/terraform/eval_context.go @@ -22,11 +22,11 @@ type EvalContext interface { // Input is the UIInput object for interacting with the UI. Input() UIInput - // InitProvider initializes the provider with the given name and + // InitProvider initializes the provider with the given type and name, and // returns the implementation of the resource provider or an error. // // It is an error to initialize the same provider more than once. - InitProvider(string) (ResourceProvider, error) + InitProvider(typ string, name string) (ResourceProvider, error) // Provider gets the provider instance with the given name (already // initialized) or returns nil if the provider isn't initialized. diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index 3753beec7..193421b78 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "strings" "sync" "github.com/hashicorp/terraform/config" @@ -79,12 +78,12 @@ func (ctx *BuiltinEvalContext) Input() UIInput { return ctx.InputValue } -func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error) { +func (ctx *BuiltinEvalContext) InitProvider(typeName, name string) (ResourceProvider, error) { ctx.once.Do(ctx.init) // If we already initialized, it is an error - if p := ctx.Provider(n); p != nil { - return nil, fmt.Errorf("Provider '%s' already initialized", n) + if p := ctx.Provider(name); p != nil { + return nil, fmt.Errorf("Provider '%s' already initialized", name) } // Warning: make sure to acquire these locks AFTER the call to Provider @@ -92,13 +91,12 @@ func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error) ctx.ProviderLock.Lock() defer ctx.ProviderLock.Unlock() - typeName := strings.SplitN(n, ".", 2)[0] - p, err := ctx.Components.ResourceProvider(typeName, n) + p, err := ctx.Components.ResourceProvider(typeName, name) if err != nil { return nil, err } - ctx.ProviderCache[n] = p + ctx.ProviderCache[name] = p return p, nil } diff --git a/terraform/eval_context_mock.go b/terraform/eval_context_mock.go index a148a54bf..646451795 100644 --- a/terraform/eval_context_mock.go +++ b/terraform/eval_context_mock.go @@ -107,7 +107,7 @@ func (c *MockEvalContext) Input() UIInput { return c.InputInput } -func (c *MockEvalContext) InitProvider(n string) (ResourceProvider, error) { +func (c *MockEvalContext) InitProvider(t, n string) (ResourceProvider, error) { c.InitProviderCalled = true c.InitProviderName = n return c.InitProviderProvider, c.InitProviderError diff --git a/terraform/eval_provider.go b/terraform/eval_provider.go index 328d72b5e..61f6ff941 100644 --- a/terraform/eval_provider.go +++ b/terraform/eval_provider.go @@ -52,11 +52,12 @@ func (n *EvalConfigProvider) Eval(ctx EvalContext) (interface{}, error) { // and returns nothing. The provider can be retrieved again with the // EvalGetProvider node. type EvalInitProvider struct { - Name string + TypeName string + Name string } func (n *EvalInitProvider) Eval(ctx EvalContext) (interface{}, error) { - return ctx.InitProvider(n.Name) + return ctx.InitProvider(n.TypeName, n.Name) } // EvalCloseProvider is an EvalNode implementation that closes provider @@ -129,6 +130,7 @@ func (n *EvalInputProvider) Eval(ctx EvalContext) (interface{}, error) { } } } + ctx.SetProviderInput(n.Name, confMap) return nil, nil diff --git a/terraform/evaltree_provider.go b/terraform/evaltree_provider.go index 55e5c5258..0c3da48f0 100644 --- a/terraform/evaltree_provider.go +++ b/terraform/evaltree_provider.go @@ -1,17 +1,24 @@ package terraform import ( + "strings" + "github.com/hashicorp/terraform/config" ) // ProviderEvalTree returns the evaluation tree for initializing and // configuring providers. -func ProviderEvalTree(n string, config *config.ProviderConfig) EvalNode { +func ProviderEvalTree(n *NodeApplyableProvider, config *config.ProviderConfig) EvalNode { var provider ResourceProvider var resourceConfig *ResourceConfig + typeName := strings.SplitN(n.NameValue, ".", 2)[0] + seq := make([]EvalNode, 0, 5) - seq = append(seq, &EvalInitProvider{Name: n}) + seq = append(seq, &EvalInitProvider{ + TypeName: typeName, + Name: n.Name(), + }) // Input stuff seq = append(seq, &EvalOpFilter{ @@ -19,7 +26,7 @@ func ProviderEvalTree(n string, config *config.ProviderConfig) EvalNode { Node: &EvalSequence{ Nodes: []EvalNode{ &EvalGetProvider{ - Name: n, + Name: n.Name(), Output: &provider, }, &EvalInterpolateProvider{ @@ -27,12 +34,12 @@ func ProviderEvalTree(n string, config *config.ProviderConfig) EvalNode { Output: &resourceConfig, }, &EvalBuildProviderConfig{ - Provider: n, + Provider: n.NameValue, Config: &resourceConfig, Output: &resourceConfig, }, &EvalInputProvider{ - Name: n, + Name: n.NameValue, Provider: &provider, Config: &resourceConfig, }, @@ -45,7 +52,7 @@ func ProviderEvalTree(n string, config *config.ProviderConfig) EvalNode { Node: &EvalSequence{ Nodes: []EvalNode{ &EvalGetProvider{ - Name: n, + Name: n.Name(), Output: &provider, }, &EvalInterpolateProvider{ @@ -53,7 +60,7 @@ func ProviderEvalTree(n string, config *config.ProviderConfig) EvalNode { Output: &resourceConfig, }, &EvalBuildProviderConfig{ - Provider: n, + Provider: n.NameValue, Config: &resourceConfig, Output: &resourceConfig, }, @@ -71,7 +78,7 @@ func ProviderEvalTree(n string, config *config.ProviderConfig) EvalNode { Node: &EvalSequence{ Nodes: []EvalNode{ &EvalGetProvider{ - Name: n, + Name: n.Name(), Output: &provider, }, &EvalInterpolateProvider{ @@ -79,7 +86,7 @@ func ProviderEvalTree(n string, config *config.ProviderConfig) EvalNode { Output: &resourceConfig, }, &EvalBuildProviderConfig{ - Provider: n, + Provider: n.NameValue, Config: &resourceConfig, Output: &resourceConfig, }, @@ -94,7 +101,7 @@ func ProviderEvalTree(n string, config *config.ProviderConfig) EvalNode { Node: &EvalSequence{ Nodes: []EvalNode{ &EvalConfigProvider{ - Provider: n, + Provider: n.Name(), Config: &resourceConfig, }, }, diff --git a/terraform/graph_builder_import.go b/terraform/graph_builder_import.go index 7070c59e4..e064ae629 100644 --- a/terraform/graph_builder_import.go +++ b/terraform/graph_builder_import.go @@ -52,6 +52,13 @@ func (b *ImportGraphBuilder) Steps() []GraphTransformer { // Add the import steps &ImportStateTransformer{Targets: b.ImportTargets}, + // add configured providers + &ProviderConfigTransformer{ + Module: b.Module, + Providers: b.Providers, + Concrete: concreteProvider, + }, + // Provider-related transformations &MissingProviderTransformer{Providers: b.Providers, Concrete: concreteProvider}, &ProviderTransformer{}, diff --git a/terraform/graph_builder_refresh.go b/terraform/graph_builder_refresh.go index beb5a4a44..da506170b 100644 --- a/terraform/graph_builder_refresh.go +++ b/terraform/graph_builder_refresh.go @@ -126,6 +126,13 @@ func (b *RefreshGraphBuilder) Steps() []GraphTransformer { // Add root variables &RootVariableTransformer{Module: b.Module}, + // add configured providers + &ProviderConfigTransformer{ + Module: b.Module, + Providers: b.Providers, + Concrete: concreteProvider, + }, + // Create all the providers &MissingProviderTransformer{Providers: b.Providers, Concrete: concreteProvider}, &ProviderTransformer{}, diff --git a/terraform/node_data_refresh.go b/terraform/node_data_refresh.go index 45129b3cb..15d9b8f9c 100644 --- a/terraform/node_data_refresh.go +++ b/terraform/node_data_refresh.go @@ -27,6 +27,7 @@ func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, er concreteResource := func(a *NodeAbstractResource) dag.Vertex { // Add the config and state since we don't do that via transforms a.Config = n.Config + a.ResolvedProvider = n.ResolvedProvider return &NodeRefreshableDataResourceInstance{ NodeAbstractResource: a, @@ -185,7 +186,7 @@ func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode { // provider configurations that need this data during // refresh/plan. &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, diff --git a/terraform/node_provider.go b/terraform/node_provider.go index 8e2c176fa..2071ab168 100644 --- a/terraform/node_provider.go +++ b/terraform/node_provider.go @@ -7,5 +7,5 @@ type NodeApplyableProvider struct { // GraphNodeEvalable func (n *NodeApplyableProvider) EvalTree() EvalNode { - return ProviderEvalTree(n.NameValue, n.ProviderConfig()) + return ProviderEvalTree(n, n.ProviderConfig()) } diff --git a/terraform/node_provider_abstract.go b/terraform/node_provider_abstract.go index 4a2c00087..3230558e8 100644 --- a/terraform/node_provider_abstract.go +++ b/terraform/node_provider_abstract.go @@ -24,13 +24,17 @@ type NodeAbstractProvider struct { Config *config.ProviderConfig } -func (n *NodeAbstractProvider) Name() string { - result := fmt.Sprintf("provider.%s", n.NameValue) - if len(n.PathValue) > 1 { - result = fmt.Sprintf("%s.%s", modulePrefixStr(n.PathValue), result) +func ResolveProviderName(name string, path []string) string { + name = fmt.Sprintf("provider.%s", name) + if len(path) > 1 { + name = fmt.Sprintf("%s.%s", modulePrefixStr(path), name) } - return result + return name +} + +func (n *NodeAbstractProvider) Name() string { + return ResolveProviderName(n.NameValue, n.PathValue) } // GraphNodeSubPath diff --git a/terraform/node_resource_abstract.go b/terraform/node_resource_abstract.go index 50bb70792..e46c32009 100644 --- a/terraform/node_resource_abstract.go +++ b/terraform/node_resource_abstract.go @@ -33,6 +33,9 @@ type NodeAbstractResource struct { ResourceState *ResourceState // ResourceState is the ResourceState for this Targets []ResourceAddress // Set from GraphNodeTargetable + + // The address of the provider this resource will use + ResolvedProvider string } func (n *NodeAbstractResource) Name() string { @@ -170,6 +173,10 @@ func (n *NodeAbstractResource) StateReferences() []string { return deps } +func (n *NodeAbstractResource) SetProvider(p string) { + n.ResolvedProvider = p +} + // GraphNodeProviderConsumer func (n *NodeAbstractResource) ProvidedBy() []string { // If we have a config we prefer that above all else diff --git a/terraform/node_resource_apply.go b/terraform/node_resource_apply.go index 3599782b9..807b1f416 100644 --- a/terraform/node_resource_apply.go +++ b/terraform/node_resource_apply.go @@ -135,7 +135,7 @@ func (n *NodeApplyableResource) evalTreeDataResource( }, &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, @@ -242,7 +242,7 @@ func (n *NodeApplyableResource) evalTreeManagedResource( Output: &resourceConfig, }, &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, &EvalReadState{ @@ -283,7 +283,7 @@ func (n *NodeApplyableResource) evalTreeManagedResource( }, &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, &EvalReadState{ diff --git a/terraform/node_resource_destroy.go b/terraform/node_resource_destroy.go index c2efd2c38..cffb9ae60 100644 --- a/terraform/node_resource_destroy.go +++ b/terraform/node_resource_destroy.go @@ -102,8 +102,9 @@ func (n *NodeDestroyResource) DynamicExpand(ctx EvalContext) (*Graph, error) { // We want deposed resources in the state to be destroyed steps = append(steps, &DeposedTransformer{ - State: state, - View: n.Addr.stateId(), + State: state, + View: n.Addr.stateId(), + ResolvedProvider: n.ResolvedProvider, }) // Target @@ -188,7 +189,7 @@ func (n *NodeDestroyResource) EvalTree() EvalNode { &EvalInstanceInfo{Info: info}, &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, &EvalReadState{ diff --git a/terraform/node_resource_plan.go b/terraform/node_resource_plan.go index 52bbf88a1..1afae7a04 100644 --- a/terraform/node_resource_plan.go +++ b/terraform/node_resource_plan.go @@ -27,6 +27,7 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) { concreteResource := func(a *NodeAbstractResource) dag.Vertex { // Add the config and state since we don't do that via transforms a.Config = n.Config + a.ResolvedProvider = n.ResolvedProvider return &NodePlannableResourceInstance{ NodeAbstractResource: a, @@ -37,6 +38,7 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) { concreteResourceOrphan := func(a *NodeAbstractResource) dag.Vertex { // Add the config and state since we don't do that via transforms a.Config = n.Config + a.ResolvedProvider = n.ResolvedProvider return &NodePlannableResourceOrphan{ NodeAbstractResource: a, diff --git a/terraform/node_resource_plan_instance.go b/terraform/node_resource_plan_instance.go index b52956908..25a76a99f 100644 --- a/terraform/node_resource_plan_instance.go +++ b/terraform/node_resource_plan_instance.go @@ -97,7 +97,7 @@ func (n *NodePlannableResourceInstance) evalTreeDataResource( }, &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, @@ -143,7 +143,7 @@ func (n *NodePlannableResourceInstance) evalTreeManagedResource( Output: &resourceConfig, }, &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, // Re-run validation to catch any errors we missed, e.g. type diff --git a/terraform/node_resource_refresh.go b/terraform/node_resource_refresh.go index cd4fe9201..d504e7de4 100644 --- a/terraform/node_resource_refresh.go +++ b/terraform/node_resource_refresh.go @@ -30,6 +30,7 @@ func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, concreteResource := func(a *NodeAbstractResource) dag.Vertex { // Add the config and state since we don't do that via transforms a.Config = n.Config + a.ResolvedProvider = n.ResolvedProvider return &NodeRefreshableManagedResourceInstance{ NodeAbstractResource: a, @@ -149,7 +150,7 @@ func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalN return &EvalSequence{ Nodes: []EvalNode{ &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, &EvalReadState{ @@ -220,7 +221,7 @@ func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResourceNoState( Output: &resourceConfig, }, &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, // Re-run validation to catch any errors we missed, e.g. type diff --git a/terraform/node_resource_validate.go b/terraform/node_resource_validate.go index f528f24b1..0df223d9e 100644 --- a/terraform/node_resource_validate.go +++ b/terraform/node_resource_validate.go @@ -39,6 +39,7 @@ func (n *NodeValidatableResource) DynamicExpand(ctx EvalContext) (*Graph, error) concreteResource := func(a *NodeAbstractResource) dag.Vertex { // Add the config and state since we don't do that via transforms a.Config = n.Config + a.ResolvedProvider = n.ResolvedProvider return &NodeValidatableResourceInstance{ NodeAbstractResource: a, @@ -108,7 +109,7 @@ func (n *NodeValidatableResourceInstance) EvalTree() EvalNode { Config: &n.Config.RawConfig, }, &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, &EvalInterpolate{ diff --git a/terraform/transform_deposed.go b/terraform/transform_deposed.go index 2148cef47..fd920fbda 100644 --- a/terraform/transform_deposed.go +++ b/terraform/transform_deposed.go @@ -12,6 +12,9 @@ type DeposedTransformer struct { // View, if non-empty, is the ModuleState.View used around the state // to find deposed resources. View string + + // The provider used by the resourced which were deposed + ResolvedProvider string } func (t *DeposedTransformer) Transform(g *Graph) error { @@ -33,14 +36,16 @@ func (t *DeposedTransformer) Transform(g *Graph) error { if len(rs.Deposed) == 0 { continue } + deposed := rs.Deposed for i, _ := range deposed { g.Add(&graphNodeDeposedResource{ - Index: i, - ResourceName: k, - ResourceType: rs.Type, - Provider: rs.Provider, + Index: i, + ResourceName: k, + ResourceType: rs.Type, + ProviderName: rs.Provider, + ResolvedProvider: t.ResolvedProvider, }) } } @@ -50,10 +55,11 @@ func (t *DeposedTransformer) Transform(g *Graph) error { // graphNodeDeposedResource is the graph vertex representing a deposed resource. type graphNodeDeposedResource struct { - Index int - ResourceName string - ResourceType string - Provider string + Index int + ResourceName string + ResourceType string + ProviderName string + ResolvedProvider string } func (n *graphNodeDeposedResource) Name() string { @@ -61,7 +67,11 @@ func (n *graphNodeDeposedResource) Name() string { } func (n *graphNodeDeposedResource) ProvidedBy() []string { - return []string{resourceProvider(n.ResourceName, n.Provider)} + return []string{resourceProvider(n.ResourceName, n.ProviderName)} +} + +func (n *graphNodeDeposedResource) SetProvider(p string) { + n.ResolvedProvider = p } // GraphNodeEvalable impl. @@ -81,7 +91,7 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode { Node: &EvalSequence{ Nodes: []EvalNode{ &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, &EvalReadStateDeposed{ @@ -98,7 +108,7 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode { &EvalWriteStateDeposed{ Name: n.ResourceName, ResourceType: n.ResourceType, - Provider: n.Provider, + Provider: n.ProviderName, State: &state, Index: n.Index, }, @@ -114,7 +124,7 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode { Node: &EvalSequence{ Nodes: []EvalNode{ &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, &EvalReadStateDeposed{ @@ -147,7 +157,7 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode { &EvalWriteStateDeposed{ Name: n.ResourceName, ResourceType: n.ResourceType, - Provider: n.Provider, + Provider: n.ProviderName, State: &state, Index: n.Index, }, diff --git a/terraform/transform_import_state.go b/terraform/transform_import_state.go index 081df2f84..762bf1ded 100644 --- a/terraform/transform_import_state.go +++ b/terraform/transform_import_state.go @@ -21,9 +21,9 @@ func (t *ImportStateTransformer) Transform(g *Graph) error { } nodes = append(nodes, &graphNodeImportState{ - Addr: addr, - ID: target.ID, - Provider: target.Provider, + Addr: addr, + ID: target.ID, + ProviderName: target.Provider, }) } @@ -36,9 +36,10 @@ func (t *ImportStateTransformer) Transform(g *Graph) error { } type graphNodeImportState struct { - Addr *ResourceAddress // Addr is the resource address to import to - ID string // ID is the ID to import as - Provider string // Provider string + Addr *ResourceAddress // Addr is the resource address to import to + ID string // ID is the ID to import as + ProviderName string // Provider string + ResolvedProvider string // provider node address states []*InstanceState } @@ -48,7 +49,11 @@ func (n *graphNodeImportState) Name() string { } func (n *graphNodeImportState) ProvidedBy() []string { - return []string{resourceProvider(n.Addr.Type, n.Provider)} + return []string{resourceProvider(n.Addr.Type, n.ProviderName)} +} + +func (n *graphNodeImportState) SetProvider(p string) { + n.ResolvedProvider = p } // GraphNodeSubPath @@ -72,7 +77,7 @@ func (n *graphNodeImportState) EvalTree() EvalNode { return &EvalSequence{ Nodes: []EvalNode{ &EvalGetProvider{ - Name: n.ProvidedBy()[0], + Name: n.ResolvedProvider, Output: &provider, }, &EvalImportState{ @@ -149,10 +154,11 @@ func (n *graphNodeImportState) DynamicExpand(ctx EvalContext) (*Graph, error) { // is safe. for i, state := range n.states { g.Add(&graphNodeImportStateSub{ - Target: addrs[i], - Path_: n.Path(), - State: state, - Provider: n.Provider, + Target: addrs[i], + Path_: n.Path(), + State: state, + ProviderName: n.ProviderName, + ResolvedProvider: n.ResolvedProvider, }) } @@ -170,10 +176,11 @@ func (n *graphNodeImportState) DynamicExpand(ctx EvalContext) (*Graph, error) { // and is part of the subgraph. This node is responsible for refreshing // and adding a resource to the state once it is imported. type graphNodeImportStateSub struct { - Target *ResourceAddress - State *InstanceState - Path_ []string - Provider string + Target *ResourceAddress + State *InstanceState + Path_ []string + ProviderName string + ResolvedProvider string } func (n *graphNodeImportStateSub) Name() string { @@ -216,7 +223,7 @@ func (n *graphNodeImportStateSub) EvalTree() EvalNode { return &EvalSequence{ Nodes: []EvalNode{ &EvalGetProvider{ - Name: resourceProvider(info.Type, n.Provider), + Name: n.ResolvedProvider, Output: &provider, }, &EvalRefresh{ @@ -233,7 +240,7 @@ func (n *graphNodeImportStateSub) EvalTree() EvalNode { &EvalWriteState{ Name: key.String(), ResourceType: info.Type, - Provider: resourceProvider(info.Type, n.Provider), + Provider: resourceProvider(info.Type, n.ProviderName), State: &state, }, }, diff --git a/terraform/transform_provider.go b/terraform/transform_provider.go index 0bbbd1d99..37bb6be6c 100644 --- a/terraform/transform_provider.go +++ b/terraform/transform_provider.go @@ -10,10 +10,12 @@ import ( ) // GraphNodeProvider is an interface that nodes that can be a provider -// must implement. The ProviderName returned is the name of the provider -// they satisfy. +// must implement. +// ProviderName returns the name of the provider this satisfies. +// Name returns the full name of the provider in the config. type GraphNodeProvider interface { ProviderName() string + Name() string } // GraphNodeCloseProvider is an interface that nodes that can be a close @@ -29,6 +31,8 @@ type GraphNodeCloseProvider interface { type GraphNodeProviderConsumer interface { // TODO: make this return s string instead of a []string ProvidedBy() []string + // Set the resolved provider address for this resource. + SetProvider(string) } // ProviderTransformer is a GraphTransformer that maps resources to @@ -58,19 +62,16 @@ func (t *ProviderTransformer) Transform(g *Graph) error { // if we don't have a provider at this level, walk up the path looking for one for i := 1; target == nil; i++ { - pathPrefix := "" - raw := normalizeModulePath(sp.Path()) - if len(raw) < i { + path := normalizeModulePath(sp.Path()) + if len(path) < i { break } - raw = raw[:len(raw)-i] - - if len(raw) > len(rootModulePath) { - pathPrefix = modulePrefixStr(raw) + "." - } - key = pathPrefix + p + key = ResolveProviderName(p, path[:len(path)-i]) target = m[key] + if target != nil { + break + } } if target == nil { @@ -80,6 +81,7 @@ func (t *ProviderTransformer) Transform(g *Graph) error { break } + pv.SetProvider(key) g.Connect(dag.BasicEdge(v, target)) } } @@ -95,36 +97,32 @@ type CloseProviderTransformer struct{} func (t *CloseProviderTransformer) Transform(g *Graph) error { pm := providerVertexMap(g) - cpm := closeProviderVertexMap(g) + cpm := make(map[string]*graphNodeCloseProvider) var err error - for _, v := range g.Vertices() { - if pv, ok := v.(GraphNodeProviderConsumer); ok { - for _, p := range pv.ProvidedBy() { - key := p - source := cpm[key] - if source == nil { - // Create a new graphNodeCloseProvider and add it to the graph - source = &graphNodeCloseProvider{ProviderNameValue: p} - g.Add(source) + for _, v := range pm { + p := v.(GraphNodeProvider) - // Close node needs to depend on provider - provider, ok := pm[key] - if !ok { - err = multierror.Append(err, fmt.Errorf( - "%s: provider %s couldn't be found for closing", - dag.VertexName(v), p)) - continue - } - g.Connect(dag.BasicEdge(source, provider)) + // get the close provider of this type if we alread created it + closer := cpm[p.ProviderName()] - // Make sure we also add the new graphNodeCloseProvider to the map - // so we don't create and add any duplicate graphNodeCloseProviders. - cpm[key] = source - } + if closer == nil { + // create a closer for this provider type + closer = &graphNodeCloseProvider{ProviderNameValue: p.ProviderName()} + g.Add(closer) + cpm[p.ProviderName()] = closer + } - // Close node depends on all nodes provided by the provider - g.Connect(dag.BasicEdge(source, v)) + // Close node depends on the provider itself + // this is added unconditionally, so it will connect to all instances + // of the provider. Extra edges will be removed by transitive + // reduction. + g.Connect(dag.BasicEdge(closer, p)) + + // connect all the provider's resources to the close node + for _, s := range g.UpEdges(p).List() { + if _, ok := s.(GraphNodeResource); ok { + g.Connect(dag.BasicEdge(closer, s)) } } } @@ -187,54 +185,47 @@ func (t *MissingProviderTransformer) Transform(g *Graph) error { } } - for _, p := range pv.ProvidedBy() { - // always add the parent nodes to check, since configured providers - // may have already been added for modules. - if len(path) > 0 { - // We'll need the parent provider as well, so let's - // add a dummy node to check to make sure that we add - // that parent provider. - check = append(check, &graphNodeProviderConsumerDummy{ - ProviderValue: p, - PathValue: path[:len(path)-1], - }) - } + p := pv.ProvidedBy()[0] + // always add the parent nodes to check, since configured providers + // may have already been added for modules. + if len(path) > 0 { + // We'll need the parent provider as well, so let's + // add a dummy node to check to make sure that we add + // that parent provider. + check = append(check, &graphNodeProviderConsumerDummy{ + ProviderValue: p, + PathValue: path[:len(path)-1], + }) + } - key := providerMapKey(p, pv) + key := providerMapKey(p, pv) + if _, ok := m[key]; ok { + // This provider already exists as a configure node + continue + } - // TODO: jbardin come back to this - // only adding root level missing providers - key = p - if _, ok := m[key]; ok { - // This provider already exists as a configure node + // If the provider has an alias in it, we just want the type + // TODO: jbardin -- stop adding aliased providers altogether + ptype := p + if idx := strings.IndexRune(p, '.'); idx != -1 { + ptype = p[:idx] + } + + if !t.AllowAny { + if _, ok := supported[ptype]; !ok { + // If we don't support the provider type, skip it. + // Validation later will catch this as an error. continue } - - // If the provider has an alias in it, we just want the type - // TODO: jbardin -- stop adding aliased providers altogether - ptype := p - if idx := strings.IndexRune(p, '.'); idx != -1 { - ptype = p[:idx] - } - - if !t.AllowAny { - if _, ok := supported[ptype]; !ok { - // If we don't support the provider type, skip it. - // Validation later will catch this as an error. - continue - } - } - - // Add the missing provider node to the graph - v := t.Concrete(&NodeAbstractProvider{ - NameValue: p, - - // TODO: jbardin come back to this - // only adding root level missing providers - //PathValue: path, - }).(dag.Vertex) - m[key] = g.Add(v) } + + // Add the missing provider node to the graph + provider := t.Concrete(&NodeAbstractProvider{ + NameValue: p, + PathValue: path, + }).(dag.Vertex) + + m[key] = g.Add(provider) } return nil @@ -274,15 +265,14 @@ func (t *ParentProviderTransformer) Transform(g *Graph) error { } path = normalizeModulePath(path) - // Build the key with path.name i.e. "child.subchild.aws" - key := fmt.Sprintf("%s.%s", strings.Join(path, "."), pn.ProviderName()) + key := ResolveProviderName(pn.ProviderName(), path) m[key] = raw // Determine the parent if we're non-root. This is length 1 since // the 0 index should be "root" since we normalize above. if len(path) > 1 { path = path[:len(path)-1] - key := fmt.Sprintf("%s.%s", strings.Join(path, "."), pn.ProviderName()) + key := ResolveProviderName(pn.ProviderName(), path) parentMap[raw] = key } } @@ -323,23 +313,19 @@ func (t *PruneProviderTransformer) Transform(g *Graph) error { // providerMapKey is a helper that gives us the key to use for the // maps returned by things such as providerVertexMap. func providerMapKey(k string, v dag.Vertex) string { - pathPrefix := "" + // we create a dummy provider to + var path []string if sp, ok := v.(GraphNodeSubPath); ok { - raw := normalizeModulePath(sp.Path()) - if len(raw) > len(rootModulePath) { - pathPrefix = modulePrefixStr(raw) + "." - } + path = normalizeModulePath(sp.Path()) } - - return pathPrefix + k + return ResolveProviderName(k, path) } func providerVertexMap(g *Graph) map[string]dag.Vertex { m := make(map[string]dag.Vertex) for _, v := range g.Vertices() { if pv, ok := v.(GraphNodeProvider); ok { - key := providerMapKey(pv.ProviderName(), v) - m[key] = v + m[pv.Name()] = v } } @@ -416,3 +402,5 @@ func (n *graphNodeProviderConsumerDummy) Path() []string { func (n *graphNodeProviderConsumerDummy) ProvidedBy() []string { return []string{n.ProviderValue} } + +func (n *graphNodeProviderConsumerDummy) SetProvider(string) {} diff --git a/terraform/transform_provider_test.go b/terraform/transform_provider_test.go index 29561a246..a577d2c12 100644 --- a/terraform/transform_provider_test.go +++ b/terraform/transform_provider_test.go @@ -256,7 +256,7 @@ func TestMissingProviderTransformer_moduleGrandchild(t *testing.T) { actual := strings.TrimSpace(g.String()) expected := strings.TrimSpace(testTransformMissingProviderModuleGrandchildStr) if actual != expected { - t.Fatalf("bad:\n\n%s", actual) + t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) } } diff --git a/terraform/transform_reference.go b/terraform/transform_reference.go index 24ddc7a0a..2560e5ad6 100644 --- a/terraform/transform_reference.go +++ b/terraform/transform_reference.go @@ -335,8 +335,13 @@ func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string { } func modulePrefixStr(p []string) string { + // strip "root" + if len(p) > 0 && p[0] == rootModulePath[0] { + p = p[1:] + } + parts := make([]string, 0, len(p)*2) - for _, p := range p[1:] { + for _, p := range p { parts = append(parts, "module", p) } diff --git a/terraform/transform_resource_count.go b/terraform/transform_resource_count.go index cda35cb7b..e528b37b4 100644 --- a/terraform/transform_resource_count.go +++ b/terraform/transform_resource_count.go @@ -37,7 +37,9 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error { addr.Index = index // Build the abstract node and the concrete one - abstract := &NodeAbstractResource{Addr: addr} + abstract := &NodeAbstractResource{ + Addr: addr, + } var node dag.Vertex = abstract if f := t.Concrete; f != nil { node = f(abstract)