Skip to content

Commit

Permalink
Support import blocks in config
Browse files Browse the repository at this point in the history
Fixes #22219
  • Loading branch information
tmccombs committed Sep 30, 2022
1 parent 0cc752e commit af92500
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 3 deletions.
11 changes: 11 additions & 0 deletions internal/addrs/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,17 @@ func (r AbsResourceInstance) Less(o AbsResourceInstance) bool {
}
}

// InModule returns a new AbsResourceInstance with the module path
// prefixed by module
func (r AbsResourceInstance) InModule(module ModuleInstance) AbsResourceInstance {
modPath := make(ModuleInstance, 0, len(module)+len(r.Module))
modPath = append(modPath, module...)
modPath = append(modPath, r.Module...)
ret := r // copy address
ret.Module = modPath
return ret
}

// AbsResourceInstance is a Checkable
func (r AbsResourceInstance) checkableSigil() {}

Expand Down
83 changes: 83 additions & 0 deletions internal/configs/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package configs

import (
"fmt"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/zclconf/go-cty/cty"
)

type Import struct {
ID string
To addrs.AbsResourceInstance

DeclRange hcl.Range
}

func decodeImportBlock(block *hcl.Block) (*Import, hcl.Diagnostics) {
var diags hcl.Diagnostics
imp := &Import{
DeclRange: block.DefRange,
}

content, moreDiags := block.Body.Content(importBlockSchema)
diags = append(diags, moreDiags...)

if attr, exists := content.Attributes["id"]; exists {
id, idDiags := decodeId(attr.Expr)
diags = append(diags, idDiags...)
imp.ID = id
}

if attr, exists := content.Attributes["to"]; exists {
to, traversalDiags := hcl.AbsTraversalForExpr(attr.Expr)
diags = append(diags, traversalDiags...)
if !traversalDiags.HasErrors() {
to, toDiags := addrs.ParseAbsResourceInstance(to)
diags = append(diags, toDiags.ToHCL()...)
imp.To = to
if !toDiags.HasErrors() && to.Resource.Resource.Mode != addrs.ManagedResourceMode {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid Resource",
Detail: fmt.Sprintf("%v is not a managed resource. Importing into a data source is not allowed.", to),
Subject: attr.Expr.Range().Ptr(),
})
}
}
}

return imp, diags

}

func decodeId(expr hcl.Expression) (string, hcl.Diagnostics) {
id, diags := expr.Value(nil)
if diags.HasErrors() {
return "", diags
}
if id.Type() != cty.String {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid Attribute",
Detail: fmt.Sprintf("Invalid attribute value for import id: %#v", id),
Subject: expr.Range().Ptr(),
})
return "", diags
}
return id.AsString(), diags
}

var importBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "id",
Required: true,
},
{
Name: "to",
Required: true,
},
},
}
181 changes: 181 additions & 0 deletions internal/configs/import_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package configs

import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcltest"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/zclconf/go-cty/cty"
)

func TestImportBlock_decode(t *testing.T) {
blockRange := hcl.Range{
Filename: "mock.tf",
Start: hcl.Pos{Line: 3, Column: 12, Byte: 27},
End: hcl.Pos{Line: 3, Column: 19, Byte: 34},
}

id := "test1"
id_expr := hcltest.MockExprLiteral(cty.StringVal(id))

res_expr := hcltest.MockExprTraversalSrc("test_instance.foo")

index_expr := hcltest.MockExprTraversalSrc("test_instance.foo[1]")

mod_expr := hcltest.MockExprTraversalSrc("module.foo.test_instance.this")

tests := map[string]struct {
input *hcl.Block
want *Import
err string
}{
"success": {
&hcl.Block{
Type: "import",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"id": {
Name: "id",
Expr: id_expr,
},
"to": {
Name: "to",
Expr: res_expr,
},
},
}),
DefRange: blockRange,
},
&Import{
Id: id,
To: mustImportEndpointFromExpr(res_expr),
DeclRange: blockRange,
},
``,
},
"indexed_resource": {
&hcl.Block{
Type: "import",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"id": {
Name: "id",
Expr: id_expr,
},
"to": {
Name: "to",
Expr: index_expr,
},
},
}),
DefRange: blockRange,
},
&Import{
Id: id,
To: mustImportEndpointFromExpr(index_expr),
DeclRange: blockRange,
},
``,
},
"module": {
&hcl.Block{
Type: "import",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"id": {
Name: "id",
Expr: id_expr,
},
"to": {
Name: "to",
Expr: mod_expr,
},
},
}),
DefRange: blockRange,
},
&Import{
Id: id,
To: mustImportEndpointFromExpr(mod_expr),
DeclRange: blockRange,
},
``,
},
"error: missing argument": {
&hcl.Block{
Type: "import",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"id": {
Name: "id",
Expr: id_expr,
},
},
}),
DefRange: blockRange,
},
&Import{
Id: id,
DeclRange: blockRange,
},
"Missing required argument",
},
"error: type mismatch": {
&hcl.Block{
Type: "import",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcl.Attributes{
"id": {
Name: "id",
Expr: hcltest.MockExprLiteral(cty.NumberIntVal(0)),
},
"to": {
Name: "to",
Expr: res_expr,
},
},
}),
DefRange: blockRange,
},
&Import{
To: mustImportEndpointFromExpr(res_expr),
DeclRange: blockRange,
},
"Invalid Attribute",
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
got, diags := decodeImportBlock(test.input)

if diags.HasErrors() {
if test.err == "" {
t.Fatalf("unexpected error: %s", diags.Errs())
}
if gotErr := diags[0].Summary; gotErr != test.err {
t.Errorf("wrong error, got %q, want %q", gotErr, test.err)
}
} else if test.err != "" {
t.Fatalf("expected error")
}
if !cmp.Equal(got, test.want, cmp.AllowUnexported(addrs.AbsResourceInstance{})) {
t.Fatalf("wrong result: %s", cmp.Diff(got, test.want))
}
})
}
}

func mustImportEndpointFromExpr(expr hcl.Expression) addrs.AbsResourceInstance {
traversal, hcldiags := hcl.AbsTraversalForExpr(expr)
if hcldiags.HasErrors() {
panic(hcldiags.Errs())
}
ep, diags := addrs.ParseAbsResourceInstance(traversal)
if diags.HasErrors() {
panic(diags.Err())
}
return ep
}
18 changes: 15 additions & 3 deletions internal/configs/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ type Module struct {
ManagedResources map[string]*Resource
DataResources map[string]*Resource

Moved []*Moved
Moved []*Moved
Import []*Import
}

// File describes the contents of a single configuration file.
Expand Down Expand Up @@ -78,7 +79,8 @@ type File struct {
ManagedResources []*Resource
DataResources []*Resource

Moved []*Moved
Moved []*Moved
Import []*Import
}

// NewModule takes a list of primary files and a list of override files and
Expand Down Expand Up @@ -357,10 +359,11 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics {
}
}

// "Moved" blocks just append, because they are all independent
// "Moved" and "Import" blocks just append, because they are all independent
// of one another at this level. (We handle any references between
// them at runtime.)
m.Moved = append(m.Moved, file.Moved...)
m.Import = append(m.Import, file.Import...)

return diags
}
Expand Down Expand Up @@ -543,6 +546,15 @@ func (m *Module) mergeFile(file *File) hcl.Diagnostics {
})
}

for _, i := range file.Import {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Cannot override 'import' blocks",
Detail: "Records of import objects can appear only in normal files, not in override files.",
Subject: i.DeclRange.Ptr(),
})
}

return diags
}

Expand Down
10 changes: 10 additions & 0 deletions internal/configs/parser_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,13 @@ func (p *Parser) loadConfigFile(path string, override bool) (*File, hcl.Diagnost
file.Moved = append(file.Moved, cfg)
}

case "import":
cfg, cfgDiags := decodeImportBlock(block)
diags = append(diags, cfgDiags...)
if cfg != nil {
file.Import = append(file.Import, cfg)
}

default:
// Should never happen because the above cases should be exhaustive
// for all block type names in our schema.
Expand Down Expand Up @@ -252,6 +259,9 @@ var configFileSchema = &hcl.BodySchema{
{
Type: "moved",
},
{
Type: "import",
},
},
}

Expand Down
Loading

0 comments on commit af92500

Please sign in to comment.