opentofu/commands.go
Martin Atkins 7f78342953 command: Experimental "terraform test" command
This is just a prototype to gather some feedback in our ongoing research
on integration testing of Terraform modules. The hope is that by having a
command integrated into Terraform itself it'll be easier for interested
module authors to give it a try, and also easier for us to iterate quickly
based on feedback without having to coordinate across multiple codebases.

Everything about this is subject to change even in future patch releases.
Since it's a CLI command rather than a configuration language feature it's
not using the language experiments mechanism, but generates a warning
similar to the one language experiments generate in order to be clear that
backward compatibility is not guaranteed.
2021-02-22 14:21:45 -08:00

434 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/addrs"
"github.com/hashicorp/terraform/command"
"github.com/hashicorp/terraform/command/cliconfig"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/command/webbrowser"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/terminal"
pluginDiscovery "github.com/hashicorp/terraform/plugin/discovery"
)
// 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)
}
dataDir := os.Getenv("TF_DATA_DIR")
meta := command.Meta{
OriginalWorkingDir: originalWorkingDir,
Streams: streams,
View: views.NewView(streams),
Color: true,
GlobalPluginDirs: globalPluginDirs(),
Ui: Ui,
Services: services,
BrowserLauncher: webbrowser.NewNativeLauncher(),
RunningInAutomation: inAutomation,
CLIConfigDir: configDir,
PluginCacheDir: config.PluginCacheDir,
OverrideDataDir: dataDir,
ShutdownCh: makeShutdownCh(),
ProviderSource: providerSrc,
ProviderDevOverrides: providerDevOverrides,
UnmanagedProviders: unmanagedProviders,
}
// 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)
}