github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/addrs/output_value.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package addrs 5 6 import ( 7 "fmt" 8 9 "github.com/hashicorp/hcl/v2" 10 "github.com/hashicorp/hcl/v2/hclsyntax" 11 12 "github.com/terramate-io/tf/tfdiags" 13 ) 14 15 // OutputValue is the address of an output value, in the context of the module 16 // that is defining it. 17 // 18 // This is related to but separate from ModuleCallOutput, which represents 19 // a module output from the perspective of its parent module. Outputs are 20 // referencable from the testing scope, in general terraform operation users 21 // will be referencing ModuleCallOutput. 22 type OutputValue struct { 23 referenceable 24 Name string 25 } 26 27 func (v OutputValue) String() string { 28 return "output." + v.Name 29 } 30 31 func (v OutputValue) Equal(o OutputValue) bool { 32 return v.Name == o.Name 33 } 34 35 func (v OutputValue) UniqueKey() UniqueKey { 36 return v // An OutputValue is its own UniqueKey 37 } 38 39 func (v OutputValue) uniqueKeySigil() {} 40 41 // Absolute converts the receiver into an absolute address within the given 42 // module instance. 43 func (v OutputValue) Absolute(m ModuleInstance) AbsOutputValue { 44 return AbsOutputValue{ 45 Module: m, 46 OutputValue: v, 47 } 48 } 49 50 // InModule converts the receiver into a config address within the given 51 // module. 52 func (v OutputValue) InModule(m Module) ConfigOutputValue { 53 return ConfigOutputValue{ 54 Module: m, 55 OutputValue: v, 56 } 57 } 58 59 // AbsOutputValue is the absolute address of an output value within a module instance. 60 // 61 // This represents an output globally within the namespace of a particular 62 // configuration. It is related to but separate from ModuleCallOutput, which 63 // represents a module output from the perspective of its parent module. 64 type AbsOutputValue struct { 65 Module ModuleInstance 66 OutputValue OutputValue 67 } 68 69 // OutputValue returns the absolute address of an output value of the given 70 // name within the receiving module instance. 71 func (m ModuleInstance) OutputValue(name string) AbsOutputValue { 72 return AbsOutputValue{ 73 Module: m, 74 OutputValue: OutputValue{ 75 Name: name, 76 }, 77 } 78 } 79 80 func (v AbsOutputValue) CheckRule(t CheckRuleType, i int) CheckRule { 81 return CheckRule{ 82 Container: v, 83 Type: t, 84 Index: i, 85 } 86 } 87 88 func (v AbsOutputValue) String() string { 89 if v.Module.IsRoot() { 90 return v.OutputValue.String() 91 } 92 return fmt.Sprintf("%s.%s", v.Module.String(), v.OutputValue.String()) 93 } 94 95 func (v AbsOutputValue) Equal(o AbsOutputValue) bool { 96 return v.OutputValue.Equal(o.OutputValue) && v.Module.Equal(o.Module) 97 } 98 99 func (v AbsOutputValue) ConfigOutputValue() ConfigOutputValue { 100 return ConfigOutputValue{ 101 Module: v.Module.Module(), 102 OutputValue: v.OutputValue, 103 } 104 } 105 106 func (v AbsOutputValue) checkableSigil() { 107 // Output values are checkable 108 } 109 110 func (v AbsOutputValue) ConfigCheckable() ConfigCheckable { 111 // Output values are declared by "output" blocks in the configuration, 112 // represented as ConfigOutputValue. 113 return v.ConfigOutputValue() 114 } 115 116 func (v AbsOutputValue) CheckableKind() CheckableKind { 117 return CheckableOutputValue 118 } 119 120 func (v AbsOutputValue) UniqueKey() UniqueKey { 121 return absOutputValueUniqueKey(v.String()) 122 } 123 124 type absOutputValueUniqueKey string 125 126 func (k absOutputValueUniqueKey) uniqueKeySigil() {} 127 128 func ParseAbsOutputValue(traversal hcl.Traversal) (AbsOutputValue, tfdiags.Diagnostics) { 129 path, remain, diags := parseModuleInstancePrefix(traversal) 130 if diags.HasErrors() { 131 return AbsOutputValue{}, diags 132 } 133 134 if len(remain) != 2 { 135 diags = diags.Append(&hcl.Diagnostic{ 136 Severity: hcl.DiagError, 137 Summary: "Invalid address", 138 Detail: "An output name is required.", 139 Subject: traversal.SourceRange().Ptr(), 140 }) 141 return AbsOutputValue{}, diags 142 } 143 144 if remain.RootName() != "output" { 145 diags = diags.Append(&hcl.Diagnostic{ 146 Severity: hcl.DiagError, 147 Summary: "Invalid address", 148 Detail: "Output address must start with \"output.\".", 149 Subject: remain[0].SourceRange().Ptr(), 150 }) 151 return AbsOutputValue{}, diags 152 } 153 154 var name string 155 switch tt := remain[1].(type) { 156 case hcl.TraverseAttr: 157 name = tt.Name 158 default: 159 diags = diags.Append(&hcl.Diagnostic{ 160 Severity: hcl.DiagError, 161 Summary: "Invalid address", 162 Detail: "An output name is required.", 163 Subject: remain[1].SourceRange().Ptr(), 164 }) 165 return AbsOutputValue{}, diags 166 } 167 168 return AbsOutputValue{ 169 Module: path, 170 OutputValue: OutputValue{ 171 Name: name, 172 }, 173 }, diags 174 } 175 176 func ParseAbsOutputValueStr(str string) (AbsOutputValue, tfdiags.Diagnostics) { 177 var diags tfdiags.Diagnostics 178 179 traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) 180 diags = diags.Append(parseDiags) 181 if parseDiags.HasErrors() { 182 return AbsOutputValue{}, diags 183 } 184 185 addr, addrDiags := ParseAbsOutputValue(traversal) 186 diags = diags.Append(addrDiags) 187 return addr, diags 188 } 189 190 // ModuleCallOutput converts an AbsModuleOutput into a ModuleCallOutput, 191 // returning also the module instance that the ModuleCallOutput is relative 192 // to. 193 // 194 // The root module does not have a call, and so this method cannot be used 195 // with outputs in the root module, and will panic in that case. 196 func (v AbsOutputValue) ModuleCallOutput() (ModuleInstance, ModuleCallInstanceOutput) { 197 if v.Module.IsRoot() { 198 panic("ReferenceFromCall used with root module output") 199 } 200 201 caller, call := v.Module.CallInstance() 202 return caller, ModuleCallInstanceOutput{ 203 Call: call, 204 Name: v.OutputValue.Name, 205 } 206 } 207 208 // ConfigOutputValue represents a particular "output" block in the 209 // configuration, which might have many AbsOutputValue addresses associated 210 // with it at runtime if it belongs to a module that was called using 211 // "count" or "for_each". 212 type ConfigOutputValue struct { 213 Module Module 214 OutputValue OutputValue 215 } 216 217 func (v ConfigOutputValue) String() string { 218 if v.Module.IsRoot() { 219 return v.OutputValue.String() 220 } 221 return fmt.Sprintf("%s.%s", v.Module.String(), v.OutputValue.String()) 222 } 223 224 func (v ConfigOutputValue) configCheckableSigil() { 225 // ConfigOutputValue is the ConfigCheckable for AbsOutputValue. 226 } 227 228 func (v ConfigOutputValue) CheckableKind() CheckableKind { 229 return CheckableOutputValue 230 } 231 232 func (v ConfigOutputValue) UniqueKey() UniqueKey { 233 return configOutputValueUniqueKey(v.String()) 234 } 235 236 type configOutputValueUniqueKey string 237 238 func (k configOutputValueUniqueKey) uniqueKeySigil() {}