Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
df23c70
import block decode
jbardin Aug 8, 2023
f4db7e3
refactor for_each evaluation
jbardin Sep 11, 2023
e63b5b1
use new for_each evaluator for import
jbardin Sep 18, 2023
9b30473
evaluate import for_each and to
jbardin Sep 14, 2023
5128b21
skip imports for existing resources
jbardin Sep 15, 2023
0c005ce
remove legacyImportMode flag
jbardin Sep 15, 2023
5f13eec
remove ImportTarget.ID field
jbardin Sep 15, 2023
5786f82
improve import diagnostic handling
jbardin Sep 16, 2023
e130c7b
correct check for duplicate imports
jbardin Sep 16, 2023
41fabcf
don't add identical config-gen resource nodes
jbardin Sep 18, 2023
ba6f471
further refine import validation
jbardin Sep 19, 2023
7f6e320
handle legacy import in new expansion node
jbardin Sep 20, 2023
e1a8270
expose ImportTarget.IDString for legacy import
jbardin Sep 21, 2023
6ee08c7
add new context_plan_import_test.go file
jbardin Sep 21, 2023
e3cb32c
first import for_each tests
jbardin Sep 21, 2023
167f235
partial for_each import
jbardin Sep 22, 2023
e522134
import id ref in module
jbardin Sep 22, 2023
1f260f0
import for_each from data source
jbardin Sep 22, 2023
1831536
fix to expression target error
jbardin Sep 22, 2023
8619450
remove unused method
jbardin Sep 22, 2023
2c4608f
update import validate test
jbardin Sep 22, 2023
59d8aa0
check for dynamic for_each during validation
jbardin Sep 22, 2023
a2b189f
handle to expression from json files
jbardin Sep 25, 2023
66637ed
use new unwrapJSONRefExpr for replace_triggered_by
jbardin Sep 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions internal/command/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend"
Expand Down Expand Up @@ -236,11 +235,8 @@ func (c *ImportCommand) Run(args []string) int {
newState, importDiags := lr.Core.Import(lr.Config, lr.InputState, &terraform.ImportOpts{
Targets: []*terraform.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()),
LegacyAddr: addr,
IDString: args[1],
Comment on lines +238 to +239
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered adding "legacy" fields to ImportTarget in #33618 but decided on the HCL synth kludge instead. Glad we are removing it.

},
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@
"range": {
"filename": "testdata/validate-invalid/duplicate_import_targets/main.tf",
"start": {
"line": 9,
"column": 1,
"byte": 85
"line": 10,
"column": 8,
"byte": 101
},
"end": {
"line": 9,
"column": 7,
"byte": 91
"line": 10,
"column": 24,
"byte": 117
}
},
"snippet": {
"context": null,
"code": "import {",
"start_line": 9,
"highlight_start_offset": 0,
"highlight_end_offset": 6,
"context": "import",
"code": " to = aws_instance.web",
"start_line": 10,
"highlight_start_offset": 7,
"highlight_end_offset": 23,
"values": []
}
}
Expand Down
8 changes: 5 additions & 3 deletions internal/configs/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,10 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse
reqs[fqn] = nil
}
for _, i := range c.Module.Import {
implied, err := addrs.ParseProviderPart(i.To.Resource.Resource.ImpliedProvider())
implied, err := addrs.ParseProviderPart(i.ToResource.Resource.ImpliedProvider())
if err == nil {
// FIXME: this will not resolve correctly if the target module uses
// a different local name from the root module.
provider := c.Module.ImpliedProviderForUnqualifiedType(implied)
if _, exists := reqs[provider]; exists {
// Explicit dependency already present
Expand All @@ -452,14 +454,14 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse
// this will be because the user has written explicit provider arguments
// that don't agree and we'll get them to fix it.
for _, i := range c.Module.Import {
if len(i.To.Module) > 0 {
if len(i.ToResource.Module) > 0 {
// All provider information for imports into modules should come
// from the module block, so we don't need to load anything for
// import targets within modules.
continue
}

if target, exists := c.Module.ManagedResources[i.To.String()]; exists {
if target, exists := c.Module.ManagedResources[i.ToResource.Resource.String()]; exists {
// This means the information about the provider for this import
// should come from the resource block itself and not the import
// block.
Expand Down
152 changes: 144 additions & 8 deletions internal/configs/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,23 @@ package configs

import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
hcljson "github.com/hashicorp/hcl/v2/json"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)

type Import struct {
ID hcl.Expression
To addrs.AbsResourceInstance

To hcl.Expression
// The To address may not be resolvable immediately if it contains dynamic
// index expressions, so we will extract the ConfigResource address and
// store it here for reference.
ToResource addrs.ConfigResource

ForEach hcl.Expression

ProviderConfigRef *ProviderConfigRef
Provider addrs.Provider
Expand All @@ -33,17 +44,25 @@ func decodeImportBlock(block *hcl.Block) (*Import, hcl.Diagnostics) {
}

if attr, exists := content.Attributes["to"]; exists {
traversal, traversalDiags := hcl.AbsTraversalForExpr(attr.Expr)
diags = append(diags, traversalDiags...)
if !traversalDiags.HasErrors() {
to, toDiags := addrs.ParseAbsResourceInstance(traversal)
diags = append(diags, toDiags.ToHCL()...)
imp.To = to
toExpr, jsDiags := unwrapJSONRefExpr(attr.Expr)
diags = diags.Extend(jsDiags)
if diags.HasErrors() {
return imp, diags
}

imp.To = toExpr

addr, toDiags := parseConfigResourceFromExpression(imp.To)
diags = diags.Extend(toDiags.ToHCL())
imp.ToResource = addr
}

if attr, exists := content.Attributes["for_each"]; exists {
imp.ForEach = attr.Expr
}

if attr, exists := content.Attributes["provider"]; exists {
if len(imp.To.Module) > 0 {
if len(imp.ToResource.Module) > 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid import provider argument",
Expand All @@ -66,6 +85,9 @@ var importBlockSchema = &hcl.BodySchema{
{
Name: "provider",
},
{
Name: "for_each",
},
{
Name: "id",
Required: true,
Expand All @@ -76,3 +98,117 @@ var importBlockSchema = &hcl.BodySchema{
},
},
}

// parseResourceInstanceFromExpression takes an arbitrary expression
// representing a resource instance, and parses out the static ConfigResource
// skipping an variable index expressions. This is used to connect an import
// block's "to" to the configuration address before the full instance
// expressions are evaluated.
func parseConfigResourceFromExpression(expr hcl.Expression) (addrs.ConfigResource, tfdiags.Diagnostics) {
traversal, hcdiags := exprToResourceTraversal(expr)
if hcdiags.HasErrors() {
return addrs.ConfigResource{}, tfdiags.Diagnostics(nil).Append(hcdiags)
}

addr, diags := addrs.ParseAbsResourceInstance(traversal)
if diags.HasErrors() {
return addrs.ConfigResource{}, diags
}

return addr.ConfigResource(), diags
}

// unwrapJSONRefExpr takes a string expression from a JSON configuration,
// and re-evaluates the string as HCL. If the expression is not JSON, the
// original expression is returned directly.
func unwrapJSONRefExpr(expr hcl.Expression) (hcl.Expression, hcl.Diagnostics) {
if !hcljson.IsJSONExpression(expr) {
return expr, nil
}

// We can abuse the hcl json api and rely on the fact that calling
// Value on a json expression with no EvalContext will return the
// raw string. We can then parse that as normal hcl syntax, and
// continue with the decoding.
v, diags := expr.Value(nil)
if diags.HasErrors() {
return nil, diags
}

// the JSON representation can only be a string
if v.Type() != cty.String {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference expression",
Detail: "A single reference string is required.",
Subject: expr.Range().Ptr(),
})

return nil, diags
}

rng := expr.Range()
expr, ds := hclsyntax.ParseExpression([]byte(v.AsString()), rng.Filename, rng.Start)
diags = diags.Extend(ds)
return expr, diags
}

// exprToResourceTraversal is used to parse the import block's to expression,
// which must be a resource instance, but may contain limited variables with
// index expressions. Since we only need the ConfigResource to connect the
// import to the configuration, we skip any index expressions.
func exprToResourceTraversal(expr hcl.Expression) (hcl.Traversal, hcl.Diagnostics) {
var trav hcl.Traversal
var diags hcl.Diagnostics

switch e := expr.(type) {
case *hclsyntax.RelativeTraversalExpr:
t, d := exprToResourceTraversal(e.Source)
diags = diags.Extend(d)
trav = append(trav, t...)
trav = append(trav, e.Traversal...)

case *hclsyntax.ScopeTraversalExpr:
// a static reference, we can just append the traversal
trav = append(trav, e.Traversal...)

case *hclsyntax.IndexExpr:
// Get the collection from the index expression, we don't need the
// index for a ConfigResource
t, d := exprToResourceTraversal(e.Collection)
diags = diags.Extend(d)
if diags.HasErrors() {
return nil, diags
}
trav = append(trav, t...)

default:
// if we don't recognise the expression type (which means we are likely
// dealing with a test mock), try and interpret this as an absolute
// traversal
t, d := hcl.AbsTraversalForExpr(e)
diags = diags.Extend(d)
trav = append(trav, t...)
}

return trav, diags
}

// parseImportToStatic attempts to parse the To address of an import block
// statically to get the resource address. This returns false when the address
// cannot be parsed, which is usually a result of dynamic index expressions
// using for_each.
func parseImportToStatic(expr hcl.Expression) (addrs.AbsResourceInstance, bool) {
// we may have a nil expression in some error cases, which we can just
// false to avoid the parsing
if expr == nil {
return addrs.AbsResourceInstance{}, false
}

var toDiags tfdiags.Diagnostics
traversal, hd := hcl.AbsTraversalForExpr(expr)
toDiags = toDiags.Append(hd)
to, td := addrs.ParseAbsResourceInstance(traversal)
toDiags = toDiags.Append(td)
return to, !toDiags.HasErrors()
}
Loading