opentofu/commands.go
Martin Atkins e2380b1038 cliconfig: Allow forcing use of the plugin cache despite the lock file
Currently Terraform will use an entry from the global plugin cache only if
it matches a checksum already recorded in the dependency lock file. This
allows Terraform to produce a complete lock file entry on the first
encounter with a new provider, whereas using the cache in that case would
cause the lock file to only cover the single package in the cache and
thereefore be unusable on any other operating system or CPU architecture.

This temporary CLI config option is a pragmatic exception to support those
who cannot currently correctly use the dependency lock file but who still
want to benefit from the plugin cache. With this setting enabled,
Terraform has permission to produce a dependency lock file that is only
suitable for the current system if that would allow use of an existing
entry in the plugin cache.

We are introducing this option to resolve a conflict between the needs of
folks who are using the dependency lock file as expected and the needs of
folks who cannot use the dependency lock file for some reason. The hope
then is to give respite to those who need this exception in the meantime
while we understand better why they cannot use the dependency lock file
and improve its design so that everyone will be able to use it
successfully in a future version of Terraform. This option will become a
silent no-op in a future version of Terraform, once the dependency lock
file behavior is sufficient for all supported Terraform development
workflows.
2023-01-25 08:23:01 -08:00

437 lines
10 KiB
Go

package main
import (
"os"
"os/signal"
"github.com/mitchellh/cli"
"github.com/hashicorp/go-plugin"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/auth"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command"
"github.com/hashicorp/terraform/internal/command/cliconfig"
"github.com/hashicorp/terraform/internal/command/views"
"github.com/hashicorp/terraform/internal/command/webbrowser"
"github.com/hashicorp/terraform/internal/getproviders"
pluginDiscovery "github.com/hashicorp/terraform/internal/plugin/discovery"
"github.com/hashicorp/terraform/internal/terminal"
)
// runningInAutomationEnvName gives the name of an environment variable that
// can be set to any non-empty value in order to suppress certain messages
// that assume that Terraform is being run from a command prompt.
const runningInAutomationEnvName = "TF_IN_AUTOMATION"
// Commands is the mapping of all the available Terraform commands.
var Commands map[string]cli.CommandFactory
// PrimaryCommands is an ordered sequence of the top-level commands (not
// subcommands) that we emphasize at the top of our help output. This is
// ordered so that we can show them in the typical workflow order, rather
// than in alphabetical order. Anything not in this sequence or in the
// HiddenCommands set appears under "all other commands".
var PrimaryCommands []string
// HiddenCommands is a set of top-level commands (not subcommands) that are
// not advertised in the top-level help at all. This is typically because
// they are either just stubs that return an error message about something
// no longer being supported or backward-compatibility aliases for other
// commands.
//
// No commands in the PrimaryCommands sequence should also appear in the
// HiddenCommands set, because that would be rather silly.
var HiddenCommands map[string]struct{}
// Ui is the cli.Ui used for communicating to the outside world.
var Ui cli.Ui
func initCommands(
originalWorkingDir string,
streams *terminal.Streams,
config *cliconfig.Config,
services *disco.Disco,
providerSrc getproviders.Source,
providerDevOverrides map[addrs.Provider]getproviders.PackageLocalDir,
unmanagedProviders map[addrs.Provider]*plugin.ReattachConfig,
) {
var inAutomation bool
if v := os.Getenv(runningInAutomationEnvName); v != "" {
inAutomation = true
}
for userHost, hostConfig := range config.Hosts {
host, err := svchost.ForComparison(userHost)
if err != nil {
// We expect the config was already validated by the time we get
// here, so we'll just ignore invalid hostnames.
continue
}
services.ForceHostServices(host, hostConfig.Services)
}
configDir, err := cliconfig.ConfigDir()
if err != nil {
configDir = "" // No config dir available (e.g. looking up a home directory failed)
}
wd := WorkingDir(originalWorkingDir, os.Getenv("TF_DATA_DIR"))
meta := command.Meta{
WorkingDir: wd,
Streams: streams,
View: views.NewView(streams).SetRunningInAutomation(inAutomation),
Color: true,
GlobalPluginDirs: globalPluginDirs(),
Ui: Ui,
Services: services,
BrowserLauncher: webbrowser.NewNativeLauncher(),
RunningInAutomation: inAutomation,
CLIConfigDir: configDir,
PluginCacheDir: config.PluginCacheDir,
PluginCacheMayBreakDependencyLockFile: config.PluginCacheMayBreakDependencyLockFile,
ShutdownCh: makeShutdownCh(),
ProviderSource: providerSrc,
ProviderDevOverrides: providerDevOverrides,
UnmanagedProviders: unmanagedProviders,
AllowExperimentalFeatures: ExperimentsAllowed(),
}
// The command list is included in the terraform -help
// output, which is in turn included in the docs at
// website/docs/cli/commands/index.html.markdown; if you
// add, remove or reclassify commands then consider updating
// that to match.
Commands = map[string]cli.CommandFactory{
"apply": func() (cli.Command, error) {
return &command.ApplyCommand{
Meta: meta,
}, nil
},
"console": func() (cli.Command, error) {
return &command.ConsoleCommand{
Meta: meta,
}, nil
},
"destroy": func() (cli.Command, error) {
return &command.ApplyCommand{
Meta: meta,
Destroy: true,
}, nil
},
"env": func() (cli.Command, error) {
return &command.WorkspaceCommand{
Meta: meta,
LegacyName: true,
}, nil
},
"env list": func() (cli.Command, error) {
return &command.WorkspaceListCommand{
Meta: meta,
LegacyName: true,
}, nil
},
"env select": func() (cli.Command, error) {
return &command.WorkspaceSelectCommand{
Meta: meta,
LegacyName: true,
}, nil
},
"env new": func() (cli.Command, error) {
return &command.WorkspaceNewCommand{
Meta: meta,
LegacyName: true,
}, nil
},
"env delete": func() (cli.Command, error) {
return &command.WorkspaceDeleteCommand{
Meta: meta,
LegacyName: true,
}, nil
},
"fmt": func() (cli.Command, error) {
return &command.FmtCommand{
Meta: meta,
}, nil
},
"get": func() (cli.Command, error) {
return &command.GetCommand{
Meta: meta,
}, nil
},
"graph": func() (cli.Command, error) {
return &command.GraphCommand{
Meta: meta,
}, nil
},
"import": func() (cli.Command, error) {
return &command.ImportCommand{
Meta: meta,
}, nil
},
"init": func() (cli.Command, error) {
return &command.InitCommand{
Meta: meta,
}, nil
},
"login": func() (cli.Command, error) {
return &command.LoginCommand{
Meta: meta,
}, nil
},
"logout": func() (cli.Command, error) {
return &command.LogoutCommand{
Meta: meta,
}, nil
},
"output": func() (cli.Command, error) {
return &command.OutputCommand{
Meta: meta,
}, nil
},
"plan": func() (cli.Command, error) {
return &command.PlanCommand{
Meta: meta,
}, nil
},
"providers": func() (cli.Command, error) {
return &command.ProvidersCommand{
Meta: meta,
}, nil
},
"providers lock": func() (cli.Command, error) {
return &command.ProvidersLockCommand{
Meta: meta,
}, nil
},
"providers mirror": func() (cli.Command, error) {
return &command.ProvidersMirrorCommand{
Meta: meta,
}, nil
},
"providers schema": func() (cli.Command, error) {
return &command.ProvidersSchemaCommand{
Meta: meta,
}, nil
},
"push": func() (cli.Command, error) {
return &command.PushCommand{
Meta: meta,
}, nil
},
"refresh": func() (cli.Command, error) {
return &command.RefreshCommand{
Meta: meta,
}, nil
},
"show": func() (cli.Command, error) {
return &command.ShowCommand{
Meta: meta,
}, nil
},
"taint": func() (cli.Command, error) {
return &command.TaintCommand{
Meta: meta,
}, nil
},
"test": func() (cli.Command, error) {
return &command.TestCommand{
Meta: meta,
}, nil
},
"validate": func() (cli.Command, error) {
return &command.ValidateCommand{
Meta: meta,
}, nil
},
"version": func() (cli.Command, error) {
return &command.VersionCommand{
Meta: meta,
Version: Version,
VersionPrerelease: VersionPrerelease,
Platform: getproviders.CurrentPlatform,
CheckFunc: commandVersionCheck,
}, nil
},
"untaint": func() (cli.Command, error) {
return &command.UntaintCommand{
Meta: meta,
}, nil
},
"workspace": func() (cli.Command, error) {
return &command.WorkspaceCommand{
Meta: meta,
}, nil
},
"workspace list": func() (cli.Command, error) {
return &command.WorkspaceListCommand{
Meta: meta,
}, nil
},
"workspace select": func() (cli.Command, error) {
return &command.WorkspaceSelectCommand{
Meta: meta,
}, nil
},
"workspace show": func() (cli.Command, error) {
return &command.WorkspaceShowCommand{
Meta: meta,
}, nil
},
"workspace new": func() (cli.Command, error) {
return &command.WorkspaceNewCommand{
Meta: meta,
}, nil
},
"workspace delete": func() (cli.Command, error) {
return &command.WorkspaceDeleteCommand{
Meta: meta,
}, nil
},
//-----------------------------------------------------------
// Plumbing
//-----------------------------------------------------------
"force-unlock": func() (cli.Command, error) {
return &command.UnlockCommand{
Meta: meta,
}, nil
},
"state": func() (cli.Command, error) {
return &command.StateCommand{}, nil
},
"state list": func() (cli.Command, error) {
return &command.StateListCommand{
Meta: meta,
}, nil
},
"state rm": func() (cli.Command, error) {
return &command.StateRmCommand{
StateMeta: command.StateMeta{
Meta: meta,
},
}, nil
},
"state mv": func() (cli.Command, error) {
return &command.StateMvCommand{
StateMeta: command.StateMeta{
Meta: meta,
},
}, nil
},
"state pull": func() (cli.Command, error) {
return &command.StatePullCommand{
Meta: meta,
}, nil
},
"state push": func() (cli.Command, error) {
return &command.StatePushCommand{
Meta: meta,
}, nil
},
"state show": func() (cli.Command, error) {
return &command.StateShowCommand{
Meta: meta,
}, nil
},
"state replace-provider": func() (cli.Command, error) {
return &command.StateReplaceProviderCommand{
StateMeta: command.StateMeta{
Meta: meta,
},
}, nil
},
}
PrimaryCommands = []string{
"init",
"validate",
"plan",
"apply",
"destroy",
}
HiddenCommands = map[string]struct{}{
"env": struct{}{},
"internal-plugin": struct{}{},
"push": struct{}{},
}
}
// makeShutdownCh creates an interrupt listener and returns a channel.
// A message will be sent on the channel for every interrupt received.
func makeShutdownCh() <-chan struct{} {
resultCh := make(chan struct{})
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, ignoreSignals...)
signal.Notify(signalCh, forwardSignals...)
go func() {
for {
<-signalCh
resultCh <- struct{}{}
}
}()
return resultCh
}
func credentialsSource(config *cliconfig.Config) (auth.CredentialsSource, error) {
helperPlugins := pluginDiscovery.FindPlugins("credentials", globalPluginDirs())
return config.CredentialsSource(helperPlugins)
}