mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-26 16:36:26 -06:00
Checks: Refactor existing check addrs and add new check block addr (#32733)
* Add support for scoped resources * refactor existing checks addrs and add check block addr * address comments
This commit is contained in:
parent
d53cb81bed
commit
87c457781d
@ -1,251 +1,131 @@
|
||||
package addrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
import "fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// Check is the address of a check rule within a checkable object.
|
||||
// Check is the address of a check block within a module.
|
||||
//
|
||||
// This represents the check rule globally within a configuration, and is used
|
||||
// during graph evaluation to identify a condition result object to update with
|
||||
// the result of check rule evaluation.
|
||||
//
|
||||
// The check address is not distinct from resource traversals, and check rule
|
||||
// values are not intended to be available to the language, so the address is
|
||||
// not Referenceable.
|
||||
//
|
||||
// Note also that the check address is only relevant within the scope of a run,
|
||||
// as reordering check blocks between runs will result in their addresses
|
||||
// changing. Check is therefore for internal use only and should not be exposed
|
||||
// in durable artifacts such as state snapshots.
|
||||
// For now, checks do not support meta arguments such as "count" or "for_each"
|
||||
// so this address uniquely describes a single check within a module.
|
||||
type Check struct {
|
||||
Container Checkable
|
||||
Type CheckType
|
||||
Index int
|
||||
}
|
||||
|
||||
func NewCheck(container Checkable, typ CheckType, index int) Check {
|
||||
return Check{
|
||||
Container: container,
|
||||
Type: typ,
|
||||
Index: index,
|
||||
}
|
||||
referenceable
|
||||
Name string
|
||||
}
|
||||
|
||||
func (c Check) String() string {
|
||||
container := c.Container.String()
|
||||
switch c.Type {
|
||||
case ResourcePrecondition:
|
||||
return fmt.Sprintf("%s.precondition[%d]", container, c.Index)
|
||||
case ResourcePostcondition:
|
||||
return fmt.Sprintf("%s.postcondition[%d]", container, c.Index)
|
||||
case OutputPrecondition:
|
||||
return fmt.Sprintf("%s.precondition[%d]", container, c.Index)
|
||||
default:
|
||||
// This should not happen
|
||||
return fmt.Sprintf("%s.condition[%d]", container, c.Index)
|
||||
return fmt.Sprintf("check.%s", c.Name)
|
||||
}
|
||||
|
||||
// InModule returns a ConfigCheck from the receiver and the given module
|
||||
// address.
|
||||
func (c Check) InModule(modAddr Module) ConfigCheck {
|
||||
return ConfigCheck{
|
||||
Module: modAddr,
|
||||
Check: c,
|
||||
}
|
||||
}
|
||||
|
||||
// Absolute returns an AbsCheck from the receiver and the given module instance
|
||||
// address.
|
||||
func (c Check) Absolute(modAddr ModuleInstance) AbsCheck {
|
||||
return AbsCheck{
|
||||
Module: modAddr,
|
||||
Check: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Check) Equal(o Check) bool {
|
||||
return c.Name == o.Name
|
||||
}
|
||||
|
||||
func (c Check) UniqueKey() UniqueKey {
|
||||
return checkKey{
|
||||
ContainerKey: c.Container.UniqueKey(),
|
||||
Type: c.Type,
|
||||
Index: c.Index,
|
||||
}
|
||||
return c // A Check is its own UniqueKey
|
||||
}
|
||||
|
||||
type checkKey struct {
|
||||
ContainerKey UniqueKey
|
||||
Type CheckType
|
||||
Index int
|
||||
}
|
||||
func (c Check) uniqueKeySigil() {}
|
||||
|
||||
func (k checkKey) uniqueKeySigil() {}
|
||||
|
||||
// CheckType describes a category of check. We use this only to establish
|
||||
// uniqueness for Check values, and do not expose this concept of "check types"
|
||||
// (which is subject to change in future) in any durable artifacts such as
|
||||
// state snapshots.
|
||||
// ConfigCheck is an address for a check block within a configuration.
|
||||
//
|
||||
// (See [CheckableKind] for an enumeration that we _do_ use externally, to
|
||||
// describe the type of object being checked rather than the type of the check
|
||||
// itself.)
|
||||
type CheckType int
|
||||
// This contains a Check address and a Module address, meaning this describes
|
||||
// a check block within the entire configuration.
|
||||
type ConfigCheck struct {
|
||||
Module Module
|
||||
Check Check
|
||||
}
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=CheckType check.go
|
||||
var _ ConfigCheckable = ConfigCheck{}
|
||||
|
||||
const (
|
||||
InvalidCondition CheckType = 0
|
||||
ResourcePrecondition CheckType = 1
|
||||
ResourcePostcondition CheckType = 2
|
||||
OutputPrecondition CheckType = 3
|
||||
)
|
||||
func (c ConfigCheck) UniqueKey() UniqueKey {
|
||||
return configCheckUniqueKey(c.String())
|
||||
}
|
||||
|
||||
// Description returns a human-readable description of the check type. This is
|
||||
// presented in the user interface through a diagnostic summary.
|
||||
func (c CheckType) Description() string {
|
||||
switch c {
|
||||
case ResourcePrecondition:
|
||||
return "Resource precondition"
|
||||
case ResourcePostcondition:
|
||||
return "Resource postcondition"
|
||||
case OutputPrecondition:
|
||||
return "Module output value precondition"
|
||||
default:
|
||||
// This should not happen
|
||||
return "Condition"
|
||||
func (c ConfigCheck) configCheckableSigil() {}
|
||||
|
||||
func (c ConfigCheck) CheckableKind() CheckableKind {
|
||||
return CheckableCheck
|
||||
}
|
||||
|
||||
func (c ConfigCheck) String() string {
|
||||
if len(c.Module) == 0 {
|
||||
return c.Check.String()
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", c.Module, c.Check)
|
||||
}
|
||||
|
||||
// Checkable is an interface implemented by all address types that can contain
|
||||
// condition blocks.
|
||||
type Checkable interface {
|
||||
UniqueKeyer
|
||||
|
||||
checkableSigil()
|
||||
|
||||
// Check returns the address of an individual check rule of a specified
|
||||
// type and index within this checkable container.
|
||||
Check(CheckType, int) Check
|
||||
|
||||
// ConfigCheckable returns the address of the configuration construct that
|
||||
// this Checkable belongs to.
|
||||
//
|
||||
// Checkable objects can potentially be dynamically declared during a
|
||||
// plan operation using constructs like resource for_each, and so
|
||||
// ConfigCheckable gives us a way to talk about the static containers
|
||||
// those dynamic objects belong to, in case we wish to group together
|
||||
// dynamic checkable objects into their static checkable for reporting
|
||||
// purposes.
|
||||
ConfigCheckable() ConfigCheckable
|
||||
|
||||
CheckableKind() CheckableKind
|
||||
String() string
|
||||
}
|
||||
|
||||
var (
|
||||
_ Checkable = AbsResourceInstance{}
|
||||
_ Checkable = AbsOutputValue{}
|
||||
)
|
||||
|
||||
// CheckableKind describes the different kinds of checkable objects.
|
||||
type CheckableKind rune
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=CheckableKind check.go
|
||||
|
||||
const (
|
||||
CheckableKindInvalid CheckableKind = 0
|
||||
CheckableResource CheckableKind = 'R'
|
||||
CheckableOutputValue CheckableKind = 'O'
|
||||
)
|
||||
|
||||
// ConfigCheckable is an interfaces implemented by address types that represent
|
||||
// configuration constructs that can have Checkable addresses associated with
|
||||
// them.
|
||||
// AbsCheck is an absolute address for a check block under a given module path.
|
||||
//
|
||||
// This address type therefore in a sense represents a container for zero or
|
||||
// more checkable objects all declared by the same configuration construct,
|
||||
// so that we can talk about these groups of checkable objects before we're
|
||||
// ready to decide how many checkable objects belong to each one.
|
||||
type ConfigCheckable interface {
|
||||
UniqueKeyer
|
||||
|
||||
configCheckableSigil()
|
||||
|
||||
CheckableKind() CheckableKind
|
||||
String() string
|
||||
// This contains an actual ModuleInstance address (compared to the Module within
|
||||
// a ConfigCheck), meaning this uniquely describes a check block within the
|
||||
// entire configuration after any "count" or "foreach" meta arguments have been
|
||||
// evaluated on the containing module.
|
||||
type AbsCheck struct {
|
||||
Module ModuleInstance
|
||||
Check Check
|
||||
}
|
||||
|
||||
var (
|
||||
_ ConfigCheckable = ConfigResource{}
|
||||
_ ConfigCheckable = ConfigOutputValue{}
|
||||
)
|
||||
var _ Checkable = AbsCheck{}
|
||||
|
||||
// ParseCheckableStr attempts to parse the given string as a Checkable address
|
||||
// of the given kind.
|
||||
func (c AbsCheck) UniqueKey() UniqueKey {
|
||||
return absCheckUniqueKey(c.String())
|
||||
}
|
||||
|
||||
func (c AbsCheck) checkableSigil() {}
|
||||
|
||||
// CheckRule returns an address for a given rule type within the check block.
|
||||
//
|
||||
// This should be the opposite of Checkable.String for any Checkable address
|
||||
// type, as long as "kind" is set to the value returned by the address's
|
||||
// CheckableKind method.
|
||||
//
|
||||
// We do not typically expect users to write out checkable addresses as input,
|
||||
// but we use them as part of some of our wire formats for persisting check
|
||||
// results between runs.
|
||||
func ParseCheckableStr(kind CheckableKind, src string) (Checkable, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(src), "", hcl.InitialPos)
|
||||
diags = diags.Append(parseDiags)
|
||||
if parseDiags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
path, remain, diags := parseModuleInstancePrefix(traversal)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
if remain.IsRelative() {
|
||||
// (relative means that there's either nothing left or what's next isn't an identifier)
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid checkable address",
|
||||
Detail: "Module path must be followed by either a resource instance address or an output value address.",
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
// We use "kind" to disambiguate here because unfortunately we've
|
||||
// historically never reserved "output" as a possible resource type name
|
||||
// and so it is in principle possible -- albeit unlikely -- that there
|
||||
// might be a resource whose type is literally "output".
|
||||
switch kind {
|
||||
case CheckableResource:
|
||||
riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain)
|
||||
diags = diags.Append(moreDiags)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
return riAddr, diags
|
||||
|
||||
case CheckableOutputValue:
|
||||
if len(remain) != 2 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid checkable address",
|
||||
Detail: "Output address must have only one attribute part after the keyword 'output', giving the name of the output value.",
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
if remain.RootName() != "output" {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid checkable address",
|
||||
Detail: "Output address must follow the module address with the keyword 'output'.",
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
if step, ok := remain[1].(hcl.TraverseAttr); !ok {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid checkable address",
|
||||
Detail: "Output address must have only one attribute part after the keyword 'output', giving the name of the output value.",
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
} else {
|
||||
return OutputValue{Name: step.Name}.Absolute(path), diags
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported CheckableKind %s", kind))
|
||||
// There will be at most one CheckDataResource rule within a check block (with
|
||||
// an index of 0). There will be at least one, but potentially many,
|
||||
// CheckAssertion rules within a check block.
|
||||
func (c AbsCheck) CheckRule(typ CheckRuleType, i int) CheckRule {
|
||||
return CheckRule{
|
||||
Container: c,
|
||||
Type: typ,
|
||||
Index: i,
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigCheckable returns the ConfigCheck address for this absolute reference.
|
||||
func (c AbsCheck) ConfigCheckable() ConfigCheckable {
|
||||
return ConfigCheck{
|
||||
Module: c.Module.Module(),
|
||||
Check: c.Check,
|
||||
}
|
||||
}
|
||||
|
||||
func (c AbsCheck) CheckableKind() CheckableKind {
|
||||
return CheckableCheck
|
||||
}
|
||||
|
||||
func (c AbsCheck) String() string {
|
||||
if len(c.Module) == 0 {
|
||||
return c.Check.String()
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", c.Module, c.Check)
|
||||
}
|
||||
|
||||
type configCheckUniqueKey string
|
||||
|
||||
func (k configCheckUniqueKey) uniqueKeySigil() {}
|
||||
|
||||
type absCheckUniqueKey string
|
||||
|
||||
func (k absCheckUniqueKey) uniqueKeySigil() {}
|
||||
|
105
internal/addrs/check_rule.go
Normal file
105
internal/addrs/check_rule.go
Normal file
@ -0,0 +1,105 @@
|
||||
package addrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CheckRule is the address of a check rule within a checkable object.
|
||||
//
|
||||
// This represents the check rule globally within a configuration, and is used
|
||||
// during graph evaluation to identify a condition result object to update with
|
||||
// the result of check rule evaluation.
|
||||
//
|
||||
// The check address is not distinct from resource traversals, and check rule
|
||||
// values are not intended to be available to the language, so the address is
|
||||
// not Referenceable.
|
||||
//
|
||||
// Note also that the check address is only relevant within the scope of a run,
|
||||
// as reordering check blocks between runs will result in their addresses
|
||||
// changing. CheckRule is therefore for internal use only and should not be
|
||||
// exposed in durable artifacts such as state snapshots.
|
||||
type CheckRule struct {
|
||||
Container Checkable
|
||||
Type CheckRuleType
|
||||
Index int
|
||||
}
|
||||
|
||||
func NewCheckRule(container Checkable, typ CheckRuleType, index int) CheckRule {
|
||||
return CheckRule{
|
||||
Container: container,
|
||||
Type: typ,
|
||||
Index: index,
|
||||
}
|
||||
}
|
||||
|
||||
func (c CheckRule) String() string {
|
||||
container := c.Container.String()
|
||||
switch c.Type {
|
||||
case ResourcePrecondition:
|
||||
return fmt.Sprintf("%s.precondition[%d]", container, c.Index)
|
||||
case ResourcePostcondition:
|
||||
return fmt.Sprintf("%s.postcondition[%d]", container, c.Index)
|
||||
case OutputPrecondition:
|
||||
return fmt.Sprintf("%s.precondition[%d]", container, c.Index)
|
||||
default:
|
||||
// This should not happen
|
||||
return fmt.Sprintf("%s.condition[%d]", container, c.Index)
|
||||
}
|
||||
}
|
||||
|
||||
func (c CheckRule) UniqueKey() UniqueKey {
|
||||
return checkRuleKey{
|
||||
ContainerKey: c.Container.UniqueKey(),
|
||||
Type: c.Type,
|
||||
Index: c.Index,
|
||||
}
|
||||
}
|
||||
|
||||
type checkRuleKey struct {
|
||||
ContainerKey UniqueKey
|
||||
Type CheckRuleType
|
||||
Index int
|
||||
}
|
||||
|
||||
func (k checkRuleKey) uniqueKeySigil() {}
|
||||
|
||||
// CheckRuleType describes a category of check. We use this only to establish
|
||||
// uniqueness for Check values, and do not expose this concept of "check types"
|
||||
// (which is subject to change in future) in any durable artifacts such as
|
||||
// state snapshots.
|
||||
//
|
||||
// (See [CheckableKind] for an enumeration that we _do_ use externally, to
|
||||
// describe the type of object being checked rather than the type of the check
|
||||
// itself.)
|
||||
type CheckRuleType int
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=CheckRuleType check_rule.go
|
||||
|
||||
const (
|
||||
InvalidCondition CheckRuleType = 0
|
||||
ResourcePrecondition CheckRuleType = 1
|
||||
ResourcePostcondition CheckRuleType = 2
|
||||
OutputPrecondition CheckRuleType = 3
|
||||
CheckDataResource CheckRuleType = 4
|
||||
CheckAssertion CheckRuleType = 5
|
||||
)
|
||||
|
||||
// Description returns a human-readable description of the check type. This is
|
||||
// presented in the user interface through a diagnostic summary.
|
||||
func (c CheckRuleType) Description() string {
|
||||
switch c {
|
||||
case ResourcePrecondition:
|
||||
return "Resource precondition"
|
||||
case ResourcePostcondition:
|
||||
return "Resource postcondition"
|
||||
case OutputPrecondition:
|
||||
return "Module output value precondition"
|
||||
case CheckDataResource:
|
||||
return "Check block data resource"
|
||||
case CheckAssertion:
|
||||
return "Check block assertion"
|
||||
default:
|
||||
// This should not happen
|
||||
return "Condition"
|
||||
}
|
||||
}
|
182
internal/addrs/checkable.go
Normal file
182
internal/addrs/checkable.go
Normal file
@ -0,0 +1,182 @@
|
||||
package addrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// Checkable is an interface implemented by all address types that can contain
|
||||
// condition blocks.
|
||||
type Checkable interface {
|
||||
UniqueKeyer
|
||||
|
||||
checkableSigil()
|
||||
|
||||
// CheckRule returns the address of an individual check rule of a specified
|
||||
// type and index within this checkable container.
|
||||
CheckRule(CheckRuleType, int) CheckRule
|
||||
|
||||
// ConfigCheckable returns the address of the configuration construct that
|
||||
// this Checkable belongs to.
|
||||
//
|
||||
// Checkable objects can potentially be dynamically declared during a
|
||||
// plan operation using constructs like resource for_each, and so
|
||||
// ConfigCheckable gives us a way to talk about the static containers
|
||||
// those dynamic objects belong to, in case we wish to group together
|
||||
// dynamic checkable objects into their static checkable for reporting
|
||||
// purposes.
|
||||
ConfigCheckable() ConfigCheckable
|
||||
|
||||
CheckableKind() CheckableKind
|
||||
String() string
|
||||
}
|
||||
|
||||
var (
|
||||
_ Checkable = AbsResourceInstance{}
|
||||
_ Checkable = AbsOutputValue{}
|
||||
)
|
||||
|
||||
// CheckableKind describes the different kinds of checkable objects.
|
||||
type CheckableKind rune
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=CheckableKind checkable.go
|
||||
|
||||
const (
|
||||
CheckableKindInvalid CheckableKind = 0
|
||||
CheckableResource CheckableKind = 'R'
|
||||
CheckableOutputValue CheckableKind = 'O'
|
||||
CheckableCheck CheckableKind = 'C'
|
||||
)
|
||||
|
||||
// ConfigCheckable is an interfaces implemented by address types that represent
|
||||
// configuration constructs that can have Checkable addresses associated with
|
||||
// them.
|
||||
//
|
||||
// This address type therefore in a sense represents a container for zero or
|
||||
// more checkable objects all declared by the same configuration construct,
|
||||
// so that we can talk about these groups of checkable objects before we're
|
||||
// ready to decide how many checkable objects belong to each one.
|
||||
type ConfigCheckable interface {
|
||||
UniqueKeyer
|
||||
|
||||
configCheckableSigil()
|
||||
|
||||
CheckableKind() CheckableKind
|
||||
String() string
|
||||
}
|
||||
|
||||
var (
|
||||
_ ConfigCheckable = ConfigResource{}
|
||||
_ ConfigCheckable = ConfigOutputValue{}
|
||||
)
|
||||
|
||||
// ParseCheckableStr attempts to parse the given string as a Checkable address
|
||||
// of the given kind.
|
||||
//
|
||||
// This should be the opposite of Checkable.String for any Checkable address
|
||||
// type, as long as "kind" is set to the value returned by the address's
|
||||
// CheckableKind method.
|
||||
//
|
||||
// We do not typically expect users to write out checkable addresses as input,
|
||||
// but we use them as part of some of our wire formats for persisting check
|
||||
// results between runs.
|
||||
func ParseCheckableStr(kind CheckableKind, src string) (Checkable, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(src), "", hcl.InitialPos)
|
||||
diags = diags.Append(parseDiags)
|
||||
if parseDiags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
path, remain, diags := parseModuleInstancePrefix(traversal)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
if remain.IsRelative() {
|
||||
// (relative means that there's either nothing left or what's next isn't an identifier)
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid checkable address",
|
||||
Detail: "Module path must be followed by either a resource instance address or an output value address.",
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
getCheckableName := func(keyword string, descriptor string) (string, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
var name string
|
||||
|
||||
if len(remain) != 2 {
|
||||
diags = diags.Append(hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid checkable address",
|
||||
Detail: fmt.Sprintf("%s address must have only one attribute part after the keyword '%s', giving the name of the %s.", cases.Title(language.English, cases.NoLower).String(keyword), keyword, descriptor),
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
if remain.RootName() != keyword {
|
||||
diags = diags.Append(hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid checkable address",
|
||||
Detail: fmt.Sprintf("%s address must follow the module address with the keyword '%s'.", cases.Title(language.English, cases.NoLower).String(keyword), keyword),
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
}
|
||||
if step, ok := remain[1].(hcl.TraverseAttr); !ok {
|
||||
diags = diags.Append(hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid checkable address",
|
||||
Detail: fmt.Sprintf("%s address must have only one attribute part after the keyword '%s', giving the name of the %s.", cases.Title(language.English, cases.NoLower).String(keyword), keyword, descriptor),
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
} else {
|
||||
name = step.Name
|
||||
}
|
||||
|
||||
return name, diags
|
||||
}
|
||||
|
||||
// We use "kind" to disambiguate here because unfortunately we've
|
||||
// historically never reserved "output" as a possible resource type name
|
||||
// and so it is in principle possible -- albeit unlikely -- that there
|
||||
// might be a resource whose type is literally "output".
|
||||
switch kind {
|
||||
case CheckableResource:
|
||||
riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain)
|
||||
diags = diags.Append(moreDiags)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
return riAddr, diags
|
||||
|
||||
case CheckableOutputValue:
|
||||
name, nameDiags := getCheckableName("output", "output value")
|
||||
diags = diags.Append(nameDiags)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
return OutputValue{Name: name}.Absolute(path), diags
|
||||
|
||||
case CheckableCheck:
|
||||
name, nameDiags := getCheckableName("check", "check block")
|
||||
diags = diags.Append(nameDiags)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
return Check{Name: name}.Absolute(path), diags
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported CheckableKind %s", kind))
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Code generated by "stringer -type=CheckableKind check.go"; DO NOT EDIT.
|
||||
// Code generated by "stringer -type=CheckableKind checkable.go"; DO NOT EDIT.
|
||||
|
||||
package addrs
|
||||
|
||||
@ -11,22 +11,26 @@ func _() {
|
||||
_ = x[CheckableKindInvalid-0]
|
||||
_ = x[CheckableResource-82]
|
||||
_ = x[CheckableOutputValue-79]
|
||||
_ = x[CheckableCheck-67]
|
||||
}
|
||||
|
||||
const (
|
||||
_CheckableKind_name_0 = "CheckableKindInvalid"
|
||||
_CheckableKind_name_1 = "CheckableOutputValue"
|
||||
_CheckableKind_name_2 = "CheckableResource"
|
||||
_CheckableKind_name_1 = "CheckableCheck"
|
||||
_CheckableKind_name_2 = "CheckableOutputValue"
|
||||
_CheckableKind_name_3 = "CheckableResource"
|
||||
)
|
||||
|
||||
func (i CheckableKind) String() string {
|
||||
switch {
|
||||
case i == 0:
|
||||
return _CheckableKind_name_0
|
||||
case i == 79:
|
||||
case i == 67:
|
||||
return _CheckableKind_name_1
|
||||
case i == 82:
|
||||
case i == 79:
|
||||
return _CheckableKind_name_2
|
||||
case i == 82:
|
||||
return _CheckableKind_name_3
|
||||
default:
|
||||
return "CheckableKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
|
28
internal/addrs/checkruletype_string.go
Normal file
28
internal/addrs/checkruletype_string.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Code generated by "stringer -type=CheckRuleType check_rule.go"; DO NOT EDIT.
|
||||
|
||||
package addrs
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[InvalidCondition-0]
|
||||
_ = x[ResourcePrecondition-1]
|
||||
_ = x[ResourcePostcondition-2]
|
||||
_ = x[OutputPrecondition-3]
|
||||
_ = x[CheckDataResource-4]
|
||||
_ = x[CheckAssertion-5]
|
||||
}
|
||||
|
||||
const _CheckRuleType_name = "InvalidConditionResourcePreconditionResourcePostconditionOutputPreconditionCheckDataResourceCheckAssertion"
|
||||
|
||||
var _CheckRuleType_index = [...]uint8{0, 16, 36, 57, 75, 92, 106}
|
||||
|
||||
func (i CheckRuleType) String() string {
|
||||
if i < 0 || i >= CheckRuleType(len(_CheckRuleType_index)-1) {
|
||||
return "CheckRuleType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _CheckRuleType_name[_CheckRuleType_index[i]:_CheckRuleType_index[i+1]]
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Code generated by "stringer -type=CheckType check.go"; DO NOT EDIT.
|
||||
|
||||
package addrs
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[InvalidCondition-0]
|
||||
_ = x[ResourcePrecondition-1]
|
||||
_ = x[ResourcePostcondition-2]
|
||||
_ = x[OutputPrecondition-3]
|
||||
}
|
||||
|
||||
const _CheckType_name = "InvalidConditionResourcePreconditionResourcePostconditionOutputPrecondition"
|
||||
|
||||
var _CheckType_index = [...]uint8{0, 16, 36, 57, 75}
|
||||
|
||||
func (i CheckType) String() string {
|
||||
if i < 0 || i >= CheckType(len(_CheckType_index)-1) {
|
||||
return "CheckType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _CheckType_name[_CheckType_index[i]:_CheckType_index[i+1]]
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
@ -62,8 +63,8 @@ func (m ModuleInstance) OutputValue(name string) AbsOutputValue {
|
||||
}
|
||||
}
|
||||
|
||||
func (v AbsOutputValue) Check(t CheckType, i int) Check {
|
||||
return Check{
|
||||
func (v AbsOutputValue) CheckRule(t CheckRuleType, i int) CheckRule {
|
||||
return CheckRule{
|
||||
Container: v,
|
||||
Type: t,
|
||||
Index: i,
|
||||
|
@ -329,8 +329,8 @@ func (r AbsResourceInstance) AffectedAbsResource() AbsResource {
|
||||
}
|
||||
}
|
||||
|
||||
func (r AbsResourceInstance) Check(t CheckType, i int) Check {
|
||||
return Check{
|
||||
func (r AbsResourceInstance) CheckRule(t CheckRuleType, i int) CheckRule {
|
||||
return CheckRule{
|
||||
Container: r,
|
||||
Type: t,
|
||||
Index: i,
|
||||
|
@ -33,7 +33,7 @@ type State struct {
|
||||
mu sync.Mutex
|
||||
|
||||
statuses addrs.Map[addrs.ConfigCheckable, *configCheckableState]
|
||||
failureMsgs addrs.Map[addrs.Check, string]
|
||||
failureMsgs addrs.Map[addrs.CheckRule, string]
|
||||
}
|
||||
|
||||
// configCheckableState is an internal part of type State that represents
|
||||
@ -53,7 +53,7 @@ type configCheckableState struct {
|
||||
// associated with object declared by this configuration construct. Since
|
||||
// checks are statically declared (even though the checkable objects
|
||||
// aren't) we can compute this only from the configuration.
|
||||
checkTypes map[addrs.CheckType]int
|
||||
checkTypes map[addrs.CheckRuleType]int
|
||||
|
||||
// objects represents the set of dynamic checkable objects associated
|
||||
// with this configuration construct. This is initially nil to represent
|
||||
@ -64,7 +64,7 @@ type configCheckableState struct {
|
||||
// The leaf Status values will initially be StatusUnknown
|
||||
// and then gradually updated by Terraform Core as it visits the
|
||||
// individual checkable objects and reports their status.
|
||||
objects addrs.Map[addrs.Checkable, map[addrs.CheckType][]Status]
|
||||
objects addrs.Map[addrs.Checkable, map[addrs.CheckRuleType][]Status]
|
||||
}
|
||||
|
||||
// NOTE: For the "Report"-prefixed methods that we use to gradually update
|
||||
@ -253,7 +253,7 @@ func (c *State) ObjectFailureMessages(addr addrs.Checkable) []string {
|
||||
for checkType, checks := range checksByType {
|
||||
for i, status := range checks {
|
||||
if status == StatusFail {
|
||||
checkAddr := addrs.NewCheck(addr, checkType, i)
|
||||
checkAddr := addrs.NewCheckRule(addr, checkType, i)
|
||||
msg := c.failureMsgs.Get(checkAddr)
|
||||
if msg != "" {
|
||||
ret = append(ret, msg)
|
||||
|
@ -42,7 +42,7 @@ func collectInitialStatuses(into addrs.Map[addrs.ConfigCheckable, *configCheckab
|
||||
|
||||
st := &configCheckableState{}
|
||||
|
||||
st.checkTypes = map[addrs.CheckType]int{
|
||||
st.checkTypes = map[addrs.CheckRuleType]int{
|
||||
addrs.OutputPrecondition: ct,
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ func collectInitialStatusForResource(into addrs.Map[addrs.ConfigCheckable, *conf
|
||||
}
|
||||
|
||||
st := &configCheckableState{
|
||||
checkTypes: make(map[addrs.CheckType]int),
|
||||
checkTypes: make(map[addrs.CheckRuleType]int),
|
||||
}
|
||||
|
||||
if ct := len(rc.Preconditions); ct > 0 {
|
||||
|
@ -32,14 +32,14 @@ func (c *State) ReportCheckableObjects(configAddr addrs.ConfigCheckable, objectA
|
||||
// At this point we pre-populate all of the check results as StatusUnknown,
|
||||
// so that even if we never hear from Terraform Core again we'll still
|
||||
// remember that these results were all pending.
|
||||
st.objects = addrs.MakeMap[addrs.Checkable, map[addrs.CheckType][]Status]()
|
||||
st.objects = addrs.MakeMap[addrs.Checkable, map[addrs.CheckRuleType][]Status]()
|
||||
for _, objectAddr := range objectAddrs {
|
||||
if gotConfigAddr := objectAddr.ConfigCheckable(); !addrs.Equivalent(configAddr, gotConfigAddr) {
|
||||
// All of the given object addresses must belong to the specified configuration address
|
||||
panic(fmt.Sprintf("%s belongs to %s, not %s", objectAddr, gotConfigAddr, configAddr))
|
||||
}
|
||||
|
||||
checks := make(map[addrs.CheckType][]Status, len(st.checkTypes))
|
||||
checks := make(map[addrs.CheckRuleType][]Status, len(st.checkTypes))
|
||||
for checkType, count := range st.checkTypes {
|
||||
// NOTE: This is intentionally a slice of count of the zero value
|
||||
// of Status, which is StatusUnknown to represent that we don't
|
||||
@ -61,7 +61,7 @@ func (c *State) ReportCheckableObjects(configAddr addrs.ConfigCheckable, objectA
|
||||
//
|
||||
// This method will also panic if the specified check already had a known
|
||||
// status; each check should have its result reported only once.
|
||||
func (c *State) ReportCheckResult(objectAddr addrs.Checkable, checkType addrs.CheckType, index int, status Status) {
|
||||
func (c *State) ReportCheckResult(objectAddr addrs.Checkable, checkType addrs.CheckRuleType, index int, status Status) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
@ -76,21 +76,21 @@ func (c *State) ReportCheckResult(objectAddr addrs.Checkable, checkType addrs.Ch
|
||||
// situations where the check condition was itself invalid, because that
|
||||
// should be represented by StatusError instead, and the error signalled via
|
||||
// diagnostics as normal.
|
||||
func (c *State) ReportCheckFailure(objectAddr addrs.Checkable, checkType addrs.CheckType, index int, errorMessage string) {
|
||||
func (c *State) ReportCheckFailure(objectAddr addrs.Checkable, checkType addrs.CheckRuleType, index int, errorMessage string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.reportCheckResult(objectAddr, checkType, index, StatusFail)
|
||||
if c.failureMsgs.Elems == nil {
|
||||
c.failureMsgs = addrs.MakeMap[addrs.Check, string]()
|
||||
c.failureMsgs = addrs.MakeMap[addrs.CheckRule, string]()
|
||||
}
|
||||
checkAddr := addrs.NewCheck(objectAddr, checkType, index)
|
||||
checkAddr := addrs.NewCheckRule(objectAddr, checkType, index)
|
||||
c.failureMsgs.Put(checkAddr, errorMessage)
|
||||
}
|
||||
|
||||
// reportCheckResult is shared between both ReportCheckResult and
|
||||
// ReportCheckFailure, and assumes its caller already holds the mutex.
|
||||
func (c *State) reportCheckResult(objectAddr addrs.Checkable, checkType addrs.CheckType, index int, status Status) {
|
||||
func (c *State) reportCheckResult(objectAddr addrs.Checkable, checkType addrs.CheckRuleType, index int, status Status) {
|
||||
configAddr := objectAddr.ConfigCheckable()
|
||||
|
||||
st, ok := c.statuses.GetOk(configAddr)
|
||||
|
@ -27,7 +27,7 @@ import (
|
||||
//
|
||||
// If any of the rules do not pass, the returned diagnostics will contain
|
||||
// errors. Otherwise, it will either be empty or contain only warnings.
|
||||
func evalCheckRules(typ addrs.CheckType, rules []*configs.CheckRule, ctx EvalContext, self addrs.Checkable, keyData instances.RepetitionData, diagSeverity tfdiags.Severity) tfdiags.Diagnostics {
|
||||
func evalCheckRules(typ addrs.CheckRuleType, rules []*configs.CheckRule, ctx EvalContext, self addrs.Checkable, keyData instances.RepetitionData, diagSeverity tfdiags.Severity) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
checkState := ctx.Checks()
|
||||
@ -67,7 +67,7 @@ type checkResult struct {
|
||||
FailureMessage string
|
||||
}
|
||||
|
||||
func evalCheckRule(typ addrs.CheckType, rule *configs.CheckRule, ctx EvalContext, self addrs.Checkable, keyData instances.RepetitionData, severity hcl.DiagnosticSeverity) (checkResult, tfdiags.Diagnostics) {
|
||||
func evalCheckRule(typ addrs.CheckRuleType, rule *configs.CheckRule, ctx EvalContext, self addrs.Checkable, keyData instances.RepetitionData, severity hcl.DiagnosticSeverity) (checkResult, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
const errInvalidCondition = "Invalid condition result"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user