From d06cbfe6c8e915a5dac1a1815cde1984fa37a284 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 14 Jun 2022 16:18:56 -0700 Subject: [PATCH] addrs: ConfigCheckable type Our existing addrs.Checkable represents a particular (possibly-dynamic) object that can have checks associated with it. This new addrs.ConfigCheckable represents static configuration objects that can potentially generate addrs.Checkable objects. The idea here is to allow us to predict from the configuration a set of potential checkable object containers and then dynamically associate the dynamic checkable objects with them as we make progress with planning. This is intended for our integration of checks into the "terraform test" testing harness, to be used instead of the weirdo builtin provider we were using as a placeholder before we had first-class syntax for checks. Test reporting tools find it helpful for there to be a consistent set of test cases from one run to the next so that they can report on trends over multiple runs, and so our ConfigCheckable addresses will serve as the relatively-static "test case" that we'll then associate the dynamic checks with, so that we can still talk about objects in the test result report even if we end up not reaching them due to an upstream conditution failure. --- internal/addrs/check.go | 41 ++++++++++++++++++++++---- internal/addrs/output_value.go | 54 +++++++++++++++++++++++++++++++++- internal/addrs/resource.go | 34 +++++++++++++++++++-- 3 files changed, 119 insertions(+), 10 deletions(-) diff --git a/internal/addrs/check.go b/internal/addrs/check.go index 5e7ff6b0fb..3593eee911 100644 --- a/internal/addrs/check.go +++ b/internal/addrs/check.go @@ -39,11 +39,25 @@ func (c Check) String() string { // 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 + String() string } @@ -52,12 +66,6 @@ var ( _ Checkable = AbsOutputValue{} ) -type checkable struct { -} - -func (c checkable) checkableSigil() { -} - // CheckType describes the category of check. type CheckType int @@ -85,3 +93,24 @@ func (c CheckType) Description() string { return "Condition" } } + +// 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() + + String() string +} + +var ( + _ ConfigCheckable = ConfigResource{} + _ ConfigCheckable = ConfigOutputValue{} +) diff --git a/internal/addrs/output_value.go b/internal/addrs/output_value.go index e45da89858..4970eee9a4 100644 --- a/internal/addrs/output_value.go +++ b/internal/addrs/output_value.go @@ -38,7 +38,6 @@ func (v OutputValue) Absolute(m ModuleInstance) AbsOutputValue { // configuration. It is related to but separate from ModuleCallOutput, which // represents a module output from the perspective of its parent module. type AbsOutputValue struct { - checkable Module ModuleInstance OutputValue OutputValue } @@ -73,6 +72,31 @@ func (v AbsOutputValue) Equal(o AbsOutputValue) bool { return v.OutputValue == o.OutputValue && v.Module.Equal(o.Module) } +func (v AbsOutputValue) ConfigOutputValue() ConfigOutputValue { + return ConfigOutputValue{ + Module: v.Module.Module(), + OutputValue: v.OutputValue, + } +} + +func (v AbsOutputValue) checkableSigil() { + // Output values are checkable +} + +func (v AbsOutputValue) ConfigCheckable() ConfigCheckable { + // Output values are declared by "output" blocks in the configuration, + // represented as ConfigOutputValue. + return v.ConfigOutputValue() +} + +func (v AbsOutputValue) UniqueKey() UniqueKey { + return absOutputValueUniqueKey(v.String()) +} + +type absOutputValueUniqueKey string + +func (k absOutputValueUniqueKey) uniqueKeySigil() {} + func ParseAbsOutputValue(traversal hcl.Traversal) (AbsOutputValue, tfdiags.Diagnostics) { path, remain, diags := parseModuleInstancePrefix(traversal) if diags.HasErrors() { @@ -152,3 +176,31 @@ func (v AbsOutputValue) ModuleCallOutput() (ModuleInstance, ModuleCallInstanceOu Name: v.OutputValue.Name, } } + +// ConfigOutputValue represents a particular "output" block in the +// configuration, which might have many AbsOutputValue addresses associated +// with it at runtime if it belongs to a module that was called using +// "count" or "for_each". +type ConfigOutputValue struct { + Module Module + OutputValue OutputValue +} + +func (v ConfigOutputValue) String() string { + if v.Module.IsRoot() { + return v.OutputValue.String() + } + return fmt.Sprintf("%s.%s", v.Module.String(), v.OutputValue.String()) +} + +func (v ConfigOutputValue) configCheckableSigil() { + // ConfigOutputValue is the ConfigCheckable for AbsOutputValue. +} + +func (v ConfigOutputValue) UniqueKey() UniqueKey { + return configOutputValueUniqueKey(v.String()) +} + +type configOutputValueUniqueKey string + +func (k configOutputValueUniqueKey) uniqueKeySigil() {} diff --git a/internal/addrs/resource.go b/internal/addrs/resource.go index 75b8a22286..12bdcf1132 100644 --- a/internal/addrs/resource.go +++ b/internal/addrs/resource.go @@ -210,7 +210,6 @@ func (r AbsResource) UniqueKey() UniqueKey { // AbsResourceInstance is an absolute address for a resource instance under a // given module path. type AbsResourceInstance struct { - checkable targetable Module ModuleInstance Resource ResourceInstance @@ -241,6 +240,15 @@ func (r AbsResourceInstance) ContainingResource() AbsResource { } } +// ConfigResource returns the address of the configuration block that declared +// this instance. +func (r AbsResourceInstance) ConfigResource() ConfigResource { + return ConfigResource{ + Module: r.Module.Module(), + Resource: r.Resource.Resource, + } +} + // TargetContains implements Targetable by returning true if the given other // address is equal to the receiver. func (r AbsResourceInstance) TargetContains(other Targetable) bool { @@ -322,6 +330,14 @@ func (r AbsResourceInstance) Less(o AbsResourceInstance) bool { } } +// AbsResourceInstance is a Checkable +func (r AbsResourceInstance) checkableSigil() {} + +func (r AbsResourceInstance) ConfigCheckable() ConfigCheckable { + // The ConfigCheckable for an AbsResourceInstance is its ConfigResource. + return r.ConfigResource() +} + type absResourceInstanceKey string func (r AbsResourceInstance) UniqueKey() UniqueKey { @@ -393,10 +409,22 @@ func (r ConfigResource) Equal(o ConfigResource) bool { return r.Module.Equal(o.Module) && r.Resource.Equal(o.Resource) } -func (r ConfigResource) configMoveableSigil() { - // AbsResource is moveable +func (r ConfigResource) UniqueKey() UniqueKey { + return configResourceKey(r.String()) } +func (r ConfigResource) configMoveableSigil() { + // ConfigResource is moveable +} + +func (r ConfigResource) configCheckableSigil() { + // ConfigResource represents a configuration object that declares checkable objects +} + +type configResourceKey string + +func (k configResourceKey) uniqueKeySigil() {} + // ResourceMode defines which lifecycle applies to a given resource. Each // resource lifecycle has a slightly different address format. type ResourceMode rune