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