Merge pull request #29943 from hashicorp/kmoe/moved-block-resource-type
refactoring: error when changing resource type during move
This commit is contained in:
commit
18ab3512fa
|
@ -19,7 +19,7 @@ import (
|
||||||
// prompt creating a different plan than Terraform would by default.
|
// prompt creating a different plan than Terraform would by default.
|
||||||
//
|
//
|
||||||
// To obtain a full address from a MoveEndpoint you must use
|
// To obtain a full address from a MoveEndpoint you must use
|
||||||
// either the package function UnifyMoveEndpoints (to get an AbsMovable) or
|
// either the package function UnifyMoveEndpoints (to get an AbsMoveable) or
|
||||||
// the method ConfigMoveable (to get a ConfigMoveable).
|
// the method ConfigMoveable (to get a ConfigMoveable).
|
||||||
type MoveEndpoint struct {
|
type MoveEndpoint struct {
|
||||||
// SourceRange is the location of the physical endpoint address
|
// SourceRange is the location of the physical endpoint address
|
||||||
|
@ -27,15 +27,15 @@ type MoveEndpoint struct {
|
||||||
// configuration expresson.
|
// configuration expresson.
|
||||||
SourceRange tfdiags.SourceRange
|
SourceRange tfdiags.SourceRange
|
||||||
|
|
||||||
// Internally we (ab)use AbsMovable as the representation of our
|
// Internally we (ab)use AbsMoveable as the representation of our
|
||||||
// relative address, even though everywhere else in Terraform
|
// relative address, even though everywhere else in Terraform
|
||||||
// AbsMovable always represents a fully-absolute address.
|
// AbsMoveable always represents a fully-absolute address.
|
||||||
// In practice, due to the implementation of ParseMoveEndpoint,
|
// In practice, due to the implementation of ParseMoveEndpoint,
|
||||||
// this is always either a ModuleInstance or an AbsResourceInstance,
|
// this is always either a ModuleInstance or an AbsResourceInstance,
|
||||||
// and we only consider the possibility of interpreting it as
|
// and we only consider the possibility of interpreting it as
|
||||||
// a AbsModuleCall or an AbsResource in UnifyMoveEndpoints.
|
// a AbsModuleCall or an AbsResource in UnifyMoveEndpoints.
|
||||||
// This is intentionally unexported to encapsulate this unusual
|
// This is intentionally unexported to encapsulate this unusual
|
||||||
// meaning of AbsMovable.
|
// meaning of AbsMoveable.
|
||||||
relSubject AbsMoveable
|
relSubject AbsMoveable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ func (e *MoveEndpoint) ObjectKind() MoveEndpointKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *MoveEndpoint) String() string {
|
func (e *MoveEndpoint) String() string {
|
||||||
// Our internal pseudo-AbsMovable representing the relative
|
// Our internal pseudo-AbsMoveable representing the relative
|
||||||
// address (either ModuleInstance or AbsResourceInstance) is
|
// address (either ModuleInstance or AbsResourceInstance) is
|
||||||
// a good enough proxy for the relative move endpoint address
|
// a good enough proxy for the relative move endpoint address
|
||||||
// serialization.
|
// serialization.
|
||||||
|
|
|
@ -131,7 +131,7 @@ func (e *MoveEndpointInModule) Module() Module {
|
||||||
return e.module
|
return e.module
|
||||||
}
|
}
|
||||||
|
|
||||||
// InModuleInstance returns an AbsMovable address which concatenates the
|
// InModuleInstance returns an AbsMoveable address which concatenates the
|
||||||
// given module instance address with the receiver's relative object selection
|
// given module instance address with the receiver's relative object selection
|
||||||
// to produce one example of an instance that might be affected by this
|
// to produce one example of an instance that might be affected by this
|
||||||
// move statement.
|
// move statement.
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
func TestParseMoveEndpoint(t *testing.T) {
|
func TestParseMoveEndpoint(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Input string
|
Input string
|
||||||
WantRel AbsMoveable // funny intermediate subset of AbsMovable
|
WantRel AbsMoveable // funny intermediate subset of AbsMoveable
|
||||||
WantErr string
|
WantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@ package addrs
|
||||||
// with any other similar cross-module state refactoring statements we might
|
// with any other similar cross-module state refactoring statements we might
|
||||||
// allow.
|
// allow.
|
||||||
//
|
//
|
||||||
// Note that AbsMovable represents an absolute address relative to the root
|
// Note that AbsMoveable represents an absolute address relative to the root
|
||||||
// of the configuration, which is different than the direct representation
|
// of the configuration, which is different than the direct representation
|
||||||
// of these in configuration where the author gives an address relative to
|
// of these in configuration where the author gives an address relative to
|
||||||
// the current module where the address is defined. The type MoveEndpoint
|
// the current module where the address is defined. The type MoveEndpoint
|
||||||
|
@ -16,7 +16,7 @@ type AbsMoveable interface {
|
||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following are all of the possible AbsMovable address types:
|
// The following are all of the possible AbsMoveable address types:
|
||||||
var (
|
var (
|
||||||
_ AbsMoveable = AbsResource{}
|
_ AbsMoveable = AbsResource{}
|
||||||
_ AbsMoveable = AbsResourceInstance{}
|
_ AbsMoveable = AbsResourceInstance{}
|
||||||
|
@ -24,6 +24,19 @@ var (
|
||||||
_ AbsMoveable = AbsModuleCall{}
|
_ AbsMoveable = AbsModuleCall{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AbsMoveableResource is an AbsMoveable that is either a resource or a resource
|
||||||
|
// instance.
|
||||||
|
type AbsMoveableResource interface {
|
||||||
|
AbsMoveable
|
||||||
|
AffectedAbsResource() AbsResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following are all of the possible AbsMoveableResource types:
|
||||||
|
var (
|
||||||
|
_ AbsMoveableResource = AbsResource{}
|
||||||
|
_ AbsMoveableResource = AbsResourceInstance{}
|
||||||
|
)
|
||||||
|
|
||||||
// ConfigMoveable is similar to AbsMoveable but represents a static object in
|
// ConfigMoveable is similar to AbsMoveable but represents a static object in
|
||||||
// the configuration, rather than an instance of that object created by
|
// the configuration, rather than an instance of that object created by
|
||||||
// module expansion.
|
// module expansion.
|
||||||
|
|
|
@ -186,6 +186,11 @@ func (r AbsResource) String() string {
|
||||||
return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String())
|
return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AffectedAbsResource returns the AbsResource.
|
||||||
|
func (r AbsResource) AffectedAbsResource() AbsResource {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func (r AbsResource) Equal(o AbsResource) bool {
|
func (r AbsResource) Equal(o AbsResource) bool {
|
||||||
return r.Module.Equal(o.Module) && r.Resource.Equal(o.Resource)
|
return r.Module.Equal(o.Module) && r.Resource.Equal(o.Resource)
|
||||||
}
|
}
|
||||||
|
@ -267,6 +272,14 @@ func (r AbsResourceInstance) String() string {
|
||||||
return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String())
|
return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AffectedAbsResource returns the AbsResource for the instance.
|
||||||
|
func (r AbsResourceInstance) AffectedAbsResource() AbsResource {
|
||||||
|
return AbsResource{
|
||||||
|
Module: r.Module,
|
||||||
|
Resource: r.Resource.Resource,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r AbsResourceInstance) Equal(o AbsResourceInstance) bool {
|
func (r AbsResourceInstance) Equal(o AbsResourceInstance) bool {
|
||||||
return r.Module.Equal(o.Module) && r.Resource.Equal(o.Resource)
|
return r.Module.Equal(o.Module) && r.Resource.Equal(o.Resource)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/hashicorp/terraform/internal/addrs"
|
"github.com/hashicorp/terraform/internal/addrs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDecodeMovedBlock(t *testing.T) {
|
func TestMovedBlock_decode(t *testing.T) {
|
||||||
blockRange := hcl.Range{
|
blockRange := hcl.Range{
|
||||||
Filename: "mock.tf",
|
Filename: "mock.tf",
|
||||||
Start: hcl.Pos{Line: 3, Column: 12, Byte: 27},
|
Start: hcl.Pos{Line: 3, Column: 12, Byte: 27},
|
||||||
|
@ -169,7 +169,7 @@ func TestDecodeMovedBlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMovedBlocksInModule(t *testing.T) {
|
func TestMovedBlock_inModule(t *testing.T) {
|
||||||
parser := NewParser(nil)
|
parser := NewParser(nil)
|
||||||
mod, diags := parser.LoadConfigDir("testdata/valid-modules/moved-blocks")
|
mod, diags := parser.LoadConfigDir("testdata/valid-modules/moved-blocks")
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
|
|
|
@ -110,7 +110,7 @@ func ValidateMoves(stmts []MoveStatement, rootCfg *configs.Config, declaredInsts
|
||||||
shortNoun = "resource"
|
shortNoun = "resource"
|
||||||
default:
|
default:
|
||||||
// The above cases should cover all of the AbsMoveable types
|
// The above cases should cover all of the AbsMoveable types
|
||||||
panic("unsupported AbsMovable address type")
|
panic("unsupported AbsMoveable address type")
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's invalid to have a move statement whose "from" address
|
// It's invalid to have a move statement whose "from" address
|
||||||
|
@ -178,6 +178,16 @@ func ValidateMoves(stmts []MoveStatement, rootCfg *configs.Config, declaredInsts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resource types must match.
|
||||||
|
if resourceTypesDiffer(absFrom, absTo) {
|
||||||
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Resource type mismatch",
|
||||||
|
Detail: fmt.Sprintf(
|
||||||
|
"This statement declares a move from %s to %s, which is a %s of a different type.", absFrom, absTo, noun,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,7 +244,20 @@ func moveableObjectExists(addr addrs.AbsMoveable, in instances.Set) bool {
|
||||||
return in.HasResource(addr)
|
return in.HasResource(addr)
|
||||||
default:
|
default:
|
||||||
// The above cases should cover all of the AbsMoveable types
|
// The above cases should cover all of the AbsMoveable types
|
||||||
panic("unsupported AbsMovable address type")
|
panic("unsupported AbsMoveable address type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceTypesDiffer(absFrom, absTo addrs.AbsMoveable) bool {
|
||||||
|
switch absFrom := absFrom.(type) {
|
||||||
|
case addrs.AbsMoveableResource:
|
||||||
|
// addrs.UnifyMoveEndpoints guarantees that both addresses are of the
|
||||||
|
// same kind, so at this point we can assume that absTo is also an
|
||||||
|
// addrs.AbsResourceInstance or addrs.AbsResource.
|
||||||
|
absTo := absTo.(addrs.AbsMoveableResource)
|
||||||
|
return absFrom.AffectedAbsResource().Resource.Type != absTo.AffectedAbsResource().Resource.Type
|
||||||
|
default:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +332,7 @@ func movableObjectDeclRange(addr addrs.AbsMoveable, cfg *configs.Config) (tfdiag
|
||||||
return tfdiags.SourceRangeFromHCL(rc.DeclRange), true
|
return tfdiags.SourceRangeFromHCL(rc.DeclRange), true
|
||||||
default:
|
default:
|
||||||
// The above cases should cover all of the AbsMoveable types
|
// The above cases should cover all of the AbsMoveable types
|
||||||
panic("unsupported AbsMovable address type")
|
panic("unsupported AbsMoveable address type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -386,6 +386,24 @@ Each resource can have moved from only one source resource.`,
|
||||||
},
|
},
|
||||||
WantError: ``, // This is okay because the call itself is not considered to be inside the package it refers to
|
WantError: ``, // This is okay because the call itself is not considered to be inside the package it refers to
|
||||||
},
|
},
|
||||||
|
"resource type mismatch": {
|
||||||
|
Statements: []MoveStatement{
|
||||||
|
makeTestMoveStmt(t, ``,
|
||||||
|
`test.nonexist1`,
|
||||||
|
`other.single`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
WantError: `Resource type mismatch: This statement declares a move from test.nonexist1 to other.single, which is a resource of a different type.`,
|
||||||
|
},
|
||||||
|
"resource instance type mismatch": {
|
||||||
|
Statements: []MoveStatement{
|
||||||
|
makeTestMoveStmt(t, ``,
|
||||||
|
`test.nonexist1[0]`,
|
||||||
|
`other.single`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
WantError: `Resource type mismatch: This statement declares a move from test.nonexist1[0] to other.single, which is a resource instance of a different type.`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
|
|
|
@ -41,6 +41,9 @@ resource "test" "for_each" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "other" "single" {
|
||||||
|
}
|
||||||
|
|
||||||
module "fake_external" {
|
module "fake_external" {
|
||||||
# Our configuration fixture loader has a special case for a module call
|
# Our configuration fixture loader has a special case for a module call
|
||||||
# named "fake_external" where it will mutate the source address after
|
# named "fake_external" where it will mutate the source address after
|
||||||
|
|
Loading…
Reference in New Issue