mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 09:48:32 -05:00
Previously the Go toolchain had no explicit support for "tools" and so we used the typical Go community workaround of adding "tools.go" files (two, for some reason) that existed only to trick the Go toolchain into considering the tools as dependencies we could track in go.mod. Go 1.24 introduced explicit support for tracking tools as part of go.mod, and the ability to run those using "go tool" instead of "go run", and so this commit switches us over to using that strategy for everything we were previously managing in tools.go. There are some intentional exceptions here: - The protobuf-compile script can't use "go tool" or "go run" because the tools in question are run only indirectly through protoc. However, we do still use the "tool" directive in go.mod to tell the Go toolchain that we depend on those tools, so that it'll track which versions we are currently using as part of go.mod. - Our golangci-lint Makefile target uses "go run" to run a specific version of golangci-lint. We _intentionally_ don't consider that tool to be a direct dependency of OpenTofu because it has a lot of indirect dependencies that would pollute our go.mod file. Therefore that continues to use "go run" after this commit. - Both of our tools.go files previously referred to github.com/nishanths/exhaustive , but nothing actually appears to be using that tool in the current OpenTofu tree, so it's no longer a dependency after this commit. All of the dependencies we have _only_ for tools are now classified as "indirect" in the go.mod file. This is the default behavior of the Go toolchain and appears to be motivated by making it clearer that these modules do not contribute anything to the runtime behavior of OpenTofu. This also corrected a historical oddity in our go.mod where for some reason the "indirect" dependencies had been split across two different "require" directives; they are now all grouped together in a single directive. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
197 lines
6.2 KiB
Go
197 lines
6.2 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package addrs
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
// Checkable is an interface implemented by all address types that can contain
|
|
// condition blocks.
|
|
type Checkable interface {
|
|
UniqueKeyer
|
|
|
|
checkableSigil()
|
|
|
|
// CheckRule returns the address of an individual check rule of a specified
|
|
// type and index within this checkable container.
|
|
CheckRule(CheckRuleType, int) CheckRule
|
|
|
|
// 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
|
|
|
|
CheckableKind() CheckableKind
|
|
String() string
|
|
}
|
|
|
|
var (
|
|
_ Checkable = AbsResourceInstance{}
|
|
_ Checkable = AbsOutputValue{}
|
|
)
|
|
|
|
// CheckableKind describes the different kinds of checkable objects.
|
|
type CheckableKind rune
|
|
|
|
//go:generate go tool golang.org/x/tools/cmd/stringer -type=CheckableKind checkable.go
|
|
|
|
const (
|
|
CheckableKindInvalid CheckableKind = 0
|
|
CheckableResource CheckableKind = 'R'
|
|
CheckableOutputValue CheckableKind = 'O'
|
|
CheckableCheck CheckableKind = 'C'
|
|
CheckableInputVariable CheckableKind = 'I'
|
|
)
|
|
|
|
// 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()
|
|
|
|
CheckableKind() CheckableKind
|
|
String() string
|
|
}
|
|
|
|
var (
|
|
_ ConfigCheckable = ConfigResource{}
|
|
_ ConfigCheckable = ConfigOutputValue{}
|
|
)
|
|
|
|
// ParseCheckableStr attempts to parse the given string as a Checkable address
|
|
// of the given kind.
|
|
//
|
|
// This should be the opposite of Checkable.String for any Checkable address
|
|
// type, as long as "kind" is set to the value returned by the address's
|
|
// CheckableKind method.
|
|
//
|
|
// We do not typically expect users to write out checkable addresses as input,
|
|
// but we use them as part of some of our wire formats for persisting check
|
|
// results between runs.
|
|
func ParseCheckableStr(kind CheckableKind, src string) (Checkable, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(src), "", hcl.InitialPos)
|
|
diags = diags.Append(parseDiags)
|
|
if parseDiags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
path, remain, diags := parseModuleInstancePrefix(traversal)
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
if remain.IsRelative() {
|
|
// (relative means that there's either nothing left or what's next isn't an identifier)
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid checkable address",
|
|
Detail: "Module path must be followed by either a resource instance address or an output value address.",
|
|
Subject: remain.SourceRange().Ptr(),
|
|
})
|
|
return nil, diags
|
|
}
|
|
|
|
getCheckableName := func(keyword string, descriptor string) (string, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
var name string
|
|
|
|
if len(remain) != 2 {
|
|
diags = diags.Append(hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid checkable address",
|
|
Detail: fmt.Sprintf("%s address must have only one attribute part after the keyword '%s', giving the name of the %s.", cases.Title(language.English, cases.NoLower).String(keyword), keyword, descriptor),
|
|
Subject: remain.SourceRange().Ptr(),
|
|
})
|
|
}
|
|
|
|
if remain.RootName() != keyword {
|
|
diags = diags.Append(hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid checkable address",
|
|
Detail: fmt.Sprintf("%s address must follow the module address with the keyword '%s'.", cases.Title(language.English, cases.NoLower).String(keyword), keyword),
|
|
Subject: remain.SourceRange().Ptr(),
|
|
})
|
|
}
|
|
if step, ok := remain[1].(hcl.TraverseAttr); !ok {
|
|
diags = diags.Append(hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid checkable address",
|
|
Detail: fmt.Sprintf("%s address must have only one attribute part after the keyword '%s', giving the name of the %s.", cases.Title(language.English, cases.NoLower).String(keyword), keyword, descriptor),
|
|
Subject: remain.SourceRange().Ptr(),
|
|
})
|
|
} else {
|
|
name = step.Name
|
|
}
|
|
|
|
return name, diags
|
|
}
|
|
|
|
// We use "kind" to disambiguate here because unfortunately we've
|
|
// historically never reserved "output" as a possible resource type name
|
|
// and so it is in principle possible -- albeit unlikely -- that there
|
|
// might be a resource whose type is literally "output".
|
|
switch kind {
|
|
case CheckableResource:
|
|
riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain)
|
|
diags = diags.Append(moreDiags)
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
return riAddr, diags
|
|
|
|
case CheckableOutputValue:
|
|
name, nameDiags := getCheckableName("output", "output value")
|
|
diags = diags.Append(nameDiags)
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
return OutputValue{Name: name}.Absolute(path), diags
|
|
|
|
case CheckableCheck:
|
|
name, nameDiags := getCheckableName("check", "check block")
|
|
diags = diags.Append(nameDiags)
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
return Check{Name: name}.Absolute(path), diags
|
|
|
|
case CheckableInputVariable:
|
|
name, nameDiags := getCheckableName("var", "variable value")
|
|
diags = diags.Append(nameDiags)
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
return InputVariable{Name: name}.Absolute(path), diags
|
|
|
|
default:
|
|
panic(fmt.Sprintf("unsupported CheckableKind %s", kind))
|
|
}
|
|
}
|