| package addrs |
| |
| 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. |
| // |
| // 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. |
| 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, |
| } |
| } |
| |
| 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) |
| } |
| } |
| |
| func (c Check) UniqueKey() UniqueKey { |
| return checkKey{ |
| ContainerKey: c.Container.UniqueKey(), |
| Type: c.Type, |
| Index: c.Index, |
| } |
| } |
| |
| type checkKey struct { |
| ContainerKey UniqueKey |
| Type CheckType |
| Index int |
| } |
| |
| 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. |
| // |
| // (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 |
| |
| //go:generate go run golang.org/x/tools/cmd/stringer -type=CheckType check.go |
| |
| const ( |
| InvalidCondition CheckType = 0 |
| ResourcePrecondition CheckType = 1 |
| ResourcePostcondition CheckType = 2 |
| OutputPrecondition CheckType = 3 |
| ) |
| |
| // 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" |
| } |
| } |
| |
| // 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. |
| // |
| // 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 |
| } |
| |
| // 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)) |
| } |
| } |