mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Import ForEach: Prerequisite - Prepare codebase for dynamic addresses for ImportTarget
s (#1207)
Signed-off-by: RLRabinowitz <rlrabinowitz2@gmail.com>
This commit is contained in:
parent
f92ae16419
commit
80f72cecfe
@ -14,8 +14,6 @@ import (
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/backend"
|
||||
"github.com/opentofu/opentofu/internal/command/arguments"
|
||||
@ -238,11 +236,10 @@ func (c *ImportCommand) Run(args []string) int {
|
||||
newState, importDiags := lr.Core.Import(lr.Config, lr.InputState, &tofu.ImportOpts{
|
||||
Targets: []*tofu.ImportTarget{
|
||||
{
|
||||
Addr: addr,
|
||||
|
||||
// In the import block, the ID can be an arbitrary hcl.Expression,
|
||||
// but here it's always interpreted as a literal string.
|
||||
ID: hcl.StaticExpr(cty.StringVal(args[1]), configs.SynthBody("import", nil).MissingItemRange()),
|
||||
CommandLineImportTarget: &tofu.CommandLineImportTarget{
|
||||
Addr: addr,
|
||||
ID: args[1],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/configs"
|
||||
"github.com/opentofu/opentofu/internal/states"
|
||||
@ -25,19 +26,89 @@ type ImportOpts struct {
|
||||
SetVariables InputValues
|
||||
}
|
||||
|
||||
// ImportTarget is a single resource to import,
|
||||
// in legacy (CLI) import mode.
|
||||
type ImportTarget struct {
|
||||
// Config is the original import block for this import. This might be null
|
||||
// if the import did not originate in config.
|
||||
Config *configs.Import
|
||||
|
||||
// CommandLineImportTarget is a target that we need to import, that originated from the CLI command
|
||||
// It represents a single resource that we need to import.
|
||||
// The resource's ID and Address are fully known when executing the command (unlike when using the `import` block)
|
||||
type CommandLineImportTarget struct {
|
||||
// Addr is the address for the resource instance that the new object should
|
||||
// be imported into.
|
||||
Addr addrs.AbsResourceInstance
|
||||
|
||||
// ID is the ID of the resource to import. This is resource-specific.
|
||||
ID hcl.Expression
|
||||
// ID is the string ID of the resource to import. This is resource-specific.
|
||||
ID string
|
||||
}
|
||||
|
||||
// ImportTarget is a target that we need to import.
|
||||
// It could either represent a single resource or multiple instances of the same resource, if for_each is used
|
||||
// ImportTarget can be either a result of the import CLI command, or the import block
|
||||
type ImportTarget struct {
|
||||
// Config is the original import block for this import. This might be null
|
||||
// if the import did not originate in config.
|
||||
// Config is mutually-exclusive with CommandLineImportTarget
|
||||
Config *configs.Import
|
||||
|
||||
// CommandLineImportTarget is the ImportTarget information in the case of an import target origination for the
|
||||
// command line. CommandLineImportTarget is mutually-exclusive with Config
|
||||
*CommandLineImportTarget
|
||||
}
|
||||
|
||||
// IsFromImportBlock checks whether the import target originates from an `import` block
|
||||
// Currently, it should yield the opposite result of IsFromImportCommandLine, as those two are mutually-exclusive
|
||||
func (i *ImportTarget) IsFromImportBlock() bool {
|
||||
return i.Config != nil
|
||||
}
|
||||
|
||||
// IsFromImportCommandLine checks whether the import target originates from a `tofu import` command
|
||||
// Currently, it should yield the opposite result of IsFromImportBlock, as those two are mutually-exclusive
|
||||
func (i *ImportTarget) IsFromImportCommandLine() bool {
|
||||
return i.CommandLineImportTarget != nil
|
||||
}
|
||||
|
||||
// StaticAddr returns the static address part of an import target
|
||||
// For an ImportTarget originating from the command line, the address is already known
|
||||
// However for an ImportTarget originating from an import block, the full address might not be known initially,
|
||||
// and could only be evaluated down the line. Here, we create a static representation for the address.
|
||||
// This is useful so that we could have information on the ImportTarget early on, such as the Module and Resource of it
|
||||
func (i *ImportTarget) StaticAddr() addrs.ConfigResource {
|
||||
if i.CommandLineImportTarget != nil {
|
||||
return i.CommandLineImportTarget.Addr.ConfigResource()
|
||||
}
|
||||
|
||||
// TODO change this later, once we change Config.To to not be a static address
|
||||
return i.Config.To.ConfigResource()
|
||||
}
|
||||
|
||||
// ResolvedAddr returns the resolved address of an import target, if possible. If not possible, returns an HCL diag
|
||||
// For an ImportTarget originating from the command line, the address is already known
|
||||
// However for an ImportTarget originating from an import block, the full address might not be known initially,
|
||||
// and could only be evaluated down the line. Here, we attempt to resolve the address as though it is a static absolute
|
||||
// traversal, if that's possible
|
||||
func (i *ImportTarget) ResolvedAddr() (address addrs.AbsResourceInstance, evaluationDiags hcl.Diagnostics) {
|
||||
if i.CommandLineImportTarget != nil {
|
||||
address = i.CommandLineImportTarget.Addr
|
||||
} else {
|
||||
// TODO change this later, when Config.To is not a static address
|
||||
address = i.Config.To
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResolvedConfigImportsKey is a key for a map of ImportTargets originating from the configuration
|
||||
// It is used as a one-to-one representation of an EvaluatedConfigImportTarget.
|
||||
// Used in ResolvedImports to maintain a map of all resolved imports when walking the graph
|
||||
type ResolvedConfigImportsKey struct {
|
||||
// An address string is one-to-one with addrs.AbsResourceInstance
|
||||
AddrStr string
|
||||
ID string
|
||||
}
|
||||
|
||||
// ResolvedImports is a struct that maintains a map of all imports as they are being resolved.
|
||||
// This is specifically for imports originating from configuration.
|
||||
// Import targets' addresses are not fully known from the get-go, and could only be resolved later when walking
|
||||
// the graph. This struct helps keep track of the resolved imports, mostly for validation that all imports
|
||||
// have been addressed and point to an actual configuration
|
||||
type ResolvedImports struct {
|
||||
imports map[ResolvedConfigImportsKey]bool
|
||||
}
|
||||
|
||||
// Import takes already-created external resources and brings them
|
||||
|
@ -13,10 +13,7 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hcltest"
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/configs"
|
||||
"github.com/opentofu/opentofu/internal/configs/configschema"
|
||||
"github.com/opentofu/opentofu/internal/providers"
|
||||
"github.com/opentofu/opentofu/internal/states"
|
||||
@ -42,14 +39,15 @@ func TestContextImport_basic(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -94,14 +92,15 @@ resource "aws_instance" "foo" {
|
||||
},
|
||||
}
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(0),
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(0),
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -156,14 +155,15 @@ func TestContextImport_collision(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, state, &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -201,14 +201,15 @@ func TestContextImport_missingType(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -253,14 +254,15 @@ func TestContextImport_moduleProvider(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -309,14 +311,15 @@ func TestContextImport_providerModule(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
_, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.Child("child", addrs.NoKey).ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.Child("child", addrs.NoKey).ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -366,14 +369,15 @@ func TestContextImport_providerConfig(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
SetVariables: InputValues{
|
||||
@ -427,14 +431,15 @@ func TestContextImport_providerConfigResources(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
_, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -499,14 +504,15 @@ data "aws_data_source" "bar" {
|
||||
}),
|
||||
}
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -551,14 +557,15 @@ func TestContextImport_refreshNil(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -593,14 +600,15 @@ func TestContextImport_module(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.Child("child", addrs.IntKey(0)).ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.Child("child", addrs.IntKey(0)).ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -635,14 +643,15 @@ func TestContextImport_moduleDepth2(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
bazExpr := hcl.StaticExpr(cty.StringVal("baz"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.Child("child", addrs.IntKey(0)).Child("nested", addrs.NoKey).ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: bazExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.Child("child", addrs.IntKey(0)).Child("nested", addrs.NoKey).ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -677,14 +686,15 @@ func TestContextImport_moduleDiff(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
bazExpr := hcl.StaticExpr(cty.StringVal("baz"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.Child("child", addrs.IntKey(0)).ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: bazExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.Child("child", addrs.IntKey(0)).ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -746,14 +756,15 @@ func TestContextImport_multiState(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -821,14 +832,15 @@ func TestContextImport_multiStateSame(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
barExpr := hcl.StaticExpr(cty.StringVal("bar"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: barExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -916,14 +928,15 @@ resource "test_resource" "unused" {
|
||||
},
|
||||
})
|
||||
|
||||
testExpr := hcl.StaticExpr(cty.StringVal("test"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "test_resource", "test", addrs.NoKey,
|
||||
),
|
||||
ID: testExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "test_resource", "test", addrs.NoKey,
|
||||
),
|
||||
ID: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -987,14 +1000,15 @@ resource "test_resource" "test" {
|
||||
},
|
||||
})
|
||||
|
||||
testExpr := hcl.StaticExpr(cty.StringVal("test"), configs.SynthBody("import", nil).MissingItemRange())
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "test_resource", "test", addrs.NoKey,
|
||||
),
|
||||
ID: testExpr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "test_resource", "test", addrs.NoKey,
|
||||
),
|
||||
ID: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -1015,7 +1029,6 @@ resource "test_resource" "test" {
|
||||
func TestContextImport_33572(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
m := testModule(t, "issue-33572")
|
||||
bar_expr := hcltest.MockExprLiteral(cty.StringVal("bar"))
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
@ -1037,10 +1050,12 @@ func TestContextImport_33572(t *testing.T) {
|
||||
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
|
||||
Targets: []*ImportTarget{
|
||||
{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: bar_expr,
|
||||
CommandLineImportTarget: &CommandLineImportTarget{
|
||||
Addr: addrs.RootModuleInstance.ResourceInstance(
|
||||
addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
|
||||
),
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -13,6 +13,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
@ -306,6 +308,8 @@ func (c *Context) plan(config *configs.Config, prevRunState *states.State, opts
|
||||
}
|
||||
|
||||
opts.ImportTargets = c.findImportTargets(config, prevRunState)
|
||||
importTargetDiags := c.validateImportTargets(config, opts.ImportTargets)
|
||||
diags = diags.Append(importTargetDiags)
|
||||
plan, walkDiags := c.planWalk(config, prevRunState, opts)
|
||||
diags = diags.Append(walkDiags)
|
||||
|
||||
@ -528,25 +532,19 @@ func (c *Context) postPlanValidateMoves(config *configs.Config, stmts []refactor
|
||||
// All import target addresses with a key must already exist in config.
|
||||
// When we are able to generate config for expanded resources, this rule can be
|
||||
// relaxed.
|
||||
func (c *Context) postPlanValidateImports(config *configs.Config, importTargets []*ImportTarget, allInst instances.Set) tfdiags.Diagnostics {
|
||||
func (c *Context) postPlanValidateImports(resolvedImports *ResolvedImports, allInst instances.Set) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
for _, it := range importTargets {
|
||||
for resolvedImport := range resolvedImports.imports {
|
||||
// We only care about import target addresses that have a key.
|
||||
// If the address does not have a key, we don't need it to be in config
|
||||
// because are able to generate config.
|
||||
if it.Addr.Resource.Key == nil {
|
||||
continue
|
||||
address, addrParseDiags := addrs.ParseAbsResourceInstanceStr(resolvedImport.AddrStr)
|
||||
if addrParseDiags.HasErrors() {
|
||||
return addrParseDiags
|
||||
}
|
||||
|
||||
if !allInst.HasResourceInstance(it.Addr) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Cannot import to non-existent resource address",
|
||||
fmt.Sprintf(
|
||||
"Importing to resource address %s is not possible, because that address does not exist in configuration. Please ensure that the resource key is correct, or remove this import block.",
|
||||
it.Addr,
|
||||
),
|
||||
))
|
||||
if !allInst.HasResourceInstance(address) {
|
||||
diags = diags.Append(importResourceWithoutConfigDiags(address, nil))
|
||||
}
|
||||
}
|
||||
return diags
|
||||
@ -559,8 +557,6 @@ func (c *Context) findImportTargets(config *configs.Config, priorState *states.S
|
||||
for _, ic := range config.Module.Import {
|
||||
if priorState.ResourceInstance(ic.To) == nil {
|
||||
importTargets = append(importTargets, &ImportTarget{
|
||||
Addr: ic.To,
|
||||
ID: ic.ID,
|
||||
Config: ic,
|
||||
})
|
||||
}
|
||||
@ -568,6 +564,23 @@ func (c *Context) findImportTargets(config *configs.Config, priorState *states.S
|
||||
return importTargets
|
||||
}
|
||||
|
||||
func (c *Context) validateImportTargets(config *configs.Config, importTargets []*ImportTarget) (diags tfdiags.Diagnostics) {
|
||||
for _, imp := range importTargets {
|
||||
staticAddress := imp.StaticAddr()
|
||||
descendantConfig := config.Descendent(staticAddress.Module)
|
||||
if descendantConfig == nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Cannot import to non-existent resource address",
|
||||
Detail: fmt.Sprintf("Importing to resource address '%s' is not possible, because that address does not exist in configuration. Please ensure that the resource key is correct, or remove this import block.", staticAddress),
|
||||
Subject: imp.Config.DeclRange.Ptr(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
log.Printf("[DEBUG] Building and walking plan graph for %s", opts.Mode)
|
||||
@ -608,7 +621,7 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, o
|
||||
|
||||
allInsts := walker.InstanceExpander.AllInstances()
|
||||
|
||||
importValidateDiags := c.postPlanValidateImports(config, opts.ImportTargets, allInsts)
|
||||
importValidateDiags := c.postPlanValidateImports(walker.ResolvedImports, allInsts)
|
||||
if importValidateDiags.HasErrors() {
|
||||
return nil, importValidateDiags
|
||||
}
|
||||
|
@ -4557,49 +4557,6 @@ import {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_importTargetWithKeyDoesNotExist(t *testing.T) {
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
resource "test_object" "a" {
|
||||
count = 1
|
||||
test_string = "bar"
|
||||
}
|
||||
|
||||
import {
|
||||
to = test_object.a[42]
|
||||
id = "123"
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
p := simpleMockProvider()
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
||||
NewState: cty.ObjectVal(map[string]cty.Value{
|
||||
"test_string": cty.StringVal("foo"),
|
||||
}),
|
||||
}
|
||||
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "test_object",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"test_string": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("expected error but got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_importIdVariable(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
m := testModule(t, "import-id-variable")
|
||||
@ -4948,6 +4905,193 @@ resource "test_object" "a" {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_importIntoNonExistentModule(t *testing.T) {
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
import {
|
||||
to = module.mod.test_object.a
|
||||
id = "456"
|
||||
}
|
||||
|
||||
`,
|
||||
})
|
||||
p := simpleMockProvider()
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
})
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
if !strings.Contains(diags.Err().Error(), "Cannot import to non-existent resource address") {
|
||||
t.Fatalf("expected error to be \"Cannot import to non-existent resource address\", but it was %s", diags.Err().Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_importIntoNonExistentConfiguration(t *testing.T) {
|
||||
type TestConfiguration struct {
|
||||
Description string
|
||||
inlineConfiguration map[string]string
|
||||
}
|
||||
configurations := []TestConfiguration{
|
||||
{
|
||||
Description: "Basic missing configuration",
|
||||
inlineConfiguration: map[string]string{
|
||||
"main.tf": `
|
||||
import {
|
||||
to = test_object.a
|
||||
id = "123"
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "Wrong module key",
|
||||
inlineConfiguration: map[string]string{
|
||||
"main.tf": `
|
||||
import {
|
||||
to = module.mod["non-existent"].test_object.a
|
||||
id = "123"
|
||||
}
|
||||
|
||||
module "mod" {
|
||||
for_each = {
|
||||
existent = "1"
|
||||
}
|
||||
source = "./mod"
|
||||
}
|
||||
`,
|
||||
"./mod/main.tf": `
|
||||
resource "test_object" "a" {
|
||||
test_string = "bar"
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "Module key without for_each",
|
||||
inlineConfiguration: map[string]string{
|
||||
"main.tf": `
|
||||
import {
|
||||
to = module.mod["non-existent"].test_object.a
|
||||
id = "123"
|
||||
}
|
||||
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
}
|
||||
`,
|
||||
"./mod/main.tf": `
|
||||
resource "test_object" "a" {
|
||||
test_string = "bar"
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "Non-existent resource key - in module",
|
||||
inlineConfiguration: map[string]string{
|
||||
"main.tf": `
|
||||
import {
|
||||
to = module.mod.test_object.a["non-existent"]
|
||||
id = "123"
|
||||
}
|
||||
|
||||
module "mod" {
|
||||
source = "./mod"
|
||||
}
|
||||
`,
|
||||
"./mod/main.tf": `
|
||||
resource "test_object" "a" {
|
||||
for_each = {
|
||||
existent = "1"
|
||||
}
|
||||
test_string = "bar"
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "Non-existent resource key - in root",
|
||||
inlineConfiguration: map[string]string{
|
||||
"main.tf": `
|
||||
import {
|
||||
to = test_object.a[42]
|
||||
id = "123"
|
||||
}
|
||||
|
||||
resource "test_object" "a" {
|
||||
test_string = "bar"
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Description: "Existent module key, non-existent resource key",
|
||||
inlineConfiguration: map[string]string{
|
||||
"main.tf": `
|
||||
import {
|
||||
to = module.mod["existent"].test_object.b
|
||||
id = "123"
|
||||
}
|
||||
|
||||
module "mod" {
|
||||
for_each = {
|
||||
existent = "1"
|
||||
existent_two = "2"
|
||||
}
|
||||
source = "./mod"
|
||||
}
|
||||
`,
|
||||
"./mod/main.tf": `
|
||||
resource "test_object" "a" {
|
||||
test_string = "bar"
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, configuration := range configurations {
|
||||
t.Run(configuration.Description, func(t *testing.T) {
|
||||
m := testModuleInline(t, configuration.inlineConfiguration)
|
||||
|
||||
p := simpleMockProvider()
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
})
|
||||
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
|
||||
var errNum int
|
||||
for _, diag := range diags {
|
||||
if diag.Severity() == tfdiags.Error {
|
||||
errNum++
|
||||
}
|
||||
}
|
||||
if errNum > 1 {
|
||||
t.Fatalf("expected a single error, but got %d", errNum)
|
||||
}
|
||||
|
||||
if !strings.Contains(diags.Err().Error(), "Configuration for import target does not exist") {
|
||||
t.Fatalf("expected error to be \"Configuration for import target does not exist\", but it was %s", diags.Err().Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_importResourceConfigGen(t *testing.T) {
|
||||
addr := mustResourceInstanceAddr("test_object.a")
|
||||
m := testModuleInline(t, map[string]string{
|
||||
@ -5147,6 +5291,9 @@ import {
|
||||
if !diags.HasErrors() {
|
||||
t.Fatalf("expected plan to error, but it did not")
|
||||
}
|
||||
if !strings.Contains(diags.Err().Error(), "Config generation for count and for_each resources not supported") {
|
||||
t.Fatalf("expected error to be \"Config generation for count and for_each resources not supported\", but it is %s", diags.Err().Error())
|
||||
}
|
||||
}
|
||||
|
||||
// config generation still succeeds even when planning fails
|
||||
|
@ -149,6 +149,7 @@ func (c *Context) graphWalker(operation walkOperation, opts *graphWalkOpts) *Con
|
||||
Checks: checkState,
|
||||
InstanceExpander: instances.NewExpander(),
|
||||
MoveResults: opts.MoveResults,
|
||||
ResolvedImports: &ResolvedImports{imports: make(map[ResolvedConfigImportsKey]bool)},
|
||||
Operation: operation,
|
||||
StopContext: c.runContext,
|
||||
PlanTimestamp: opts.PlanTimeTimestamp,
|
||||
|
@ -203,6 +203,14 @@ type EvalContext interface {
|
||||
// objects accessible through it.
|
||||
MoveResults() refactoring.MoveResults
|
||||
|
||||
// ResolvedImports returns a map describing the resolved imports
|
||||
// after evaluating the dynamic address of the import targets
|
||||
//
|
||||
// This data is created during the graph walk, as import target addresses are being resolved
|
||||
// Its primary use is for validation at the end of a plan - To make sure all imports have been satisfied
|
||||
// and have a configuration
|
||||
ResolvedImports() *ResolvedImports
|
||||
|
||||
// WithPath returns a copy of the context with the internal path set to the
|
||||
// path argument.
|
||||
WithPath(path addrs.ModuleInstance) EvalContext
|
||||
|
@ -75,6 +75,7 @@ type BuiltinEvalContext struct {
|
||||
PrevRunStateValue *states.SyncState
|
||||
InstanceExpanderValue *instances.Expander
|
||||
MoveResultsValue refactoring.MoveResults
|
||||
ResolvedImportsValue *ResolvedImports
|
||||
}
|
||||
|
||||
// BuiltinEvalContext implements EvalContext
|
||||
@ -510,3 +511,7 @@ func (ctx *BuiltinEvalContext) InstanceExpander() *instances.Expander {
|
||||
func (ctx *BuiltinEvalContext) MoveResults() refactoring.MoveResults {
|
||||
return ctx.MoveResultsValue
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) ResolvedImports() *ResolvedImports {
|
||||
return ctx.ResolvedImportsValue
|
||||
}
|
||||
|
@ -151,6 +151,9 @@ type MockEvalContext struct {
|
||||
MoveResultsCalled bool
|
||||
MoveResultsResults refactoring.MoveResults
|
||||
|
||||
ResolvedImportsCalled bool
|
||||
ResolvedImportsResults *ResolvedImports
|
||||
|
||||
InstanceExpanderCalled bool
|
||||
InstanceExpanderExpander *instances.Expander
|
||||
}
|
||||
@ -400,6 +403,11 @@ func (c *MockEvalContext) MoveResults() refactoring.MoveResults {
|
||||
return c.MoveResultsResults
|
||||
}
|
||||
|
||||
func (c *MockEvalContext) ResolvedImports() *ResolvedImports {
|
||||
c.ResolvedImportsCalled = true
|
||||
return c.ResolvedImportsResults
|
||||
}
|
||||
|
||||
func (c *MockEvalContext) InstanceExpander() *instances.Expander {
|
||||
c.InstanceExpanderCalled = true
|
||||
return c.InstanceExpanderExpander
|
||||
|
@ -333,13 +333,6 @@ func (b *PlanGraphBuilder) initImport() {
|
||||
// as the new state, and users are not expecting the import process
|
||||
// to update any other instances in state.
|
||||
skipRefresh: true,
|
||||
|
||||
// If we get here, we know that we are in legacy import mode, and
|
||||
// that the user has run the import command rather than plan.
|
||||
// This flag must be propagated down to the
|
||||
// NodePlannableResourceInstance so we can ignore the new import
|
||||
// behaviour.
|
||||
legacyImportMode: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,13 +32,13 @@ type ContextGraphWalker struct {
|
||||
|
||||
// Configurable values
|
||||
Context *Context
|
||||
State *states.SyncState // Used for safe concurrent access to state
|
||||
RefreshState *states.SyncState // Used for safe concurrent access to state
|
||||
PrevRunState *states.SyncState // Used for safe concurrent access to state
|
||||
Changes *plans.ChangesSync // Used for safe concurrent writes to changes
|
||||
Checks *checks.State // Used for safe concurrent writes of checkable objects and their check results
|
||||
InstanceExpander *instances.Expander // Tracks our gradual expansion of module and resource instances
|
||||
Imports []configs.Import
|
||||
State *states.SyncState // Used for safe concurrent access to state
|
||||
RefreshState *states.SyncState // Used for safe concurrent access to state
|
||||
PrevRunState *states.SyncState // Used for safe concurrent access to state
|
||||
Changes *plans.ChangesSync // Used for safe concurrent writes to changes
|
||||
Checks *checks.State // Used for safe concurrent writes of checkable objects and their check results
|
||||
InstanceExpander *instances.Expander // Tracks our gradual expansion of module and resource instances
|
||||
ResolvedImports *ResolvedImports // Tracks import targets as they are being resolved
|
||||
MoveResults refactoring.MoveResults // Read-only record of earlier processing of move statements
|
||||
Operation walkOperation
|
||||
StopContext context.Context
|
||||
@ -103,6 +103,7 @@ func (w *ContextGraphWalker) EvalContext() EvalContext {
|
||||
InstanceExpanderValue: w.InstanceExpander,
|
||||
Plugins: w.Context.plugins,
|
||||
MoveResultsValue: w.MoveResults,
|
||||
ResolvedImportsValue: w.ResolvedImports,
|
||||
ProviderCache: w.providerCache,
|
||||
ProviderInputConfig: w.Context.providerInputConfig,
|
||||
ProviderLock: &w.providerLock,
|
||||
|
@ -215,7 +215,12 @@ func (n *NodeAbstractResource) RootReferences() []*addrs.Reference {
|
||||
var root []*addrs.Reference
|
||||
|
||||
for _, importTarget := range n.importTargets {
|
||||
refs, _ := lang.ReferencesInExpr(addrs.ParseRef, importTarget.ID)
|
||||
// References are only possible in import targets originating from an import block
|
||||
if !importTarget.IsFromImportBlock() {
|
||||
continue
|
||||
}
|
||||
|
||||
refs, _ := lang.ReferencesInExpr(addrs.ParseRef, importTarget.Config.ID)
|
||||
root = append(root, refs...)
|
||||
}
|
||||
|
||||
|
@ -47,10 +47,6 @@ type nodeExpandPlannableResource struct {
|
||||
// structure in the future, as we need to compare for equality and take the
|
||||
// union of multiple groups of dependencies.
|
||||
dependencies []addrs.ConfigResource
|
||||
|
||||
// legacyImportMode is set if the graph is being constructed following an
|
||||
// invocation of the legacy "tofu import" CLI command.
|
||||
legacyImportMode bool
|
||||
}
|
||||
|
||||
var (
|
||||
@ -306,6 +302,33 @@ func (n *nodeExpandPlannableResource) expandResourceInstances(globalCtx EvalCont
|
||||
func (n *nodeExpandPlannableResource) resourceInstanceSubgraph(ctx EvalContext, addr addrs.AbsResource, instanceAddrs []addrs.AbsResourceInstance) (*Graph, error) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
var commandLineImportTargets []CommandLineImportTarget
|
||||
var evaluatedConfigImportTargets []EvaluatedConfigImportTarget
|
||||
// FIXME - Deal with cases of duplicate addresses
|
||||
|
||||
for _, importTarget := range n.importTargets {
|
||||
if importTarget.IsFromImportCommandLine() {
|
||||
commandLineImportTargets = append(commandLineImportTargets, *importTarget.CommandLineImportTarget)
|
||||
} else {
|
||||
importId, evalDiags := evaluateImportIdExpression(importTarget.Config.ID, ctx)
|
||||
if evalDiags.HasErrors() {
|
||||
return nil, evalDiags.Err()
|
||||
}
|
||||
|
||||
evaluatedConfigImportTargets = append(evaluatedConfigImportTargets, EvaluatedConfigImportTarget{
|
||||
Config: importTarget.Config,
|
||||
ID: importId,
|
||||
})
|
||||
|
||||
resolvedImports := ctx.ResolvedImports().imports
|
||||
resolvedImports[ResolvedConfigImportsKey{
|
||||
AddrStr: importTarget.Config.To.String(),
|
||||
ID: importId,
|
||||
}] = true
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Our graph transformers require access to the full state, so we'll
|
||||
// temporarily lock it while we work on this.
|
||||
state := ctx.State().Lock()
|
||||
@ -315,27 +338,14 @@ func (n *nodeExpandPlannableResource) resourceInstanceSubgraph(ctx EvalContext,
|
||||
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
||||
var m *NodePlannableResourceInstance
|
||||
|
||||
// If we're in legacy import mode (the import CLI command), we only need
|
||||
// If we're in the `tofu import` CLI command, we only need
|
||||
// to return the import node, not a plannable resource node.
|
||||
if n.legacyImportMode {
|
||||
for _, importTarget := range n.importTargets {
|
||||
if importTarget.Addr.Equal(a.Addr) {
|
||||
|
||||
// The import ID was supplied as a string on the command
|
||||
// line and made into a synthetic HCL expression.
|
||||
importId, diags := evaluateImportIdExpression(importTarget.ID, ctx)
|
||||
if diags.HasErrors() {
|
||||
// This should be impossible, because the import command
|
||||
// arg parsing builds the synth expression from a
|
||||
// non-null string.
|
||||
panic(fmt.Sprintf("Invalid import id: %s. This is a bug in OpenTofu; please report it!", diags.Err()))
|
||||
}
|
||||
|
||||
return &graphNodeImportState{
|
||||
Addr: importTarget.Addr,
|
||||
ID: importId,
|
||||
ResolvedProvider: n.ResolvedProvider,
|
||||
}
|
||||
for _, c := range commandLineImportTargets {
|
||||
if c.Addr.Equal(a.Addr) {
|
||||
return &graphNodeImportState{
|
||||
Addr: c.Addr,
|
||||
ID: c.ID,
|
||||
ResolvedProvider: n.ResolvedProvider,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -363,15 +373,13 @@ func (n *nodeExpandPlannableResource) resourceInstanceSubgraph(ctx EvalContext,
|
||||
forceReplace: n.forceReplace,
|
||||
}
|
||||
|
||||
for _, importTarget := range n.importTargets {
|
||||
if importTarget.Addr.Equal(a.Addr) {
|
||||
for _, evaluatedConfigImportTarget := range evaluatedConfigImportTargets {
|
||||
// TODO - Change this code once Config.To is not a static address, to actually evaluate it
|
||||
if evaluatedConfigImportTarget.Config.To.Equal(a.Addr) {
|
||||
// If we get here, we're definitely not in legacy import mode,
|
||||
// so go ahead and plan the resource changes including import.
|
||||
m.importTarget = ImportTarget{
|
||||
ID: importTarget.ID,
|
||||
Addr: importTarget.Addr,
|
||||
Config: importTarget.Config,
|
||||
}
|
||||
m.importTarget = evaluatedConfigImportTarget
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,19 @@ type NodePlannableResourceInstance struct {
|
||||
|
||||
// importTarget, if populated, contains the information necessary to plan
|
||||
// an import of this resource.
|
||||
importTarget ImportTarget
|
||||
importTarget EvaluatedConfigImportTarget
|
||||
}
|
||||
|
||||
// EvaluatedConfigImportTarget is a target that we need to import. It's created when an import target originated from
|
||||
// an import block, after everything regarding the configuration has been evaluated.
|
||||
// At this point, the import target is of a single resource instance
|
||||
type EvaluatedConfigImportTarget struct {
|
||||
// Config is the original import block for this import. This might be null
|
||||
// if the import did not originate in config.
|
||||
Config *configs.Import
|
||||
|
||||
// ID is the string ID of the resource to import. This is resource-instance specific.
|
||||
ID string
|
||||
}
|
||||
|
||||
var (
|
||||
@ -161,18 +173,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
|
||||
}
|
||||
}
|
||||
|
||||
importing := n.importTarget.ID != nil
|
||||
var importId string
|
||||
|
||||
if importing {
|
||||
var evalDiags tfdiags.Diagnostics
|
||||
|
||||
importId, evalDiags = evaluateImportIdExpression(n.importTarget.ID, ctx)
|
||||
if evalDiags.HasErrors() {
|
||||
diags = diags.Append(evalDiags)
|
||||
return diags
|
||||
}
|
||||
}
|
||||
importing := n.importTarget.ID != ""
|
||||
|
||||
if importing && n.Config == nil && len(n.generateConfigPath) == 0 {
|
||||
// Then the user wrote an import target to a target that didn't exist.
|
||||
@ -187,12 +188,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
|
||||
// You can't generate config for a resource that is inside a
|
||||
// module, so we will present a different error message for
|
||||
// this case.
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Import block target does not exist",
|
||||
Detail: "The target for the given import block does not exist. The specified target is within a module, and must be defined as a resource within that module before anything can be imported.",
|
||||
Subject: n.importTarget.Config.DeclRange.Ptr(),
|
||||
})
|
||||
diags = diags.Append(importResourceWithoutConfigDiags(n.Addr, n.importTarget.Config))
|
||||
}
|
||||
return diags
|
||||
}
|
||||
@ -200,7 +196,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
|
||||
// If the resource is to be imported, we now ask the provider for an Import
|
||||
// and a Refresh, and save the resulting state to instanceRefreshState.
|
||||
if importing {
|
||||
instanceRefreshState, diags = n.importState(ctx, addr, importId, provider, providerSchema)
|
||||
instanceRefreshState, diags = n.importState(ctx, addr, n.importTarget.ID, provider, providerSchema)
|
||||
} else {
|
||||
var readDiags tfdiags.Diagnostics
|
||||
instanceRefreshState, readDiags = n.readResourceInstanceState(ctx, addr)
|
||||
@ -310,7 +306,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
|
||||
}
|
||||
|
||||
if importing {
|
||||
change.Importing = &plans.Importing{ID: importId}
|
||||
change.Importing = &plans.Importing{ID: n.importTarget.ID}
|
||||
}
|
||||
|
||||
// FIXME: here we udpate the change to reflect the reason for
|
||||
@ -509,7 +505,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
|
||||
}))
|
||||
|
||||
if imported[0].TypeName == "" {
|
||||
diags = diags.Append(fmt.Errorf("import of %s didn't set type", n.importTarget.Addr.String()))
|
||||
diags = diags.Append(fmt.Errorf("import of %s didn't set type", n.Addr.String()))
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
@ -528,7 +524,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
|
||||
|
||||
// refresh
|
||||
riNode := &NodeAbstractResourceInstance{
|
||||
Addr: n.importTarget.Addr,
|
||||
Addr: n.Addr,
|
||||
NodeAbstractResource: NodeAbstractResource{
|
||||
ResolvedProvider: n.ResolvedProvider,
|
||||
},
|
||||
@ -552,7 +548,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
|
||||
"is correct and that it is associated with the provider's "+
|
||||
"configured region or endpoint, or use \"tofu apply\" to "+
|
||||
"create a new remote object for this resource.",
|
||||
n.importTarget.Addr,
|
||||
n.Addr,
|
||||
),
|
||||
))
|
||||
return instanceRefreshState, diags
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/configs"
|
||||
"github.com/opentofu/opentofu/internal/dag"
|
||||
@ -104,7 +106,7 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config, ge
|
||||
// Only include import targets that are targeting the current module.
|
||||
var importTargets []*ImportTarget
|
||||
for _, target := range t.importTargets {
|
||||
if targetModule := target.Addr.Module.Module(); targetModule.Equal(config.Path) {
|
||||
if targetModule := target.StaticAddr().Module; targetModule.Equal(config.Path) {
|
||||
importTargets = append(importTargets, target)
|
||||
}
|
||||
}
|
||||
@ -124,7 +126,7 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config, ge
|
||||
|
||||
var matchedIndices []int
|
||||
for ix, i := range importTargets {
|
||||
if target := i.Addr.ContainingResource().Config(); target.Equal(configAddr) {
|
||||
if target := i.StaticAddr(); target.Equal(configAddr) {
|
||||
// This import target has been claimed by an actual resource,
|
||||
// let's make a note of this to remove it from the targets.
|
||||
matchedIndices = append(matchedIndices, ix)
|
||||
@ -173,25 +175,53 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config, ge
|
||||
// TODO: We could actually catch and process these kind of problems earlier,
|
||||
// this is something that could be done during the Validate process.
|
||||
for _, i := range importTargets {
|
||||
// The case in which an unmatched import block targets an expanded
|
||||
// resource instance can error here. Others can error later.
|
||||
if i.Addr.Resource.Key != addrs.NoKey {
|
||||
return fmt.Errorf("Config generation for count and for_each resources not supported.\n\nYour configuration contains an import block with a \"to\" address of %s. This resource instance does not exist in configuration.\n\nIf you intended to target a resource that exists in configuration, please double-check the address. Otherwise, please remove this import block or re-run the plan without the -generate-config-out flag to ignore the import block.", i.Addr)
|
||||
// We should only allow config generation for static addresses
|
||||
// If config generation has been attempted for a non static address - we will fail here
|
||||
address, evaluationDiags := i.ResolvedAddr()
|
||||
if evaluationDiags.HasErrors() {
|
||||
return evaluationDiags
|
||||
}
|
||||
|
||||
abstract := &NodeAbstractResource{
|
||||
Addr: i.Addr.ConfigResource(),
|
||||
importTargets: []*ImportTarget{i},
|
||||
generateConfigPath: generateConfigPath,
|
||||
}
|
||||
// In case of config generation - We can error early here in two cases:
|
||||
// 1. When attempting to import a resource with a key (Config generation for count / for_each resources)
|
||||
// 2. When attempting to import a resource inside a module.
|
||||
if len(generateConfigPath) > 0 {
|
||||
if address.Resource.Key != addrs.NoKey {
|
||||
return fmt.Errorf("Config generation for count and for_each resources not supported.\n\nYour configuration contains an import block with a \"to\" address of %s. This resource instance does not exist in configuration.\n\nIf you intended to target a resource that exists in configuration, please double-check the address. Otherwise, please remove this import block or re-run the plan without the -generate-config-out flag to ignore the import block.", address)
|
||||
}
|
||||
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
// Create a node with the resource and import target. This node will take care of the config generation
|
||||
abstract := &NodeAbstractResource{
|
||||
Addr: address.ConfigResource(),
|
||||
importTargets: []*ImportTarget{i},
|
||||
generateConfigPath: generateConfigPath,
|
||||
}
|
||||
|
||||
g.Add(node)
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
|
||||
g.Add(node)
|
||||
} else {
|
||||
return importResourceWithoutConfigDiags(address, i.Config)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// importResourceWithoutConfigDiags creates the common HCL error of an attempted import for a non-existent configuration
|
||||
func importResourceWithoutConfigDiags(address addrs.AbsResourceInstance, config *configs.Import) *hcl.Diagnostic {
|
||||
diag := hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Configuration for import target does not exist",
|
||||
Detail: fmt.Sprintf("The configuration for the given import %s does not exist. All target instances must have an associated configuration to be imported.", address),
|
||||
}
|
||||
|
||||
if config != nil {
|
||||
diag.Subject = config.DeclRange.Ptr()
|
||||
}
|
||||
|
||||
return &diag
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user