-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
configs: add decodeMovedBlock behind a locked gate. (#28973)
This PR adds decoding for the upcoming "moved" blocks in configuration. This code is gated behind an experiment called EverythingIsAPlan, but the experiment is not registered as an active experiment, so it will never run (there is a test in place which will fail if the experiment is ever registered). This also adds a new function to the Targetable interface, AddrType, to simplifying comparing two addrs.Targetable. There is some validation missing still: this does not (yet) descend into resources to see if the actual resource types are the same (I've put this off in part because we will eventually need the provider schema to verify aliased resources, so I suspect this validation will have to happen later on).
- Loading branch information
1 parent
bb86860
commit 3acb5e2
Showing
10 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package configs | ||
|
||
import ( | ||
"github.com/hashicorp/hcl/v2" | ||
"github.com/hashicorp/terraform/internal/addrs" | ||
) | ||
|
||
type Moved struct { | ||
From *addrs.Target | ||
To *addrs.Target | ||
|
||
DeclRange hcl.Range | ||
} | ||
|
||
func decodeMovedBlock(block *hcl.Block) (*Moved, hcl.Diagnostics) { | ||
var diags hcl.Diagnostics | ||
moved := &Moved{ | ||
DeclRange: block.DefRange, | ||
} | ||
|
||
content, moreDiags := block.Body.Content(movedBlockSchema) | ||
diags = append(diags, moreDiags...) | ||
|
||
if attr, exists := content.Attributes["from"]; exists { | ||
from, traversalDiags := hcl.AbsTraversalForExpr(attr.Expr) | ||
diags = append(diags, traversalDiags...) | ||
if !traversalDiags.HasErrors() { | ||
from, fromDiags := addrs.ParseTarget(from) | ||
diags = append(diags, fromDiags.ToHCL()...) | ||
moved.From = from | ||
} | ||
} | ||
|
||
if attr, exists := content.Attributes["to"]; exists { | ||
to, traversalDiags := hcl.AbsTraversalForExpr(attr.Expr) | ||
diags = append(diags, traversalDiags...) | ||
if !traversalDiags.HasErrors() { | ||
to, toDiags := addrs.ParseTarget(to) | ||
diags = append(diags, toDiags.ToHCL()...) | ||
moved.To = to | ||
} | ||
} | ||
|
||
// we can only move from a module to a module, resource to resource, etc. | ||
if !diags.HasErrors() { | ||
if moved.To.Subject.AddrType() != moved.From.Subject.AddrType() { | ||
diags = diags.Append(&hcl.Diagnostic{ | ||
Severity: hcl.DiagError, | ||
Summary: "Invalid \"moved\" targets", | ||
Detail: "The \"from\" and \"to\" targets must be the same address type", | ||
Subject: &moved.DeclRange, | ||
}) | ||
} | ||
} | ||
|
||
return moved, diags | ||
} | ||
|
||
var movedBlockSchema = &hcl.BodySchema{ | ||
Attributes: []hcl.AttributeSchema{ | ||
{ | ||
Name: "from", | ||
Required: true, | ||
}, | ||
{ | ||
Name: "to", | ||
Required: true, | ||
}, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
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" | ||
) | ||
|
||
func TestDecodeMovedBlock(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}, | ||
} | ||
|
||
foo_expr := hcltest.MockExprTraversalSrc("test_instance.foo") | ||
bar_expr := hcltest.MockExprTraversalSrc("test_instance.bar") | ||
|
||
foo_index_expr := hcltest.MockExprTraversalSrc("test_instance.foo[1]") | ||
bar_index_expr := hcltest.MockExprTraversalSrc("test_instance.bar[\"one\"]") | ||
|
||
mod_foo_expr := hcltest.MockExprTraversalSrc("module.foo") | ||
mod_bar_expr := hcltest.MockExprTraversalSrc("module.bar") | ||
|
||
tests := map[string]struct { | ||
input *hcl.Block | ||
want *Moved | ||
err string | ||
}{ | ||
"success": { | ||
&hcl.Block{ | ||
Type: "moved", | ||
Body: hcltest.MockBody(&hcl.BodyContent{ | ||
Attributes: hcl.Attributes{ | ||
"from": { | ||
Name: "from", | ||
Expr: foo_expr, | ||
}, | ||
"to": { | ||
Name: "to", | ||
Expr: bar_expr, | ||
}, | ||
}, | ||
}), | ||
DefRange: blockRange, | ||
}, | ||
&Moved{ | ||
From: mustTargetFromExpr(foo_expr), | ||
To: mustTargetFromExpr(bar_expr), | ||
DeclRange: blockRange, | ||
}, | ||
``, | ||
}, | ||
"indexed resources": { | ||
&hcl.Block{ | ||
Type: "moved", | ||
Body: hcltest.MockBody(&hcl.BodyContent{ | ||
Attributes: hcl.Attributes{ | ||
"from": { | ||
Name: "from", | ||
Expr: foo_index_expr, | ||
}, | ||
"to": { | ||
Name: "to", | ||
Expr: bar_index_expr, | ||
}, | ||
}, | ||
}), | ||
DefRange: blockRange, | ||
}, | ||
&Moved{ | ||
From: mustTargetFromExpr(foo_index_expr), | ||
To: mustTargetFromExpr(bar_index_expr), | ||
DeclRange: blockRange, | ||
}, | ||
``, | ||
}, | ||
"modules": { | ||
&hcl.Block{ | ||
Type: "moved", | ||
Body: hcltest.MockBody(&hcl.BodyContent{ | ||
Attributes: hcl.Attributes{ | ||
"from": { | ||
Name: "from", | ||
Expr: mod_foo_expr, | ||
}, | ||
"to": { | ||
Name: "to", | ||
Expr: mod_bar_expr, | ||
}, | ||
}, | ||
}), | ||
DefRange: blockRange, | ||
}, | ||
&Moved{ | ||
From: mustTargetFromExpr(mod_foo_expr), | ||
To: mustTargetFromExpr(mod_bar_expr), | ||
DeclRange: blockRange, | ||
}, | ||
``, | ||
}, | ||
"error: missing argument": { | ||
&hcl.Block{ | ||
Type: "moved", | ||
Body: hcltest.MockBody(&hcl.BodyContent{ | ||
Attributes: hcl.Attributes{ | ||
"from": { | ||
Name: "from", | ||
Expr: foo_expr, | ||
}, | ||
}, | ||
}), | ||
DefRange: blockRange, | ||
}, | ||
&Moved{ | ||
From: mustTargetFromExpr(foo_expr), | ||
DeclRange: blockRange, | ||
}, | ||
"Missing required argument", | ||
}, | ||
"error: type mismatch": { | ||
&hcl.Block{ | ||
Type: "moved", | ||
Body: hcltest.MockBody(&hcl.BodyContent{ | ||
Attributes: hcl.Attributes{ | ||
"to": { | ||
Name: "to", | ||
Expr: foo_expr, | ||
}, | ||
"from": { | ||
Name: "from", | ||
Expr: mod_foo_expr, | ||
}, | ||
}, | ||
}), | ||
DefRange: blockRange, | ||
}, | ||
&Moved{ | ||
To: mustTargetFromExpr(foo_expr), | ||
From: mustTargetFromExpr(mod_foo_expr), | ||
DeclRange: blockRange, | ||
}, | ||
"Invalid \"moved\" targets", | ||
}, | ||
} | ||
|
||
for name, test := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
got, diags := decodeMovedBlock(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.Fatal("expected error") | ||
} | ||
|
||
if !cmp.Equal(got, test.want) { | ||
t.Fatalf("wrong result: %s", cmp.Diff(got, test.want)) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func mustTargetFromExpr(expr hcl.Expression) *addrs.Target { | ||
traversal, hcldiags := hcl.AbsTraversalForExpr(expr) | ||
if hcldiags.HasErrors() { | ||
panic(hcldiags.Errs()) | ||
} | ||
|
||
target, diags := addrs.ParseTarget(traversal) | ||
if diags.HasErrors() { | ||
panic(diags.Err()) | ||
} | ||
|
||
return target | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
internal/configs/testdata/invalid-files/everything-is-a-plan.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# experiments.EverythingIsAPlan exists but is not registered as an active (or | ||
# concluded) experiment, so this should fail until the experiment "gate" is | ||
# removed. | ||
terraform { | ||
experiments = [everything_is_a_plan] | ||
} | ||
|
||
moved { | ||
from = test_instance.foo | ||
to = test_instance.bar | ||
} |
Oops, something went wrong.