github.com/pulumi/terraform@v1.4.0/pkg/addrs/check.go (about) 1 package addrs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hashicorp/hcl/v2/hclsyntax" 8 "github.com/pulumi/terraform/pkg/tfdiags" 9 ) 10 11 // Check is the address of a check rule within a checkable object. 12 // 13 // This represents the check rule globally within a configuration, and is used 14 // during graph evaluation to identify a condition result object to update with 15 // the result of check rule evaluation. 16 // 17 // The check address is not distinct from resource traversals, and check rule 18 // values are not intended to be available to the language, so the address is 19 // not Referenceable. 20 // 21 // Note also that the check address is only relevant within the scope of a run, 22 // as reordering check blocks between runs will result in their addresses 23 // changing. Check is therefore for internal use only and should not be exposed 24 // in durable artifacts such as state snapshots. 25 type Check struct { 26 Container Checkable 27 Type CheckType 28 Index int 29 } 30 31 func NewCheck(container Checkable, typ CheckType, index int) Check { 32 return Check{ 33 Container: container, 34 Type: typ, 35 Index: index, 36 } 37 } 38 39 func (c Check) String() string { 40 container := c.Container.String() 41 switch c.Type { 42 case ResourcePrecondition: 43 return fmt.Sprintf("%s.precondition[%d]", container, c.Index) 44 case ResourcePostcondition: 45 return fmt.Sprintf("%s.postcondition[%d]", container, c.Index) 46 case OutputPrecondition: 47 return fmt.Sprintf("%s.precondition[%d]", container, c.Index) 48 default: 49 // This should not happen 50 return fmt.Sprintf("%s.condition[%d]", container, c.Index) 51 } 52 } 53 54 func (c Check) UniqueKey() UniqueKey { 55 return checkKey{ 56 ContainerKey: c.Container.UniqueKey(), 57 Type: c.Type, 58 Index: c.Index, 59 } 60 } 61 62 type checkKey struct { 63 ContainerKey UniqueKey 64 Type CheckType 65 Index int 66 } 67 68 func (k checkKey) uniqueKeySigil() {} 69 70 // CheckType describes a category of check. We use this only to establish 71 // uniqueness for Check values, and do not expose this concept of "check types" 72 // (which is subject to change in future) in any durable artifacts such as 73 // state snapshots. 74 // 75 // (See [CheckableKind] for an enumeration that we _do_ use externally, to 76 // describe the type of object being checked rather than the type of the check 77 // itself.) 78 type CheckType int 79 80 //go:generate go run golang.org/x/tools/cmd/stringer -type=CheckType check.go 81 82 const ( 83 InvalidCondition CheckType = 0 84 ResourcePrecondition CheckType = 1 85 ResourcePostcondition CheckType = 2 86 OutputPrecondition CheckType = 3 87 ) 88 89 // Description returns a human-readable description of the check type. This is 90 // presented in the user interface through a diagnostic summary. 91 func (c CheckType) Description() string { 92 switch c { 93 case ResourcePrecondition: 94 return "Resource precondition" 95 case ResourcePostcondition: 96 return "Resource postcondition" 97 case OutputPrecondition: 98 return "Module output value precondition" 99 default: 100 // This should not happen 101 return "Condition" 102 } 103 } 104 105 // Checkable is an interface implemented by all address types that can contain 106 // condition blocks. 107 type Checkable interface { 108 UniqueKeyer 109 110 checkableSigil() 111 112 // Check returns the address of an individual check rule of a specified 113 // type and index within this checkable container. 114 Check(CheckType, int) Check 115 116 // ConfigCheckable returns the address of the configuration construct that 117 // this Checkable belongs to. 118 // 119 // Checkable objects can potentially be dynamically declared during a 120 // plan operation using constructs like resource for_each, and so 121 // ConfigCheckable gives us a way to talk about the static containers 122 // those dynamic objects belong to, in case we wish to group together 123 // dynamic checkable objects into their static checkable for reporting 124 // purposes. 125 ConfigCheckable() ConfigCheckable 126 127 CheckableKind() CheckableKind 128 String() string 129 } 130 131 var ( 132 _ Checkable = AbsResourceInstance{} 133 _ Checkable = AbsOutputValue{} 134 ) 135 136 // CheckableKind describes the different kinds of checkable objects. 137 type CheckableKind rune 138 139 //go:generate go run golang.org/x/tools/cmd/stringer -type=CheckableKind check.go 140 141 const ( 142 CheckableKindInvalid CheckableKind = 0 143 CheckableResource CheckableKind = 'R' 144 CheckableOutputValue CheckableKind = 'O' 145 ) 146 147 // ConfigCheckable is an interfaces implemented by address types that represent 148 // configuration constructs that can have Checkable addresses associated with 149 // them. 150 // 151 // This address type therefore in a sense represents a container for zero or 152 // more checkable objects all declared by the same configuration construct, 153 // so that we can talk about these groups of checkable objects before we're 154 // ready to decide how many checkable objects belong to each one. 155 type ConfigCheckable interface { 156 UniqueKeyer 157 158 configCheckableSigil() 159 160 CheckableKind() CheckableKind 161 String() string 162 } 163 164 var ( 165 _ ConfigCheckable = ConfigResource{} 166 _ ConfigCheckable = ConfigOutputValue{} 167 ) 168 169 // ParseCheckableStr attempts to parse the given string as a Checkable address 170 // of the given kind. 171 // 172 // This should be the opposite of Checkable.String for any Checkable address 173 // type, as long as "kind" is set to the value returned by the address's 174 // CheckableKind method. 175 // 176 // We do not typically expect users to write out checkable addresses as input, 177 // but we use them as part of some of our wire formats for persisting check 178 // results between runs. 179 func ParseCheckableStr(kind CheckableKind, src string) (Checkable, tfdiags.Diagnostics) { 180 var diags tfdiags.Diagnostics 181 182 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(src), "", hcl.InitialPos) 183 diags = diags.Append(parseDiags) 184 if parseDiags.HasErrors() { 185 return nil, diags 186 } 187 188 path, remain, diags := parseModuleInstancePrefix(traversal) 189 if diags.HasErrors() { 190 return nil, diags 191 } 192 193 if remain.IsRelative() { 194 // (relative means that there's either nothing left or what's next isn't an identifier) 195 diags = diags.Append(&hcl.Diagnostic{ 196 Severity: hcl.DiagError, 197 Summary: "Invalid checkable address", 198 Detail: "Module path must be followed by either a resource instance address or an output value address.", 199 Subject: remain.SourceRange().Ptr(), 200 }) 201 return nil, diags 202 } 203 204 // We use "kind" to disambiguate here because unfortunately we've 205 // historically never reserved "output" as a possible resource type name 206 // and so it is in principle possible -- albeit unlikely -- that there 207 // might be a resource whose type is literally "output". 208 switch kind { 209 case CheckableResource: 210 riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain) 211 diags = diags.Append(moreDiags) 212 if diags.HasErrors() { 213 return nil, diags 214 } 215 return riAddr, diags 216 217 case CheckableOutputValue: 218 if len(remain) != 2 { 219 diags = diags.Append(&hcl.Diagnostic{ 220 Severity: hcl.DiagError, 221 Summary: "Invalid checkable address", 222 Detail: "Output address must have only one attribute part after the keyword 'output', giving the name of the output value.", 223 Subject: remain.SourceRange().Ptr(), 224 }) 225 return nil, diags 226 } 227 if remain.RootName() != "output" { 228 diags = diags.Append(&hcl.Diagnostic{ 229 Severity: hcl.DiagError, 230 Summary: "Invalid checkable address", 231 Detail: "Output address must follow the module address with the keyword 'output'.", 232 Subject: remain.SourceRange().Ptr(), 233 }) 234 return nil, diags 235 } 236 if step, ok := remain[1].(hcl.TraverseAttr); !ok { 237 diags = diags.Append(&hcl.Diagnostic{ 238 Severity: hcl.DiagError, 239 Summary: "Invalid checkable address", 240 Detail: "Output address must have only one attribute part after the keyword 'output', giving the name of the output value.", 241 Subject: remain.SourceRange().Ptr(), 242 }) 243 return nil, diags 244 } else { 245 return OutputValue{Name: step.Name}.Absolute(path), diags 246 } 247 248 default: 249 panic(fmt.Sprintf("unsupported CheckableKind %s", kind)) 250 } 251 }