Skip to content

Commit

Permalink
Merge pull request #31283 from hashicorp/jbardin/plan-import
Browse files Browse the repository at this point in the history
Use plan graph for importing resources
  • Loading branch information
jbardin committed Jun 23, 2022
2 parents a5f307b + 142ce15 commit 77e6b62
Show file tree
Hide file tree
Showing 20 changed files with 167 additions and 396 deletions.
18 changes: 1 addition & 17 deletions internal/command/import.go
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
`
57 changes: 0 additions & 57 deletions internal/command/import_test.go
Expand Up @@ -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"))()

Expand Down
3 changes: 0 additions & 3 deletions internal/command/meta.go
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions internal/command/meta_backend_migrate_test.go
@@ -1,7 +1,6 @@
package command

import (
"fmt"
"testing"
)

Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion internal/terraform/context_import.go
Expand Up @@ -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
Expand Down
37 changes: 22 additions & 15 deletions internal/terraform/context_import_test.go
Expand Up @@ -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),
Expand Down Expand Up @@ -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": `
Expand All @@ -797,6 +808,9 @@ module "b" {
source = "./b"
y = module.a[each.key].y
}
resource "test_resource" "test" {
}
`,
"a/main.tf": `
output "y" {
Expand All @@ -810,6 +824,7 @@ variable "y" {
resource "test_resource" "unused" {
value = var.y
// missing required, but should not error
}
`,
})
Expand All @@ -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},
},
},
},
Expand All @@ -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"),
}),
},
},
Expand All @@ -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")
}
Expand Down
5 changes: 4 additions & 1 deletion internal/terraform/context_plan.go
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion internal/terraform/context_validate.go
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion internal/terraform/context_walk.go
Expand Up @@ -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()
Expand Down
17 changes: 0 additions & 17 deletions internal/terraform/graph_builder_destroy_plan.go

This file was deleted.

0 comments on commit 77e6b62

Please sign in to comment.