Merge pull request #31283 from hashicorp/jbardin/plan-import

Use plan graph for importing resources
This commit is contained in:
James Bardin 2022-06-23 13:26:59 -04:00 committed by GitHub
commit 77e6b622f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 168 additions and 397 deletions

View File

@ -45,7 +45,6 @@ func (c *ImportCommand) Run(args []string) int {
cmdFlags.StringVar(&configPath, "config", pwd, "path")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.BoolVar(&c.Meta.allowMissingConfig, "allow-missing-config", false, "allow missing config")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1
@ -135,7 +134,7 @@ func (c *ImportCommand) Run(args []string) int {
break
}
}
if !c.Meta.allowMissingConfig && rc == nil {
if rc == nil {
modulePath := addr.Module.String()
if modulePath == "" {
modulePath = "the root module"
@ -262,10 +261,6 @@ func (c *ImportCommand) Run(args []string) int {
c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg))
if c.Meta.allowMissingConfig && rc == nil {
c.Ui.Output(c.Colorize().Color("[reset][yellow]\n" + importCommandAllowMissingResourceMsg))
}
c.showDiagnostics(diags)
if diags.HasErrors() {
return 1
@ -310,8 +305,6 @@ Options:
If no config files are present, they must be provided
via the input prompts or env vars.
-allow-missing-config Allow import when no resource configuration block exists.
-input=false Disable interactive input prompts.
-lock=false Don't hold a state lock during the operation. This is
@ -361,12 +354,3 @@ const importCommandSuccessMsg = `Import successful!
The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
`
const importCommandAllowMissingResourceMsg = `Import does not generate resource configuration, you must create a resource
configuration block that matches the current or desired state manually.
If there is no matching resource configuration block for the imported
resource, Terraform will delete the resource on the next "terraform apply".
It is recommended that you run "terraform plan" to verify that the
configuration is correct and complete.
`

View File

@ -644,63 +644,6 @@ func TestImport_providerConfigWithVarFile(t *testing.T) {
testStateOutput(t, statePath, testImportStr)
}
func TestImport_allowMissingResourceConfig(t *testing.T) {
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
statePath := testTempFile(t)
p := testProvider()
ui := new(cli.MockUi)
view, _ := testView(t)
c := &ImportCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
View: view,
},
}
p.ImportResourceStateFn = nil
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
ImportedResources: []providers.ImportedResource{
{
TypeName: "test_instance",
State: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("yay"),
}),
},
},
}
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
ResourceTypes: map[string]providers.Schema{
"test_instance": {
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
},
},
},
},
}
args := []string{
"-state", statePath,
"-allow-missing-config",
"test_instance.foo",
"bar",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
if !p.ImportResourceStateCalled {
t.Fatal("ImportResourceState should be called")
}
testStateOutput(t, statePath, testImportStr)
}
func TestImport_emptyConfig(t *testing.T) {
defer testChdir(t, testFixturePath("empty"))()

View File

@ -231,9 +231,6 @@ type Meta struct {
migrateState bool
compactWarnings bool
// Used with the import command to allow import of state when no matching config exists.
allowMissingConfig bool
// Used with commands which write state to allow users to write remote
// state even if the remote and local Terraform versions don't match.
ignoreRemoteVersion bool

View File

@ -1,7 +1,6 @@
package command
import (
"fmt"
"testing"
)
@ -37,7 +36,7 @@ func TestBackendMigrate_promptMultiStatePattern(t *testing.T) {
},
}
for name, tc := range cases {
fmt.Println("Test: ", name)
t.Log("Test: ", name)
m := testMetaBackend(t, nil)
input := map[string]string{}
cleanup := testInputMap(t, input)

View File

@ -56,11 +56,13 @@ func (c *Context) Import(config *configs.Config, prevRunState *states.State, opt
variables := opts.SetVariables
// Initialize our graph builder
builder := &ImportGraphBuilder{
builder := &PlanGraphBuilder{
ImportTargets: opts.Targets,
Config: config,
State: state,
RootVariableValues: variables,
Plugins: c.plugins,
Operation: walkImport,
}
// Build the graph

View File

@ -52,9 +52,20 @@ func TestContextImport_basic(t *testing.T) {
}
}
// import 1 of count instances in the configuration
func TestContextImport_countIndex(t *testing.T) {
p := testProvider("aws")
m := testModule(t, "import-provider")
m := testModuleInline(t, map[string]string{
"main.tf": `
provider "aws" {
foo = "bar"
}
resource "aws_instance" "foo" {
count = 2
}
`})
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
@ -779,7 +790,7 @@ func TestContextImport_multiStateSame(t *testing.T) {
}
}
func TestContextImport_noConfigModuleImport(t *testing.T) {
func TestContextImport_nestedModuleImport(t *testing.T) {
p := testProvider("aws")
m := testModuleInline(t, map[string]string{
"main.tf": `
@ -797,6 +808,9 @@ module "b" {
source = "./b"
y = module.a[each.key].y
}
resource "test_resource" "test" {
}
`,
"a/main.tf": `
output "y" {
@ -810,6 +824,7 @@ variable "y" {
resource "test_resource" "unused" {
value = var.y
// missing required, but should not error
}
`,
})
@ -824,6 +839,7 @@ resource "test_resource" "unused" {
"test_resource": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"required": {Type: cty.String, Required: true},
},
},
},
@ -835,16 +851,7 @@ resource "test_resource" "unused" {
TypeName: "test_resource",
State: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("test"),
}),
},
},
}
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
ImportedResources: []providers.ImportedResource{
{
TypeName: "test_resource",
State: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("test"),
"required": cty.StringVal("value"),
}),
},
},
@ -871,7 +878,7 @@ resource "test_resource" "unused" {
}
ri := state.ResourceInstance(mustResourceInstanceAddr("test_resource.test"))
expected := `{"id":"test"}`
expected := `{"id":"test","required":"value"}`
if ri == nil || ri.Current == nil {
t.Fatal("no state is recorded for resource instance test_resource.test")
}

View File

@ -560,6 +560,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
Targets: opts.Targets,
ForceReplace: opts.ForceReplace,
skipRefresh: opts.SkipRefresh,
Operation: walkPlan,
}).Build(addrs.RootModuleInstance)
return graph, walkPlan, diags
case plans.RefreshOnlyMode:
@ -571,16 +572,18 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
Targets: opts.Targets,
skipRefresh: opts.SkipRefresh,
skipPlanChanges: true, // this activates "refresh only" mode.
Operation: walkPlan,
}).Build(addrs.RootModuleInstance)
return graph, walkPlan, diags
case plans.DestroyMode:
graph, diags := DestroyPlanGraphBuilder(&PlanGraphBuilder{
graph, diags := (&PlanGraphBuilder{
Config: config,
State: prevRunState,
RootVariableValues: opts.SetVariables,
Plugins: c.plugins,
Targets: opts.Targets,
skipRefresh: opts.SkipRefresh,
Operation: walkPlanDestroy,
}).Build(addrs.RootModuleInstance)
return graph, walkPlanDestroy, diags
default:

View File

@ -55,11 +55,12 @@ func (c *Context) Validate(config *configs.Config) tfdiags.Diagnostics {
}
}
graph, moreDiags := ValidateGraphBuilder(&PlanGraphBuilder{
graph, moreDiags := (&PlanGraphBuilder{
Config: config,
Plugins: c.plugins,
State: states.NewState(),
RootVariableValues: varValues,
Operation: walkValidate,
}).Build(addrs.RootModuleInstance)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {

View File

@ -73,7 +73,7 @@ func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *Con
refreshState = states.NewState().SyncWrapper()
prevRunState = states.NewState().SyncWrapper()
case walkPlan, walkPlanDestroy:
case walkPlan, walkPlanDestroy, walkImport:
state = inputState.DeepCopy().SyncWrapper()
refreshState = inputState.DeepCopy().SyncWrapper()
prevRunState = inputState.DeepCopy().SyncWrapper()

View File

@ -1,17 +0,0 @@
package terraform
import (
"github.com/hashicorp/terraform/internal/dag"
)
func DestroyPlanGraphBuilder(p *PlanGraphBuilder) GraphBuilder {
p.ConcreteResourceInstance = func(a *NodeAbstractResourceInstance) dag.Vertex {
return &NodePlanDestroyableResourceInstance{
NodeAbstractResourceInstance: a,
skipRefresh: p.skipRefresh,
}
}
p.destroy = true
return p
}

View File

@ -1,101 +0,0 @@
package terraform
import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/dag"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// ImportGraphBuilder implements GraphBuilder and is responsible for building
// a graph for importing resources into Terraform. This is a much, much
// simpler graph than a normal configuration graph.
type ImportGraphBuilder struct {
// ImportTargets are the list of resources to import.
ImportTargets []*ImportTarget
// Module is a configuration to build the graph from. See ImportOpts.Config.
Config *configs.Config
// RootVariableValues are the raw input values for root input variables
// given by the caller, which we'll resolve into final values as part
// of the plan walk.
RootVariableValues InputValues
// Plugins is a library of plug-in components (providers and
// provisioners) available for use.
Plugins *contextPlugins
}
// Build builds the graph according to the steps returned by Steps.
func (b *ImportGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
return (&BasicGraphBuilder{
Steps: b.Steps(),
Name: "ImportGraphBuilder",
}).Build(path)
}
// Steps returns the ordered list of GraphTransformers that must be executed
// to build a complete graph.
func (b *ImportGraphBuilder) Steps() []GraphTransformer {
// Get the module. If we don't have one, we just use an empty tree
// so that the transform still works but does nothing.
config := b.Config
if config == nil {
config = configs.NewEmptyConfig()
}
// Custom factory for creating providers.
concreteProvider := func(a *NodeAbstractProvider) dag.Vertex {
return &NodeApplyableProvider{
NodeAbstractProvider: a,
}
}
steps := []GraphTransformer{
// Create all our resources from the configuration and state
&ConfigTransformer{Config: config},
// Add dynamic values
&RootVariableTransformer{Config: b.Config, RawValues: b.RootVariableValues},
&ModuleVariableTransformer{Config: b.Config},
&LocalTransformer{Config: b.Config},
&OutputTransformer{Config: b.Config},
// Attach the configuration to any resources
&AttachResourceConfigTransformer{Config: b.Config},
// Add the import steps
&ImportStateTransformer{Targets: b.ImportTargets, Config: b.Config},
transformProviders(concreteProvider, config),
// Must attach schemas before ReferenceTransformer so that we can
// analyze the configuration to find references.
&AttachSchemaTransformer{Plugins: b.Plugins, Config: b.Config},
// Create expansion nodes for all of the module calls. This must
// come after all other transformers that create nodes representing
// objects that can belong to modules.
&ModuleExpansionTransformer{Config: b.Config},
// Connect so that the references are ready for targeting. We'll
// have to connect again later for providers and so on.
&ReferenceTransformer{},
// Make sure data sources are aware of any depends_on from the
// configuration
&attachDataResourceDependsOnTransformer{},
// Close opened plugin connections
&CloseProviderTransformer{},
// Close root module
&CloseRootModuleTransformer{},
// Optimize
&TransitiveReductionTransformer{},
}
return steps
}

View File

@ -1,6 +1,8 @@
package terraform
import (
"log"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/dag"
@ -52,10 +54,6 @@ type PlanGraphBuilder struct {
// where we _only_ do the refresh step.)
skipPlanChanges bool
// CustomConcrete can be set to customize the node types created
// for various parts of the plan. This is useful in order to customize
// the plan behavior.
CustomConcrete bool
ConcreteProvider ConcreteProviderNodeFunc
ConcreteResource ConcreteResourceNodeFunc
ConcreteResourceInstance ConcreteResourceInstanceNodeFunc
@ -63,12 +61,16 @@ type PlanGraphBuilder struct {
ConcreteResourceInstanceDeposed ConcreteResourceInstanceDeposedNodeFunc
ConcreteModule ConcreteModuleNodeFunc
// destroy is set to true when create a full destroy plan.
destroy bool
// Plan Operation this graph will be used for.
Operation walkOperation
// ImportTargets are the list of resources to import.
ImportTargets []*ImportTarget
}
// See GraphBuilder
func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Diagnostics) {
log.Printf("[TRACE] building graph for %s", b.Operation)
return (&BasicGraphBuilder{
Steps: b.Steps(),
Name: "PlanGraphBuilder",
@ -77,14 +79,29 @@ func (b *PlanGraphBuilder) Build(path addrs.ModuleInstance) (*Graph, tfdiags.Dia
// See GraphBuilder
func (b *PlanGraphBuilder) Steps() []GraphTransformer {
b.init()
switch b.Operation {
case walkPlan:
b.initPlan()
case walkPlanDestroy:
b.initDestroy()
case walkValidate:
b.initValidate()
case walkImport:
b.initImport()
default:
panic("invalid plan operation: " + b.Operation.String())
}
steps := []GraphTransformer{
// Creates all the resources represented in the config
&ConfigTransformer{
Concrete: b.ConcreteResource,
Config: b.Config,
skip: b.destroy,
// Resources are not added from the config on destroy.
skip: b.Operation == walkPlanDestroy,
importTargets: b.ImportTargets,
},
// Add dynamic values
@ -94,7 +111,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
&OutputTransformer{
Config: b.Config,
RefreshOnly: b.skipPlanChanges,
removeRootOutputs: b.destroy,
removeRootOutputs: b.Operation == walkPlanDestroy,
},
// Add orphan resources
@ -102,13 +119,14 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
Concrete: b.ConcreteResourceOrphan,
State: b.State,
Config: b.Config,
skip: b.destroy,
skip: b.Operation == walkPlanDestroy,
},
// We also need nodes for any deposed instance objects present in the
// state, so we can plan to destroy them. (This intentionally
// skips creating nodes for _current_ objects, since ConfigTransformer
// created nodes that will do that during DynamicExpand.)
// state, so we can plan to destroy them. (During plan this will
// intentionally skip creating nodes for _current_ objects, since
// ConfigTransformer created nodes that will do that during
// DynamicExpand.)
&StateTransformer{
ConcreteCurrent: b.ConcreteResourceInstance,
ConcreteDeposed: b.ConcreteResourceInstanceDeposed,
@ -172,12 +190,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
return steps
}
func (b *PlanGraphBuilder) init() {
// Do nothing if the user requests customizing the fields
if b.CustomConcrete {
return
}
func (b *PlanGraphBuilder) initPlan() {
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
return &NodeApplyableProvider{
NodeAbstractProvider: a,
@ -210,5 +223,61 @@ func (b *PlanGraphBuilder) init() {
skipPlanChanges: b.skipPlanChanges,
}
}
}
func (b *PlanGraphBuilder) initDestroy() {
b.initPlan()
b.ConcreteResourceInstance = func(a *NodeAbstractResourceInstance) dag.Vertex {
return &NodePlanDestroyableResourceInstance{
NodeAbstractResourceInstance: a,
skipRefresh: b.skipRefresh,
}
}
}
func (b *PlanGraphBuilder) initValidate() {
// Set the provider to the normal provider. This will ask for input.
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
return &NodeApplyableProvider{
NodeAbstractProvider: a,
}
}
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
return &NodeValidatableResource{
NodeAbstractResource: a,
}
}
b.ConcreteModule = func(n *nodeExpandModule) dag.Vertex {
return &nodeValidateModule{
nodeExpandModule: *n,
}
}
}
func (b *PlanGraphBuilder) initImport() {
b.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
return &NodeApplyableProvider{
NodeAbstractProvider: a,
}
}
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
return &nodeExpandPlannableResource{
NodeAbstractResource: a,
// For now we always skip planning changes for import, since we are
// not going to combine importing with other changes. This is
// temporary to try and maintain existing import behaviors, but
// planning will need to be allowed for more complex configurations.
skipPlanChanges: true,
// We also skip refresh for now, since the plan output is written
// as the new state, and users are not expecting the import process
// to update any other instances in state.
skipRefresh: true,
}
}
}

View File

@ -36,6 +36,7 @@ func TestPlanGraphBuilder(t *testing.T) {
b := &PlanGraphBuilder{
Config: testModule(t, "graph-builder-plan-basic"),
Plugins: plugins,
Operation: walkPlan,
}
g, err := b.Build(addrs.RootModuleInstance)
@ -78,6 +79,7 @@ func TestPlanGraphBuilder_dynamicBlock(t *testing.T) {
b := &PlanGraphBuilder{
Config: testModule(t, "graph-builder-plan-dynblock"),
Plugins: plugins,
Operation: walkPlan,
}
g, err := b.Build(addrs.RootModuleInstance)
@ -133,6 +135,7 @@ func TestPlanGraphBuilder_attrAsBlocks(t *testing.T) {
b := &PlanGraphBuilder{
Config: testModule(t, "graph-builder-plan-attr-as-blocks"),
Plugins: plugins,
Operation: walkPlan,
}
g, err := b.Build(addrs.RootModuleInstance)
@ -173,6 +176,7 @@ func TestPlanGraphBuilder_targetModule(t *testing.T) {
Targets: []addrs.Targetable{
addrs.RootModuleInstance.Child("child2", addrs.NoKey),
},
Operation: walkPlan,
}
g, err := b.Build(addrs.RootModuleInstance)
@ -196,6 +200,7 @@ func TestPlanGraphBuilder_forEach(t *testing.T) {
b := &PlanGraphBuilder{
Config: testModule(t, "plan-for-each"),
Plugins: plugins,
Operation: walkPlan,
}
g, err := b.Build(addrs.RootModuleInstance)

View File

@ -1,40 +0,0 @@
package terraform
import (
"github.com/hashicorp/terraform/internal/dag"
)
// ValidateGraphBuilder creates the graph for the validate operation.
//
// ValidateGraphBuilder is based on the PlanGraphBuilder. We do this so that
// we only have to validate what we'd normally plan anyways. The
// PlanGraphBuilder given will be modified so it shouldn't be used for anything
// else after calling this function.
func ValidateGraphBuilder(p *PlanGraphBuilder) GraphBuilder {
// We're going to customize the concrete functions
p.CustomConcrete = true
// Set the provider to the normal provider. This will ask for input.
p.ConcreteProvider = func(a *NodeAbstractProvider) dag.Vertex {
return &NodeApplyableProvider{
NodeAbstractProvider: a,
}
}
p.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
return &NodeValidatableResource{
NodeAbstractResource: a,
}
}
p.ConcreteModule = func(n *nodeExpandModule) dag.Vertex {
return &nodeValidateModule{
nodeExpandModule: *n,
}
}
// We purposely don't set any other concrete types since they don't
// require validation.
return p
}

View File

@ -68,6 +68,9 @@ type NodeAbstractResource struct {
// The address of the provider this resource will use
ResolvedProvider addrs.AbsProviderConfig
// This resource may expand into instances which need to be imported.
importTargets []*ImportTarget
}
var (
@ -124,6 +127,10 @@ func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable {
return []addrs.Referenceable{n.Addr.Resource}
}
func (n *NodeAbstractResource) Import(addr *ImportTarget) {
}
// GraphNodeReferencer
func (n *NodeAbstractResource) References() []*addrs.Reference {
// If we have a config then we prefer to use that.

View File

@ -5,62 +5,11 @@ import (
"log"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// ImportStateTransformer is a GraphTransformer that adds nodes to the
// graph to represent the imports we want to do for resources.
type ImportStateTransformer struct {
Targets []*ImportTarget
Config *configs.Config
}
func (t *ImportStateTransformer) Transform(g *Graph) error {
for _, target := range t.Targets {
// This is only likely to happen in misconfigured tests
if t.Config == nil {
return fmt.Errorf("cannot import into an empty configuration")
}
// Get the module config
modCfg := t.Config.Descendent(target.Addr.Module.Module())
if modCfg == nil {
return fmt.Errorf("module %s not found", target.Addr.Module.Module())
}
providerAddr := addrs.AbsProviderConfig{
Module: target.Addr.Module.Module(),
}
// Try to find the resource config
rsCfg := modCfg.Module.ResourceByAddr(target.Addr.Resource.Resource)
if rsCfg != nil {
// Get the provider FQN for the resource from the resource configuration
providerAddr.Provider = rsCfg.Provider
// Get the alias from the resource's provider local config
providerAddr.Alias = rsCfg.ProviderConfigAddr().Alias
} else {
// Resource has no matching config, so use an implied provider
// based on the resource type
rsProviderType := target.Addr.Resource.Resource.ImpliedProvider()
providerAddr.Provider = modCfg.Module.ImpliedProviderForUnqualifiedType(rsProviderType)
}
node := &graphNodeImportState{
Addr: target.Addr,
ID: target.ID,
ProviderAddr: providerAddr,
}
g.Add(node)
}
return nil
}
type graphNodeImportState struct {
Addr addrs.AbsResourceInstance // Addr is the resource address to import into
ID string // ID is the ID to import as

View File

@ -322,6 +322,17 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
// The concrete resource factory we'll use
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
// check if this node is being imported first
for _, importTarget := range n.importTargets {
if importTarget.Addr.Equal(a.Addr) {
return &graphNodeImportState{
Addr: importTarget.Addr,
ID: importTarget.ID,
ResolvedProvider: n.ResolvedProvider,
}
}
}
// Add the config and state since we don't do that via transforms
a.Config = n.Config
a.ResolvedProvider = n.ResolvedProvider

View File

@ -3,5 +3,4 @@ provider "aws" {
}
resource "aws_instance" "foo" {
id = "bar"
}

View File

@ -31,6 +31,9 @@ type ConfigTransformer struct {
// Do not apply this transformer.
skip bool
// configuration resources that are to be imported
importTargets []*ImportTarget
}
func (t *ConfigTransformer) Transform(g *Graph) error {
@ -89,11 +92,22 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er
continue
}
// If any of the import targets can apply to this node's instances,
// filter them down to the applicable addresses.
var imports []*ImportTarget
configAddr := relAddr.InModule(path)
for _, i := range t.importTargets {
if target := i.Addr.ContainingResource().Config(); target.Equal(configAddr) {
imports = append(imports, i)
}
}
abstract := &NodeAbstractResource{
Addr: addrs.ConfigResource{
Resource: relAddr,
Module: path,
},
importTargets: imports,
}
var node dag.Vertex = abstract

View File

@ -49,58 +49,6 @@ func TestProviderTransformer(t *testing.T) {
}
}
func TestProviderTransformer_ImportModuleChild(t *testing.T) {
mod := testModule(t, "import-module")
g := testProviderTransformerGraph(t, mod)
{
tf := &ImportStateTransformer{
Config: mod,
Targets: []*ImportTarget{
&ImportTarget{
Addr: addrs.RootModuleInstance.
Child("child", addrs.NoKey).
ResourceInstance(
addrs.ManagedResourceMode,
"aws_instance",
"foo",
addrs.NoKey,
),
ID: "bar",
},
},
}
if err := tf.Transform(g); err != nil {
t.Fatalf("err: %s", err)
}
t.Logf("graph after ImportStateTransformer:\n%s", g.String())
}
{
tf := &MissingProviderTransformer{}
if err := tf.Transform(g); err != nil {
t.Fatalf("err: %s", err)
}
t.Logf("graph after MissingProviderTransformer:\n%s", g.String())
}
{
tf := &ProviderTransformer{}
if err := tf.Transform(g); err != nil {
t.Fatalf("err: %s", err)
}
t.Logf("graph after ProviderTransformer:\n%s", g.String())
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformImportModuleChildStr)
if actual != expected {
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
}
}
// Test providers with FQNs that do not match the typeName
func TestProviderTransformer_fqns(t *testing.T) {
for _, mod := range []string{"fqns", "fqns-module"} {
@ -541,12 +489,3 @@ module.child.module.grandchild.aws_instance.baz
provider["registry.terraform.io/hashicorp/aws"].foo
provider["registry.terraform.io/hashicorp/aws"].foo
`
const testTransformImportModuleChildStr = `
module.child.aws_instance.foo
provider["registry.terraform.io/hashicorp/aws"]
module.child.aws_instance.foo (import id "bar")
provider["registry.terraform.io/hashicorp/aws"]
module.child.module.nested.aws_instance.foo
provider["registry.terraform.io/hashicorp/aws"]
provider["registry.terraform.io/hashicorp/aws"]`