diff --git a/internal/command/import.go b/internal/command/import.go index a576a29e4047..51d3895e73e9 100644 --- a/internal/command/import.go +++ b/internal/command/import.go @@ -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. -` diff --git a/internal/command/import_test.go b/internal/command/import_test.go index f81658223b2b..8c4744c5d992 100644 --- a/internal/command/import_test.go +++ b/internal/command/import_test.go @@ -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"))() diff --git a/internal/command/meta.go b/internal/command/meta.go index 37bc7c2a8d4c..594292f1b95c 100644 --- a/internal/command/meta.go +++ b/internal/command/meta.go @@ -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 diff --git a/internal/command/meta_backend_migrate_test.go b/internal/command/meta_backend_migrate_test.go index d45cbdc0d962..24e5d9df9e82 100644 --- a/internal/command/meta_backend_migrate_test.go +++ b/internal/command/meta_backend_migrate_test.go @@ -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) diff --git a/internal/terraform/context_import.go b/internal/terraform/context_import.go index d809d6bb9dca..ce5df08f5fb3 100644 --- a/internal/terraform/context_import.go +++ b/internal/terraform/context_import.go @@ -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 diff --git a/internal/terraform/context_import_test.go b/internal/terraform/context_import_test.go index 605010d17569..af8ff4b856be 100644 --- a/internal/terraform/context_import_test.go +++ b/internal/terraform/context_import_test.go @@ -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 } `, }) @@ -823,7 +838,8 @@ resource "test_resource" "unused" { ResourceTypes: map[string]*configschema.Block{ "test_resource": { Attributes: map[string]*configschema.Attribute{ - "id": {Type: cty.String, Computed: true}, + "id": {Type: cty.String, Computed: true}, + "required": {Type: cty.String, Required: true}, }, }, }, @@ -834,17 +850,8 @@ 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"), + "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") } diff --git a/internal/terraform/context_plan.go b/internal/terraform/context_plan.go index c7a7c207c196..66e22d90bff1 100644 --- a/internal/terraform/context_plan.go +++ b/internal/terraform/context_plan.go @@ -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: diff --git a/internal/terraform/context_validate.go b/internal/terraform/context_validate.go index 070694920ca5..aad884442a39 100644 --- a/internal/terraform/context_validate.go +++ b/internal/terraform/context_validate.go @@ -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() { diff --git a/internal/terraform/context_walk.go b/internal/terraform/context_walk.go index 1bc7491d0661..cc61331cbb97 100644 --- a/internal/terraform/context_walk.go +++ b/internal/terraform/context_walk.go @@ -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() diff --git a/internal/terraform/graph_builder_destroy_plan.go b/internal/terraform/graph_builder_destroy_plan.go deleted file mode 100644 index 9e29d7ceb662..000000000000 --- a/internal/terraform/graph_builder_destroy_plan.go +++ /dev/null @@ -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 -} diff --git a/internal/terraform/graph_builder_import.go b/internal/terraform/graph_builder_import.go deleted file mode 100644 index 79c0724f76d0..000000000000 --- a/internal/terraform/graph_builder_import.go +++ /dev/null @@ -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 -} diff --git a/internal/terraform/graph_builder_plan.go b/internal/terraform/graph_builder_plan.go index ed63675d2252..b3a8bf5abde4 100644 --- a/internal/terraform/graph_builder_plan.go +++ b/internal/terraform/graph_builder_plan.go @@ -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, + } + } } diff --git a/internal/terraform/graph_builder_plan_test.go b/internal/terraform/graph_builder_plan_test.go index 9ec16c6ed79f..8e6f5cdaf606 100644 --- a/internal/terraform/graph_builder_plan_test.go +++ b/internal/terraform/graph_builder_plan_test.go @@ -34,8 +34,9 @@ func TestPlanGraphBuilder(t *testing.T) { }, nil) b := &PlanGraphBuilder{ - Config: testModule(t, "graph-builder-plan-basic"), - Plugins: plugins, + Config: testModule(t, "graph-builder-plan-basic"), + Plugins: plugins, + Operation: walkPlan, } g, err := b.Build(addrs.RootModuleInstance) @@ -76,8 +77,9 @@ func TestPlanGraphBuilder_dynamicBlock(t *testing.T) { }, nil) b := &PlanGraphBuilder{ - Config: testModule(t, "graph-builder-plan-dynblock"), - Plugins: plugins, + Config: testModule(t, "graph-builder-plan-dynblock"), + Plugins: plugins, + Operation: walkPlan, } g, err := b.Build(addrs.RootModuleInstance) @@ -131,8 +133,9 @@ func TestPlanGraphBuilder_attrAsBlocks(t *testing.T) { }, nil) b := &PlanGraphBuilder{ - Config: testModule(t, "graph-builder-plan-attr-as-blocks"), - Plugins: plugins, + 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) @@ -194,8 +198,9 @@ func TestPlanGraphBuilder_forEach(t *testing.T) { }, nil) b := &PlanGraphBuilder{ - Config: testModule(t, "plan-for-each"), - Plugins: plugins, + Config: testModule(t, "plan-for-each"), + Plugins: plugins, + Operation: walkPlan, } g, err := b.Build(addrs.RootModuleInstance) diff --git a/internal/terraform/graph_builder_validate.go b/internal/terraform/graph_builder_validate.go deleted file mode 100644 index ff5148216c4f..000000000000 --- a/internal/terraform/graph_builder_validate.go +++ /dev/null @@ -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 -} diff --git a/internal/terraform/node_resource_abstract.go b/internal/terraform/node_resource_abstract.go index 1a5bd1ff5c21..45941f13a166 100644 --- a/internal/terraform/node_resource_abstract.go +++ b/internal/terraform/node_resource_abstract.go @@ -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. diff --git a/internal/terraform/transform_import_state.go b/internal/terraform/node_resource_import.go similarity index 83% rename from internal/terraform/transform_import_state.go rename to internal/terraform/node_resource_import.go index 3aa53e22d79d..ecf39a07e033 100644 --- a/internal/terraform/transform_import_state.go +++ b/internal/terraform/node_resource_import.go @@ -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 diff --git a/internal/terraform/node_resource_plan.go b/internal/terraform/node_resource_plan.go index 1ee94c7435f0..d478bc3ce0b9 100644 --- a/internal/terraform/node_resource_plan.go +++ b/internal/terraform/node_resource_plan.go @@ -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 diff --git a/internal/terraform/testdata/import-provider/main.tf b/internal/terraform/testdata/import-provider/main.tf index ed8e3fe9fd40..5d41fb3e6162 100644 --- a/internal/terraform/testdata/import-provider/main.tf +++ b/internal/terraform/testdata/import-provider/main.tf @@ -3,5 +3,4 @@ provider "aws" { } resource "aws_instance" "foo" { - id = "bar" } diff --git a/internal/terraform/transform_config.go b/internal/terraform/transform_config.go index 3895efebb26a..59fa1eeea9f8 100644 --- a/internal/terraform/transform_config.go +++ b/internal/terraform/transform_config.go @@ -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 diff --git a/internal/terraform/transform_provider_test.go b/internal/terraform/transform_provider_test.go index 3580a548b300..ff21685710d1 100644 --- a/internal/terraform/transform_provider_test.go +++ b/internal/terraform/transform_provider_test.go @@ -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"]`