// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package addrs import ( "fmt" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/placeholderplaceholderplaceholder/opentf/internal/tfdiags" ) // OutputValue is the address of an output value, in the context of the module // that is defining it. // // This is related to but separate from ModuleCallOutput, which represents // a module output from the perspective of its parent module. Outputs are // referencable from the testing scope, in general opentf operation users // will be referencing ModuleCallOutput. type OutputValue struct { referenceable Name string } func (v OutputValue) String() string { return "output." + v.Name } func (v OutputValue) Equal(o OutputValue) bool { return v.Name == o.Name } func (v OutputValue) UniqueKey() UniqueKey { return v // An OutputValue is its own UniqueKey } func (v OutputValue) uniqueKeySigil() {} // Absolute converts the receiver into an absolute address within the given // module instance. func (v OutputValue) Absolute(m ModuleInstance) AbsOutputValue { return AbsOutputValue{ Module: m, OutputValue: v, } } // InModule converts the receiver into a config address within the given // module. func (v OutputValue) InModule(m Module) ConfigOutputValue { return ConfigOutputValue{ Module: m, OutputValue: v, } } // AbsOutputValue is the absolute address of an output value within a module instance. // // This represents an output globally within the namespace of a particular // configuration. It is related to but separate from ModuleCallOutput, which // represents a module output from the perspective of its parent module. type AbsOutputValue struct { Module ModuleInstance OutputValue OutputValue } // OutputValue returns the absolute address of an output value of the given // name within the receiving module instance. func (m ModuleInstance) OutputValue(name string) AbsOutputValue { return AbsOutputValue{ Module: m, OutputValue: OutputValue{ Name: name, }, } } func (v AbsOutputValue) CheckRule(t CheckRuleType, i int) CheckRule { return CheckRule{ Container: v, Type: t, Index: i, } } func (v AbsOutputValue) String() string { if v.Module.IsRoot() { return v.OutputValue.String() } return fmt.Sprintf("%s.%s", v.Module.String(), v.OutputValue.String()) } func (v AbsOutputValue) Equal(o AbsOutputValue) bool { return v.OutputValue.Equal(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) CheckableKind() CheckableKind { return CheckableOutputValue } 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() { return AbsOutputValue{}, diags } if len(remain) != 2 { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "An output name is required.", Subject: traversal.SourceRange().Ptr(), }) return AbsOutputValue{}, diags } if remain.RootName() != "output" { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "Output address must start with \"output.\".", Subject: remain[0].SourceRange().Ptr(), }) return AbsOutputValue{}, diags } var name string switch tt := remain[1].(type) { case hcl.TraverseAttr: name = tt.Name default: diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid address", Detail: "An output name is required.", Subject: remain[1].SourceRange().Ptr(), }) return AbsOutputValue{}, diags } return AbsOutputValue{ Module: path, OutputValue: OutputValue{ Name: name, }, }, diags } func ParseAbsOutputValueStr(str string) (AbsOutputValue, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) diags = diags.Append(parseDiags) if parseDiags.HasErrors() { return AbsOutputValue{}, diags } addr, addrDiags := ParseAbsOutputValue(traversal) diags = diags.Append(addrDiags) return addr, diags } // ModuleCallOutput converts an AbsModuleOutput into a ModuleCallOutput, // returning also the module instance that the ModuleCallOutput is relative // to. // // The root module does not have a call, and so this method cannot be used // with outputs in the root module, and will panic in that case. func (v AbsOutputValue) ModuleCallOutput() (ModuleInstance, ModuleCallInstanceOutput) { if v.Module.IsRoot() { panic("ReferenceFromCall used with root module output") } caller, call := v.Module.CallInstance() return caller, ModuleCallInstanceOutput{ Call: call, 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) CheckableKind() CheckableKind { return CheckableOutputValue } func (v ConfigOutputValue) UniqueKey() UniqueKey { return configOutputValueUniqueKey(v.String()) } type configOutputValueUniqueKey string func (k configOutputValueUniqueKey) uniqueKeySigil() {}