mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
This introduces the concept of "backend aliases", which are alternative names that can be used to refer to a given backend. Each backend type has one canonical name and zero or more alias names. The "backend" block in the root module can specify either a canonical backend type or an alias, but internally OpenTofu will always track the backend type using its canonical name. In particular, the following are all true when the configuration specifies an alias instead of a canonical backend type: - The "tofu init" output includes a brief extra message saying which backend type OpenTofu actually used, because that is the name that we'd prioritize in our documentation and so an operator can use the canonical type to find the relevant docs when needed. - The .terraform/terraform.tfstate file that tracks the working directory's currently-initialized backend settings always uses the canonical backend type, and so it's possible to freely switch between aliases and canonical without "tofu init" thinking that a state migration might be needed. - Plan files similarly use the canonical backend type to track which backend was active when the plan was created, which doesn't have any significant user-facing purpose, but is consistent with the previous point since the settings in the plan file effectively substitute for the .terraform/terraform.tfstate file when applying a saved plan. - The terraform_remote_state data source in the provider terraform.io/builtin/terraform accepts both canonical and alias in its backend type argument, treating both as equivalent for the purpose of fetching the state snapshot for the configured workspace. The primary motivation for this new facility is to allow the planned "oracle_oci" backend to have an alias "oci" to allow writing configurations that are cross-compatible with HashiCorp Terraform, since that software has chosen to have unqualified OCI mean Oracle's system, whereas OpenTofu has previously established that unqualified OCI means "Open Container Initiative" in our ecosystem. In particular, this design makes it possible in principle to bring an existing Terraform configuration specifying backend "oci" over to OpenTofu without modifications, and then to optionally switch it to specifying backend "oracle-oci" at a later time without a spurious prompt to migrate state snapshots to the same physical location where they are already stored. This commit doesn't actually introduce any aliases and therefore doesn't have any tests for the new mechanism because our backend system uses a global table that isn't friendly to mocking for testing purposes. I've tested this manually using a placeholder alias to have confidence that it works, and I expect that a subsequent commit introducing the new "oracle_oci" backend will also introduce its "oci" alias and will include tests that cover use of the alias and migration from the alias to the canonical name and vice-versa. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
181 lines
7.7 KiB
Go
181 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 init contains the list of backends that can be initialized and
|
|
// basic helper functions for initializing those backends.
|
|
package init
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/opentofu/svchost/disco"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/backend"
|
|
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
|
|
backendRemote "github.com/opentofu/opentofu/internal/backend/remote"
|
|
backendAzure "github.com/opentofu/opentofu/internal/backend/remote-state/azure"
|
|
backendConsul "github.com/opentofu/opentofu/internal/backend/remote-state/consul"
|
|
backendCos "github.com/opentofu/opentofu/internal/backend/remote-state/cos"
|
|
backendGCS "github.com/opentofu/opentofu/internal/backend/remote-state/gcs"
|
|
backendHTTP "github.com/opentofu/opentofu/internal/backend/remote-state/http"
|
|
backendInmem "github.com/opentofu/opentofu/internal/backend/remote-state/inmem"
|
|
backendKubernetes "github.com/opentofu/opentofu/internal/backend/remote-state/kubernetes"
|
|
backendOSS "github.com/opentofu/opentofu/internal/backend/remote-state/oss"
|
|
backendPg "github.com/opentofu/opentofu/internal/backend/remote-state/pg"
|
|
backendS3 "github.com/opentofu/opentofu/internal/backend/remote-state/s3"
|
|
backendCloud "github.com/opentofu/opentofu/internal/cloud"
|
|
"github.com/opentofu/opentofu/internal/encryption"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
// Backends are hardcoded into OpenTofu because the API for backends uses
|
|
// complex structures and supporting that over the plugin system is currently
|
|
// prohibitively difficult. For those wanting to implement a custom backend,
|
|
// they can do so with recompilation.
|
|
|
|
// backends is the list of available backends. This is a global variable
|
|
// because backends are currently hardcoded into OpenTofu and can't be
|
|
// modified without recompilation.
|
|
//
|
|
// To read an available backend, use the Backend function. This ensures
|
|
// safe concurrent read access to the list of built-in backends by holding
|
|
// [backendsLock].
|
|
var backends map[string]backend.InitFn
|
|
|
|
// backendAliases complements [backends] by allowing alternative names for some
|
|
// backends. The keys are the alias names and the values are the canonical
|
|
// names. OpenTofu always normalizes any use of an alias into its canonical
|
|
// name so that the two are effectively interchangable.
|
|
//
|
|
// [backendsLock] also covers access to backendAliases. Use the Backend function
|
|
// to safely access both of these maps.
|
|
var backendAliases map[string]string
|
|
|
|
// backendsLock is a mutex that must be held when accessing either [backends]
|
|
// or [backendAliases].
|
|
var backendsLock sync.Mutex
|
|
|
|
// RemovedBackends is a record of previously supported backends which have
|
|
// since been deprecated and removed.
|
|
var RemovedBackends map[string]string
|
|
|
|
// Init initializes the backends map with all our hardcoded backends.
|
|
func Init(services *disco.Disco) {
|
|
backendsLock.Lock()
|
|
defer backendsLock.Unlock()
|
|
|
|
// NOTE: Underscore-prefixed named are reserved for unit testing use via
|
|
// the RegisterTemp function. Do not add any underscore-prefixed names
|
|
// to the following table.
|
|
|
|
backends = map[string]backend.InitFn{
|
|
"local": func(enc encryption.StateEncryption) backend.Backend { return backendLocal.New(enc) },
|
|
"remote": func(enc encryption.StateEncryption) backend.Backend { return backendRemote.New(services, enc) },
|
|
|
|
// Remote State backends.
|
|
"azurerm": func(enc encryption.StateEncryption) backend.Backend { return backendAzure.New(enc) },
|
|
"consul": func(enc encryption.StateEncryption) backend.Backend { return backendConsul.New(enc) },
|
|
"cos": func(enc encryption.StateEncryption) backend.Backend { return backendCos.New(enc) },
|
|
"gcs": func(enc encryption.StateEncryption) backend.Backend { return backendGCS.New(enc) },
|
|
"http": func(enc encryption.StateEncryption) backend.Backend { return backendHTTP.New(enc) },
|
|
"inmem": func(enc encryption.StateEncryption) backend.Backend { return backendInmem.New(enc) },
|
|
"kubernetes": func(enc encryption.StateEncryption) backend.Backend { return backendKubernetes.New(enc) },
|
|
"oss": func(enc encryption.StateEncryption) backend.Backend { return backendOSS.New(enc) },
|
|
"pg": func(enc encryption.StateEncryption) backend.Backend { return backendPg.New(enc) },
|
|
"s3": func(enc encryption.StateEncryption) backend.Backend { return backendS3.New(enc) },
|
|
|
|
// Terraform Cloud 'backend'
|
|
// This is an implementation detail only, used for the cloud package
|
|
"cloud": func(enc encryption.StateEncryption) backend.Backend { return backendCloud.New(services, enc) },
|
|
}
|
|
backendAliases = map[string]string{
|
|
// There are currently no backend aliases
|
|
}
|
|
|
|
RemovedBackends = map[string]string{
|
|
"artifactory": `The "artifactory" backend is not supported in OpenTofu v1.3 or later.`,
|
|
"azure": `The "azure" backend name has been removed, please use "azurerm".`,
|
|
"etcd": `The "etcd" backend is not supported in OpenTofu v1.3 or later.`,
|
|
"etcdv3": `The "etcdv3" backend is not supported in OpenTofu v1.3 or later.`,
|
|
"manta": `The "manta" backend is not supported in OpenTofu v1.3 or later.`,
|
|
"swift": `The "swift" backend is not supported in OpenTofu v1.3 or later.`,
|
|
}
|
|
}
|
|
|
|
// Backend returns the initialization factory for the given backend, or
|
|
// nil if none exists.
|
|
//
|
|
// The second return value is the canonical name for the selected backend,
|
|
// if any, which should be used in the UI and in OpenTofu's records of which
|
|
// backend is active in a particular working directory.
|
|
func Backend(name string) (backend.InitFn, string) {
|
|
backendsLock.Lock()
|
|
defer backendsLock.Unlock()
|
|
if alias, ok := backendAliases[name]; ok {
|
|
name = alias
|
|
}
|
|
return backends[name], name
|
|
}
|
|
|
|
// Set sets a new backend in the list of backends. If f is nil then the
|
|
// backend will be removed from the map. If this backend already exists
|
|
// then it will be overwritten.
|
|
//
|
|
// This method sets this backend globally and care should be taken to do
|
|
// this only before OpenTofu is executing to prevent odd behavior of backends
|
|
// changing mid-execution.
|
|
//
|
|
// NOTE: Underscore-prefixed named are reserved for unit testing use via
|
|
// the RegisterTemp function. Do not add any underscore-prefixed names
|
|
// using this function.
|
|
func Set(name string, f backend.InitFn) {
|
|
backendsLock.Lock()
|
|
defer backendsLock.Unlock()
|
|
|
|
if f == nil {
|
|
delete(backends, name)
|
|
return
|
|
}
|
|
|
|
backends[name] = f
|
|
}
|
|
|
|
// deprecatedBackendShim is used to wrap a backend and inject a deprecation
|
|
// warning into the Validate method.
|
|
type deprecatedBackendShim struct {
|
|
backend.Backend
|
|
Message string
|
|
}
|
|
|
|
// PrepareConfig delegates to the wrapped backend to validate its config
|
|
// and then appends shim's deprecation warning.
|
|
func (b deprecatedBackendShim) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) {
|
|
newObj, diags := b.Backend.PrepareConfig(obj)
|
|
return newObj, diags.Append(tfdiags.SimpleWarning(b.Message))
|
|
}
|
|
|
|
// DeprecateBackend can be used to wrap a backend to return a deprecation
|
|
// warning during validation.
|
|
func deprecateBackend(b backend.Backend, message string) backend.Backend {
|
|
// Since a Backend wrapped by deprecatedBackendShim can no longer be
|
|
// asserted as an Enhanced or Local backend, disallow those types here
|
|
// entirely. If something other than a basic backend.Backend needs to be
|
|
// deprecated, we can add that functionality to schema.Backend or the
|
|
// backend itself.
|
|
if _, ok := b.(backend.Enhanced); ok {
|
|
panic("cannot use DeprecateBackend on an Enhanced Backend")
|
|
}
|
|
|
|
if _, ok := b.(backend.Local); ok {
|
|
panic("cannot use DeprecateBackend on a Local Backend")
|
|
}
|
|
|
|
return deprecatedBackendShim{
|
|
Backend: b,
|
|
Message: message,
|
|
}
|
|
}
|