mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
These were previously settable only via environment variables. These are now handled as part of CLI Configuration and so also settable in a new "registry_protocols" block in a CLI configuration file, with the environment variables now treated as if they are an additional virtual configuration file containing the corresponding settings. This handles our settings in our modern style where package cliconfig is responsible for deciding the configuration and then package main reacts to that configuration without being aware of how it is decided. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
218 lines
7.7 KiB
Go
218 lines
7.7 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 cliconfig
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/hashicorp/hcl"
|
|
hclast "github.com/hashicorp/hcl/hcl/ast"
|
|
hcltoken "github.com/hashicorp/hcl/hcl/token"
|
|
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
// RegistryProtocolsConfig models the "registry_protocols" configuration block
|
|
// and its associated environment variables.
|
|
//
|
|
// These settings are a little awkward in that they relate to OpenTofu's native
|
|
// module and provider registry protocols, but not to any other module or
|
|
// provider installation methods. The name of this block type assumes that
|
|
// we'd extend these settings to any other OpenTofu-native registry protocols
|
|
// we might add in future, although at the time of writing this comment we have
|
|
// no plans to introduce any, since we're preferring to use industry-standard
|
|
// artifact distribution protocols like OCI Distribution.
|
|
//
|
|
// They also, due to a historical implementation mistake that we preserved for
|
|
// backward-compatibility, also partially influence OpenTofu's native service
|
|
// discovery client regardless of which service protocol it's trying to perform
|
|
// discovery for.
|
|
type RegistryProtocolsConfig struct {
|
|
// RetryCount specifies the number of times OpenTofu should
|
|
// retry making metadata requests to module or provider registries
|
|
// when it encounters a retryable error.
|
|
RetryCount int
|
|
RetryCountSet bool // for tracking overrides between files
|
|
|
|
// RequestTimeout is the amount of time to wait for a response
|
|
// to a metadata request to a module or provider registry.
|
|
RequestTimeout time.Duration
|
|
RequestTimeoutSet bool // for tracking overrides between files
|
|
}
|
|
|
|
func decodeRegistryProtocolsConfigFromConfig(hclFile *hclast.File) (*RegistryProtocolsConfig, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
var ret *RegistryProtocolsConfig
|
|
var retPos hcltoken.Pos
|
|
|
|
root := hclFile.Node.(*hclast.ObjectList)
|
|
for _, block := range root.Items {
|
|
if block.Keys[0].Token.Value() != "registry_protocols" {
|
|
continue
|
|
}
|
|
if ret != nil {
|
|
// We don't allow multiple registry_protocols blocks in the same
|
|
// file because that would be confusing and should never be
|
|
// necessary, but note that we _do_ allow different files (or
|
|
// other sources like the environment) to each have their own
|
|
// block and then our caller is responsible for merging them.
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Duplicate registry_protocols block",
|
|
fmt.Sprintf("The registry protocol settings were already defined at %s.", retPos),
|
|
))
|
|
continue
|
|
}
|
|
|
|
body, ok := block.Val.(*hclast.ObjectType)
|
|
if !ok {
|
|
// We'll get here if the config contains something particularly
|
|
// weird, such as: registry_protocols = "not_an_object"
|
|
//
|
|
// HCL 1 makes it hard to handle all of these variations robustly
|
|
// with good error messages because its AST can have many different
|
|
// shapes depending on what the author wrote, and so we'll just
|
|
// accept this generic generic error message and hope this won't
|
|
// arise often because people will follow the examples we show
|
|
// in our documentation.
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Invalid registry_protocols block",
|
|
fmt.Sprintf("The registry_protocols item at %s must have an open brace after the block type.", block.Pos()),
|
|
))
|
|
continue
|
|
}
|
|
|
|
config, moreDiags := decodeRegistryProtocolsConfigFromConfigBody(body)
|
|
diags = diags.Append(moreDiags)
|
|
ret = config
|
|
retPos = block.Pos()
|
|
}
|
|
|
|
return ret, diags
|
|
}
|
|
|
|
func decodeRegistryProtocolsConfigFromConfigBody(body *hclast.ObjectType) (*RegistryProtocolsConfig, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
ret := &RegistryProtocolsConfig{
|
|
// By the time we get here we definitely have a block, regardless of
|
|
// whether anything is set in it. We'll populate this selectively below.
|
|
}
|
|
|
|
type BodyContent struct {
|
|
RetryCount *int `hcl:"retry_count"`
|
|
RequestTimeout *int `hcl:"request_timeout_seconds"`
|
|
}
|
|
var bodyContent BodyContent
|
|
err := hcl.DecodeObject(&bodyContent, body)
|
|
if err != nil {
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Invalid registry_protocols block",
|
|
fmt.Sprintf("Invalid registry protocol settings at %s: %s.", body.Pos(), err),
|
|
))
|
|
return ret, diags
|
|
}
|
|
if bodyContent.RetryCount != nil {
|
|
ret.RetryCount = *bodyContent.RetryCount
|
|
ret.RetryCountSet = true
|
|
}
|
|
if bodyContent.RequestTimeout != nil {
|
|
ret.RequestTimeout = time.Duration(*bodyContent.RequestTimeout) * time.Second
|
|
ret.RequestTimeoutSet = true
|
|
}
|
|
return ret, diags
|
|
}
|
|
|
|
// decodeRegistryProtocolsConfigFromEnvironment returns a
|
|
// [RegistryProtocolsConfig] object describing a "virtual" registry_protocols
|
|
// configuration block implied by environment variables, if any are set.
|
|
func decodeRegistryProtocolsConfigFromEnvironment() *RegistryProtocolsConfig {
|
|
ret := RegistryProtocolsConfig{}
|
|
if v := os.Getenv(registryDiscoveryRetryEnvName); v != "" {
|
|
override, err := strconv.Atoi(v)
|
|
if err == nil && override > 0 {
|
|
ret.RetryCount = override
|
|
ret.RetryCountSet = true
|
|
}
|
|
}
|
|
if v := os.Getenv(registryClientTimeoutEnvName); v != "" {
|
|
override, err := strconv.Atoi(v)
|
|
if err == nil && override > 0 {
|
|
ret.RequestTimeout = time.Duration(override) * time.Second
|
|
ret.RequestTimeoutSet = true
|
|
}
|
|
}
|
|
if !ret.RetryCountSet && !ret.RequestTimeoutSet {
|
|
// If neither variable is set then we behave as if this virtual
|
|
// configuration block is not present at all.
|
|
return nil
|
|
}
|
|
return &ret
|
|
}
|
|
|
|
// mergeRegistryProtocolConfigs is used by [Config.Merge] for merging registry
|
|
// protocol configurations from multiple different files.
|
|
func mergeRegistryProtocolConfigs(base, override *RegistryProtocolsConfig) *RegistryProtocolsConfig {
|
|
// We only have work to do here if both arguments are set.
|
|
if base == nil {
|
|
return override
|
|
}
|
|
if override == nil {
|
|
return base
|
|
}
|
|
|
|
// If we're going to change anything then we must always produce a new
|
|
// object, because the caller expects both of the given objects to
|
|
// remain unmodified.
|
|
ret := &RegistryProtocolsConfig{}
|
|
if override.RetryCountSet {
|
|
ret.RetryCount = override.RetryCount
|
|
ret.RetryCountSet = override.RetryCountSet
|
|
} else {
|
|
ret.RetryCount = base.RetryCount
|
|
ret.RetryCountSet = base.RetryCountSet
|
|
}
|
|
if override.RequestTimeoutSet {
|
|
ret.RequestTimeout = override.RequestTimeout
|
|
ret.RequestTimeoutSet = override.RequestTimeoutSet
|
|
} else {
|
|
ret.RequestTimeout = base.RequestTimeout
|
|
ret.RequestTimeoutSet = base.RequestTimeoutSet
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func init() {
|
|
// BuiltinConfig should contain the default values for a registry_protocols
|
|
// block.
|
|
BuiltinConfig.RegistryProtocols = &RegistryProtocolsConfig{
|
|
RetryCount: registryDiscoveryDefaultRetryCount,
|
|
RetryCountSet: true,
|
|
RequestTimeout: registryClientDefaultRequestTimeout,
|
|
RequestTimeoutSet: true,
|
|
}
|
|
}
|
|
|
|
const (
|
|
// registryDiscoveryRetryEnvName is the name of the environment variable that
|
|
// can be configured to customize number of retries for module and provider
|
|
// discovery requests with the remote registry.
|
|
registryDiscoveryRetryEnvName = "TF_REGISTRY_DISCOVERY_RETRY"
|
|
registryDiscoveryDefaultRetryCount = 1
|
|
|
|
// registryClientTimeoutEnvName is the name of the environment variable that
|
|
// can be configured to customize the timeout duration (seconds) for module
|
|
// and provider discovery with a remote registry. For historical reasons
|
|
// this also applies to all service discovery requests regardless of whether
|
|
// they are registry-related.
|
|
registryClientTimeoutEnvName = "TF_REGISTRY_CLIENT_TIMEOUT"
|
|
registryClientDefaultRequestTimeout = 10 * time.Second
|
|
)
|