convert all logs to be view type exclusive for human or json format

tf-init-json
UKEME BASSEY 2 years ago
parent 370a471ecb
commit ea8d0869d8

@ -0,0 +1,116 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"flag"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// Init represents the command-line arguments for the init command.
type Init struct {
// FromModule identifies the module to copy into the target directory before init.
FromModule string
// Lockfile specifies a dependency lockfile mode.
Lockfile string
// TestDirectory is the directory containing any test files that should be
// validated alongside the main configuration. Should be relative to the
// Path.
TestsDirectory string
// ViewType specifies which init format to use: human or JSON.
ViewType ViewType
// Backend specifies whether to disable backend or Terraform Cloud initialization.
Backend bool
// Cloud specifies whether to disable backend or Terraform Cloud initialization.
Cloud bool
// Get specifies whether to disable downloading modules for this configuration
Get bool
// ForceInitCopy specifies whether to suppress prompts about copying state data.
ForceInitCopy bool
// StateLock specifies whether hold a state lock during backend migration.
StateLock bool
// StateLockTimeout specifies the duration to wait for a state lock.
StateLockTimeout time.Duration
// Reconfigure specifies whether to disregard any existing configuration, preventing migration of any existing state
Reconfigure bool
// MigrateState specifies whether to attempt to copy existing state to the new backend
MigrateState bool
// Upgrade specifies whether to upgrade modules and plugins as part of their respective installation steps
Upgrade bool
// Json specifies whether to output in JSON format
Json bool
// IgnoreRemoteVersion specifies whether to ignore remote and local Terraform versions compatibility
IgnoreRemoteVersion bool
}
// ParseInit processes CLI arguments, returning an Init value and errors.
// If errors are encountered, an Init value is still returned representing
// the best effort interpretation of the arguments.
func ParseInit(args []string, cmdFlags *flag.FlagSet) (*Init, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
init := &Init{}
cmdFlags.BoolVar(&init.Backend, "backend", true, "")
cmdFlags.BoolVar(&init.Cloud, "cloud", true, "")
cmdFlags.StringVar(&init.FromModule, "from-module", "", "copy the source of the given module into the directory before init")
cmdFlags.BoolVar(&init.Get, "get", true, "")
cmdFlags.BoolVar(&init.ForceInitCopy, "force-copy", false, "suppress prompts about copying state data")
cmdFlags.BoolVar(&init.StateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&init.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.BoolVar(&init.Reconfigure, "reconfigure", false, "reconfigure")
cmdFlags.BoolVar(&init.MigrateState, "migrate-state", false, "migrate state")
cmdFlags.BoolVar(&init.Upgrade, "upgrade", false, "")
cmdFlags.StringVar(&init.Lockfile, "lockfile", "", "Set a dependency lockfile mode")
cmdFlags.BoolVar(&init.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
cmdFlags.StringVar(&init.TestsDirectory, "test-directory", "tests", "test-directory")
cmdFlags.BoolVar(&init.Json, "json", false, "json")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
backendFlagSet := FlagIsSet(cmdFlags, "backend")
cloudFlagSet := FlagIsSet(cmdFlags, "cloud")
if backendFlagSet && cloudFlagSet {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid init options",
"The -backend and -cloud options are aliases of one another and mutually-exclusive in their use",
))
} else if backendFlagSet {
init.Cloud = init.Backend
} else if cloudFlagSet {
init.Backend = init.Cloud
}
switch {
case init.Json:
init.ViewType = ViewJSON
default:
init.ViewType = ViewHuman
}
return init, diags
}

@ -11,26 +11,39 @@ import (
"github.com/hashicorp/terraform/internal/initwd"
)
type view interface {
Log(message string, params ...any)
}
type uiModuleInstallHooks struct {
initwd.ModuleInstallHooksImpl
Ui cli.Ui
ShowLocalPaths bool
View view
}
var _ initwd.ModuleInstallHooks = uiModuleInstallHooks{}
func (h uiModuleInstallHooks) Download(modulePath, packageAddr string, v *version.Version) {
if v != nil {
h.Ui.Info(fmt.Sprintf("Downloading %s %s for %s...", packageAddr, v, modulePath))
h.log(fmt.Sprintf("Downloading %s %s for %s...", packageAddr, v, modulePath))
} else {
h.Ui.Info(fmt.Sprintf("Downloading %s for %s...", packageAddr, modulePath))
h.log(fmt.Sprintf("Downloading %s for %s...", packageAddr, modulePath))
}
}
func (h uiModuleInstallHooks) Install(modulePath string, v *version.Version, localDir string) {
if h.ShowLocalPaths {
h.Ui.Info(fmt.Sprintf("- %s in %s", modulePath, localDir))
h.log(fmt.Sprintf("- %s in %s", modulePath, localDir))
} else {
h.Ui.Info(fmt.Sprintf("- %s", modulePath))
h.log(fmt.Sprintf("- %s", modulePath))
}
}
func (h uiModuleInstallHooks) log(message string) {
switch h.View.(type) {
case view:
h.View.Log(message)
default:
h.Ui.Info(message)
}
}

@ -43,50 +43,47 @@ type InitCommand struct {
}
func (c *InitCommand) Run(args []string) int {
var flagFromModule, flagLockfile, testsDirectory string
var flagBackend, flagCloud, flagGet, flagUpgrade, flagJson bool
var flagPluginPath FlagStringSlice
flagConfigExtra := newRawFlags("-backend-config")
var diags tfdiags.Diagnostics
args = c.Meta.process(args)
cmdFlags := c.Meta.extendedFlagSet("init")
cmdFlags.BoolVar(&flagBackend, "backend", true, "")
cmdFlags.BoolVar(&flagCloud, "cloud", true, "")
cmdFlags.Usage = func() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
c.Help(),
))
}
cmdFlags.Var(flagConfigExtra, "backend-config", "")
cmdFlags.StringVar(&flagFromModule, "from-module", "", "copy the source of the given module into the directory before init")
cmdFlags.BoolVar(&flagGet, "get", true, "")
cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure")
cmdFlags.BoolVar(&c.migrateState, "migrate-state", false, "migrate state")
cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "")
cmdFlags.Var(&flagPluginPath, "plugin-dir", "plugin directory")
cmdFlags.StringVar(&flagLockfile, "lockfile", "", "Set a dependency lockfile mode")
cmdFlags.BoolVar(&c.Meta.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
cmdFlags.StringVar(&testsDirectory, "test-directory", "tests", "test-directory")
cmdFlags.BoolVar(&flagJson, "json", false, "json")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1
}
initArgs, initDiags := arguments.ParseInit(args, cmdFlags)
backendFlagSet := arguments.FlagIsSet(cmdFlags, "backend")
cloudFlagSet := arguments.FlagIsSet(cmdFlags, "cloud")
view := views.NewInit(initArgs.ViewType, c.View)
switch {
case backendFlagSet && cloudFlagSet:
c.Ui.Error("The -backend and -cloud options are aliases of one another and mutually-exclusive in their use")
if initDiags.HasErrors() {
diags = diags.Append(initDiags)
view.Diagnostics(diags)
return 1
case backendFlagSet:
flagCloud = flagBackend
case cloudFlagSet:
flagBackend = flagCloud
}
c.forceInitCopy = initArgs.ForceInitCopy
c.Meta.stateLock = initArgs.StateLock
c.Meta.stateLockTimeout = initArgs.StateLockTimeout
c.reconfigure = initArgs.Reconfigure
c.migrateState = initArgs.MigrateState
c.Meta.ignoreRemoteVersion = initArgs.IgnoreRemoteVersion
if c.migrateState && c.reconfigure {
c.Ui.Error("The -migrate-state and -reconfigure options are mutually-exclusive")
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid init options",
"The -migrate-state and -reconfigure options are mutually-exclusive",
))
view.Diagnostics(diags)
return 1
}
@ -96,17 +93,6 @@ func (c *InitCommand) Run(args []string) int {
c.migrateState = true
}
var viewType arguments.ViewType
switch {
case flagJson:
viewType = arguments.ViewJSON
default:
viewType = arguments.ViewHuman
}
view := views.NewInit(viewType, c.View)
var diags tfdiags.Diagnostics
if len(flagPluginPath) > 0 {
c.pluginPath = flagPluginPath
}
@ -134,8 +120,8 @@ func (c *InitCommand) Run(args []string) int {
// to output a newline before the success message
var header bool
if flagFromModule != "" {
src := flagFromModule
if initArgs.FromModule != "" {
src := initArgs.FromModule
empty, err := configs.IsEmptyDir(path)
if err != nil {
@ -149,14 +135,13 @@ func (c *InitCommand) Run(args []string) int {
return 1
}
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
"[reset][bold]Copying configuration[reset] from %q...", src,
)))
view.Output(views.CopyingConfigurationMessage, src)
header = true
hooks := uiModuleInstallHooks{
hooks := uiModuleInstallHooks{ // here check to verify if downloading prints text, update to handle view type
Ui: c.Ui,
ShowLocalPaths: false, // since they are in a weird location for init
View: view,
}
ctx, span := tracer.Start(ctx, "-from-module=...", trace.WithAttributes(
@ -173,7 +158,7 @@ func (c *InitCommand) Run(args []string) int {
}
span.End()
c.Ui.Output("")
view.Output(views.EmptyMessage)
}
// If our directory is empty, then we're done. We can't get or set up
@ -185,20 +170,19 @@ func (c *InitCommand) Run(args []string) int {
return 1
}
if empty {
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty)))
view.Output(views.OutputInitEmptyMessage)
return 0
}
// Load just the root module to begin backend and module initialization
rootModEarly, earlyConfDiags := c.loadSingleModuleWithTests(path, testsDirectory)
rootModEarly, earlyConfDiags := c.loadSingleModuleWithTests(path, initArgs.TestsDirectory)
// There may be parsing errors in config loading but these will be shown later _after_
// checking for core version requirement errors. Not meeting the version requirement should
// be the first error displayed if that is an issue, but other operations are required
// before being able to check core version requirements.
if rootModEarly == nil {
c.Ui.Error(c.Colorize().Color(strings.TrimSpace(errInitConfigError)))
diags = diags.Append(earlyConfDiags)
diags = diags.Append(fmt.Errorf(view.PrepareMessage(views.InitConfigError)), earlyConfDiags)
view.Diagnostics(diags)
return 1
@ -212,10 +196,10 @@ func (c *InitCommand) Run(args []string) int {
var backendOutput bool
switch {
case flagCloud && rootModEarly.CloudConfig != nil:
back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, flagConfigExtra, viewType)
case flagBackend:
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, flagConfigExtra, viewType)
case initArgs.Cloud && rootModEarly.CloudConfig != nil:
back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, flagConfigExtra, initArgs.ViewType, view)
case initArgs.Backend:
back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, flagConfigExtra, initArgs.ViewType, view)
default:
// load the previously-stored backend config
back, backDiags = c.Meta.backendFromState(ctx)
@ -253,8 +237,8 @@ func (c *InitCommand) Run(args []string) int {
state = sMgr.State()
}
if flagGet {
modsOutput, modsAbort, modsDiags := c.getModules(ctx, path, testsDirectory, rootModEarly, flagUpgrade)
if initArgs.Get {
modsOutput, modsAbort, modsDiags := c.getModules(ctx, path, initArgs.TestsDirectory, rootModEarly, initArgs.Upgrade, view)
diags = diags.Append(modsDiags)
if modsAbort || modsDiags.HasErrors() {
view.Diagnostics(diags)
@ -267,7 +251,7 @@ func (c *InitCommand) Run(args []string) int {
// With all of the modules (hopefully) installed, we can now try to load the
// whole configuration tree.
config, confDiags := c.loadConfigWithTests(path, testsDirectory)
config, confDiags := c.loadConfigWithTests(path, initArgs.TestsDirectory)
// configDiags will be handled after the version constraint check, since an
// incorrect version of terraform may be producing errors for configuration
// constructs added in later versions.
@ -290,13 +274,13 @@ func (c *InitCommand) Run(args []string) int {
diags = diags.Append(earlyConfDiags)
diags = diags.Append(backDiags)
if earlyConfDiags.HasErrors() {
c.Ui.Error(strings.TrimSpace(errInitConfigError))
diags = diags.Append(fmt.Errorf(view.PrepareMessage(views.InitConfigError)))
view.Diagnostics(diags)
return 1
}
// Now, we can show any errors from initializing the backend, but we won't
// show the errInitConfigError preamble as we didn't detect problems with
// show the InitConfigError preamble as we didn't detect problems with
// the early configuration.
if backDiags.HasErrors() {
view.Diagnostics(diags)
@ -307,7 +291,7 @@ func (c *InitCommand) Run(args []string) int {
// show other errors from loading the full configuration tree.
diags = diags.Append(confDiags)
if confDiags.HasErrors() {
c.Ui.Error(strings.TrimSpace(errInitConfigError))
diags = diags.Append(fmt.Errorf(view.PrepareMessage(views.InitConfigError)))
view.Diagnostics(diags)
return 1
}
@ -323,7 +307,7 @@ func (c *InitCommand) Run(args []string) int {
}
// Now that we have loaded all modules, check the module tree for missing providers.
providersOutput, providersAbort, providerDiags := c.getProviders(ctx, config, state, flagUpgrade, flagPluginPath, flagLockfile, view)
providersOutput, providersAbort, providerDiags := c.getProviders(ctx, config, state, initArgs.Upgrade, flagPluginPath, initArgs.Lockfile, view)
diags = diags.Append(providerDiags)
if providersAbort || providerDiags.HasErrors() {
view.Diagnostics(diags)
@ -336,7 +320,7 @@ func (c *InitCommand) Run(args []string) int {
// If we outputted information, then we need to output a newline
// so that our success message is nicely spaced out from prior text.
if header {
c.Ui.Output("")
view.Output(views.EmptyMessage)
}
// If we accumulated any warnings along the way that weren't accompanied
@ -344,27 +328,27 @@ func (c *InitCommand) Run(args []string) int {
// still the final thing shown.
view.Diagnostics(diags)
_, cloud := back.(*cloud.Cloud)
output := outputInitSuccess
output := views.OutputInitSuccessMessage
if cloud {
output = outputInitSuccessCloud
output = views.OutputInitSuccessCloudMessage
}
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(output)))
view.Output(output)
if !c.RunningInAutomation {
// If we're not running in an automation wrapper, give the user
// some more detailed next steps that are appropriate for interactive
// shell usage.
output = outputInitSuccessCLI
output = views.OutputInitSuccessCLIMessage
if cloud {
output = outputInitSuccessCLICloud
output = views.OutputInitSuccessCLICloudMessage
}
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(output)))
view.Output(output)
}
return 0
}
func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, earlyRoot *configs.Module, upgrade bool) (output bool, abort bool, diags tfdiags.Diagnostics) {
func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, earlyRoot *configs.Module, upgrade bool, view views.Init) (output bool, abort bool, diags tfdiags.Diagnostics) {
testModules := false // We can also have modules buried in test files.
for _, file := range earlyRoot.Tests {
for _, run := range file.Runs {
@ -385,14 +369,15 @@ func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, ear
defer span.End()
if upgrade {
c.Ui.Output(c.Colorize().Color("[reset][bold]Upgrading modules..."))
view.Output(views.UpgradingModulesMessage)
} else {
c.Ui.Output(c.Colorize().Color("[reset][bold]Initializing modules..."))
view.Output(views.InitializingModulesMessage)
}
hooks := uiModuleInstallHooks{
Ui: c.Ui,
ShowLocalPaths: true,
View: view,
}
installAbort, installDiags := c.installModules(ctx, path, testsDir, upgrade, false, hooks)
@ -418,12 +403,12 @@ func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, ear
return true, installAbort, diags
}
func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extraConfig rawFlags, viewType arguments.ViewType) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extraConfig rawFlags, viewType arguments.ViewType, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
ctx, span := tracer.Start(ctx, "initialize Terraform Cloud")
_ = ctx // prevent staticcheck from complaining to avoid a maintenence hazard of having the wrong ctx in scope here
defer span.End()
c.Ui.Output(c.Colorize().Color("\n[reset][bold]Initializing Terraform Cloud..."))
view.Output(views.InitializingTerraformCloudMessage)
if len(extraConfig.AllItems()) != 0 {
diags = diags.Append(tfdiags.Sourceless(
@ -447,12 +432,12 @@ func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extra
return back, true, diags
}
func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, extraConfig rawFlags, viewType arguments.ViewType) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, extraConfig rawFlags, viewType arguments.ViewType, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
ctx, span := tracer.Start(ctx, "initialize backend")
_ = ctx // prevent staticcheck from complaining to avoid a maintenence hazard of having the wrong ctx in scope here
defer span.End()
c.Ui.Output(c.Colorize().Color("\n[reset][bold]Initializing the backend..."))
view.Output(views.InitializingBackendMessage)
var backendConfig *configs.Backend
var backendConfigOverride hcl.Body
@ -606,15 +591,13 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
// are shimming our vt100 output to the legacy console API on Windows.
evts := &providercache.InstallerEvents{
PendingProviders: func(reqs map[addrs.Provider]getproviders.VersionConstraints) {
c.Ui.Output(c.Colorize().Color(
"\n[reset][bold]Initializing provider plugins...",
))
view.Output(views.InitializingProviderPluginMessage)
},
ProviderAlreadyInstalled: func(provider addrs.Provider, selectedVersion getproviders.Version) {
c.Ui.Info(fmt.Sprintf("- Using previously-installed %s v%s", provider.ForDisplay(), selectedVersion))
view.Log(views.ProviderAlreadyInstalledMessage, provider.ForDisplay(), selectedVersion)
},
BuiltInProviderAvailable: func(provider addrs.Provider) {
c.Ui.Info(fmt.Sprintf("- %s is built in to Terraform", provider.ForDisplay()))
view.Log(views.BuiltInProviderAvailableMessage, provider.ForDisplay())
},
BuiltInProviderFailure: func(provider addrs.Provider, err error) {
diags = diags.Append(tfdiags.Sourceless(
@ -625,20 +608,20 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
},
QueryPackagesBegin: func(provider addrs.Provider, versionConstraints getproviders.VersionConstraints, locked bool) {
if locked {
c.Ui.Info(fmt.Sprintf("- Reusing previous version of %s from the dependency lock file", provider.ForDisplay()))
view.Log(views.ReusingPreviousVersionInfo, provider.ForDisplay())
} else {
if len(versionConstraints) > 0 {
c.Ui.Info(fmt.Sprintf("- Finding %s versions matching %q...", provider.ForDisplay(), getproviders.VersionConstraintsString(versionConstraints)))
view.Log(views.FindingMatchingVersionMessage, provider.ForDisplay(), getproviders.VersionConstraintsString(versionConstraints))
} else {
c.Ui.Info(fmt.Sprintf("- Finding latest version of %s...", provider.ForDisplay()))
view.Log(views.FindingLatestVersionMessage, provider.ForDisplay())
}
}
},
LinkFromCacheBegin: func(provider addrs.Provider, version getproviders.Version, cacheRoot string) {
c.Ui.Info(fmt.Sprintf("- Using %s v%s from the shared cache directory", provider.ForDisplay(), version))
view.Log(views.UsingProviderFromCacheDirInfo, provider.ForDisplay(), version)
},
FetchPackageBegin: func(provider addrs.Provider, version getproviders.Version, location getproviders.PackageLocation) {
c.Ui.Info(fmt.Sprintf("- Installing %s v%s...", provider.ForDisplay(), version))
view.Log(views.InstallingProviderMessage, provider.ForDisplay(), version)
},
QueryPackagesFailure: func(provider addrs.Provider, err error) {
switch errorTy := err.(type) {
@ -835,10 +818,10 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
keyID = authResult.KeyID
}
if keyID != "" {
keyID = c.Colorize().Color(fmt.Sprintf(", key ID [reset][bold]%s[reset]", keyID))
keyID = view.PrepareMessage(views.KeyID, keyID)
}
c.Ui.Info(fmt.Sprintf("- Installed %s v%s (%s%s)", provider.ForDisplay(), version, authResult, keyID))
view.Log(views.InstalledProviderVersionInfo, provider.ForDisplay(), version, authResult, keyID)
},
ProvidersLockUpdated: func(provider addrs.Provider, version getproviders.Version, localHashes []getproviders.Hash, signedHashes []getproviders.Hash, priorHashes []getproviders.Hash) {
// We're going to use this opportunity to track if we have any
@ -884,9 +867,7 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
}
}
if thirdPartySigned {
c.Ui.Info(fmt.Sprintf("\nPartner and community providers are signed by their developers.\n" +
"If you'd like to know more about provider signing, you can read about it here:\n" +
"https://www.terraform.io/docs/cli/plugins/signing.html"))
view.Log(views.PartnerAndCommunityProvidersMessage)
}
},
}
@ -895,7 +876,8 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
mode := providercache.InstallNewProvidersOnly
if upgrade {
if flagLockfile == "readonly" {
c.Ui.Error("The -upgrade flag conflicts with -lockfile=readonly.")
diags = diags.Append(fmt.Errorf("The -upgrade flag conflicts with -lockfile=readonly."))
view.Diagnostics(diags)
return true, true, diags
}
@ -903,8 +885,8 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
}
newLocks, err := inst.EnsureProviderVersions(ctx, previousLocks, reqs, mode)
if ctx.Err() == context.Canceled {
diags = diags.Append(fmt.Errorf("Provider installation was canceled by an interrupt signal."))
view.Diagnostics(diags)
c.Ui.Error("Provider installation was canceled by an interrupt signal.")
return true, true, diags
}
if err != nil {
@ -971,16 +953,9 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
// say a little about what the dependency lock file is, for new
// users or those who are upgrading from a previous Terraform
// version that didn't have dependency lock files.
c.Ui.Output(c.Colorize().Color(`
Terraform has created a lock file [bold].terraform.lock.hcl[reset] to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.`))
view.Output(views.LockInfo)
} else {
c.Ui.Output(c.Colorize().Color(`
Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.`))
view.Output(views.DependenciesLockChangesInfo)
}
moreDiags = c.replaceLockedDependencies(newLocks)
@ -1213,14 +1188,6 @@ func (c *InitCommand) Synopsis() string {
return "Prepare your working directory for other commands"
}
const errInitConfigError = `
[reset]Terraform encountered problems during initialisation, including problems
with the configuration, described below.
The Terraform configuration must be valid before initialization so that
Terraform can determine which modules and providers need to be installed.
`
const errInitCopyNotEmpty = `
The working directory already contains files. The -from-module option requires
an empty directory into which a copy of the referenced module will be placed.
@ -1229,39 +1196,6 @@ To initialize the configuration already in this working directory, omit the
-from-module option.
`
const outputInitEmpty = `
[reset][bold]Terraform initialized in an empty directory![reset]
The directory has no Terraform configuration files. You may begin working
with Terraform immediately by creating Terraform configuration files.
`
const outputInitSuccess = `
[reset][bold][green]Terraform has been successfully initialized![reset][green]
`
const outputInitSuccessCloud = `
[reset][bold][green]Terraform Cloud has been successfully initialized![reset][green]
`
const outputInitSuccessCLI = `[reset][green]
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
`
const outputInitSuccessCLICloud = `[reset][green]
You may now begin working with Terraform Cloud. Try running "terraform plan" to
see any changes that are required for your infrastructure.
If you ever set or change modules or Terraform Settings, run "terraform init"
again to reinitialize your working directory.
`
// providerProtocolTooOld is a message sent to the CLI UI if the provider's
// supported protocol versions are too old for the user's version of terraform,
// but a newer version of the provider is compatible.

@ -4,7 +4,10 @@
package views
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -13,6 +16,9 @@ import (
// The Init view is used for the init command.
type Init interface {
Diagnostics(diags tfdiags.Diagnostics)
Output(messageCode string, params ...any)
Log(messageCode string, params ...any)
PrepareMessage(messageCode string, params ...any) string
}
// NewInit returns Init implementation for the given ViewType.
@ -43,6 +49,29 @@ func (v *InitHuman) Diagnostics(diags tfdiags.Diagnostics) {
v.view.Diagnostics(diags)
}
func (v *InitHuman) Output(messageCode string, params ...any) {
v.view.streams.Println(v.PrepareMessage(messageCode, params...))
}
func (v *InitHuman) Log(messageCode string, params ...any) {
v.view.streams.Println(v.PrepareMessage(messageCode, params...))
}
func (v *InitHuman) PrepareMessage(messageCode string, params ...any) string {
message, ok := MessageRegistry[messageCode]
if !ok {
// display the message code as fallback if not found in the message registry
return messageCode
}
if message.HumanValue == "" {
// no need to apply colorization if the message is empty
return message.HumanValue
}
return v.view.colorize.Color(strings.TrimSpace(fmt.Sprintf(message.HumanValue, params...)))
}
// The InitJSON implementation renders streaming JSON logs, suitable for
// integrating with other software.
type InitJSON struct {
@ -54,3 +83,314 @@ var _ Init = (*InitJSON)(nil)
func (v *InitJSON) Diagnostics(diags tfdiags.Diagnostics) {
v.view.Diagnostics(diags)
}
func (v *InitJSON) Output(messageCode string, params ...any) {
current_timestamp := time.Now().Format(time.RFC3339)
json_data := map[string]string{
"@level": "info",
"@message": v.PrepareMessage(messageCode, params...),
"@module": "terraform.ui",
"@timestamp": current_timestamp,
"type": "init_output"}
init_output, _ := json.Marshal(json_data)
v.view.view.streams.Println(string(init_output))
}
func (v *InitJSON) Log(messageCode string, params ...any) {
v.view.Log(v.PrepareMessage(messageCode, params...))
}
func (v *InitJSON) PrepareMessage(messageCode string, params ...any) string {
message, ok := MessageRegistry[messageCode]
if !ok {
// display the message code as fallback if not found in the message registry
return messageCode
}
return strings.TrimSpace(fmt.Sprintf(message.JSONValue, params...))
}
// InitMessage represents a message string in both json and human decorated text format.
type InitMessage struct {
HumanValue string
JSONValue string
}
var MessageRegistry map[string]InitMessage = map[string]InitMessage{
"copying_configuration_message": {
HumanValue: "[reset][bold]Copying configuration[reset] from %q...",
JSONValue: "Copying configuration from %q...",
},
"output_init_empty_message": {
HumanValue: outputInitEmpty,
JSONValue: outputInitEmptyJSON,
},
"output_init_success_message": {
HumanValue: outputInitSuccess,
JSONValue: outputInitSuccessJSON,
},
"output_init_success_cloud_message": {
HumanValue: outputInitSuccessCloud,
JSONValue: outputInitSuccessCloudJSON,
},
"output_init_success_cli_message": {
HumanValue: outputInitSuccessCLI,
JSONValue: outputInitSuccessCLI_JSON,
},
"output_init_success_cli_cloud_message": {
HumanValue: outputInitSuccessCLICloud,
JSONValue: outputInitSuccessCLICloudJSON,
},
"upgrading_modules_message": {
HumanValue: "[reset][bold]Upgrading modules...",
JSONValue: "Upgrading modules...",
},
"initializing_modules_message": {
HumanValue: "[reset][bold]Initializing modules...",
JSONValue: "Initializing modules...",
},
"initializing_terraform_cloud_message": {
HumanValue: "\n[reset][bold]Initializing Terraform Cloud...",
JSONValue: "Initializing Terraform Cloud...",
},
"initializing_backend_message": {
HumanValue: "\n[reset][bold]Initializing the backend...",
JSONValue: "Initializing the backend...",
},
"initializing_provider_plugin_message": {
HumanValue: "\n[reset][bold]Initializing provider plugins...",
JSONValue: "Initializing provider plugins...",
},
"dependencies_lock_changes_info": {
HumanValue: dependenciesLockChangesInfo,
JSONValue: dependenciesLockChangesInfo,
},
"lock_info": {
HumanValue: previousLockInfoHuman,
JSONValue: previousLockInfoJSON,
},
"provider_already_installed_message": {
HumanValue: "- Using previously-installed %s v%s",
JSONValue: "- Using previously-installed %s v%s",
},
"built_in_provider_available_message": {
HumanValue: "- %s is built in to Terraform",
JSONValue: "- %s is built in to Terraform",
},
"reusing_previous_version_info": {
HumanValue: "- Reusing previous version of %s from the dependency lock file",
JSONValue: "- Reusing previous version of %s from the dependency lock file",
},
"finding_matching_version_message": {
HumanValue: "- Finding %s versions matching %q...",
JSONValue: "- Finding %s versions matching %q...",
},
"finding_latest_version_message": {
HumanValue: "- Finding latest version of %s...",
JSONValue: "- Finding latest version of %s...",
},
"using_provider_from_cache_dir_info": {
HumanValue: "- Using %s v%s from the shared cache directory",
JSONValue: "- Using %s v%s from the shared cache directory",
},
"installing_provider_message": {
HumanValue: "- Installing %s v%s...",
JSONValue: "- Installing %s v%s...",
},
"key_id": {
HumanValue: ", key ID [reset][bold]%s[reset]",
JSONValue: ", key ID %s",
},
"installed_provider_version_info": {
HumanValue: "- Installed %s v%s (%s%s)",
JSONValue: "- Installed %s v%s (%s%s)",
},
"partner_and_community_providers_message": {
HumanValue: partnerAndCommunityProvidersInfo,
JSONValue: partnerAndCommunityProvidersInfo,
},
"init_config_error": {
HumanValue: errInitConfigError,
JSONValue: errInitConfigErrorJSON,
},
"empty_message": {
HumanValue: "",
JSONValue: "",
},
}
const (
CopyingConfigurationMessage string = "copying_configuration_message"
EmptyMessage string = "empty_message"
OutputInitEmptyMessage string = "output_init_empty_message"
OutputInitSuccessMessage string = "output_init_success_message"
OutputInitSuccessCloudMessage string = "output_init_success_cloud_message"
OutputInitSuccessCLIMessage string = "output_init_success_cli_message"
OutputInitSuccessCLICloudMessage string = "output_init_success_cli_cloud_message"
UpgradingModulesMessage string = "upgrading_modules_message"
InitializingTerraformCloudMessage string = "initializing_terraform_cloud_message"
InitializingModulesMessage string = "initializing_modules_message"
InitializingBackendMessage string = "initializing_backend_message"
InitializingProviderPluginMessage string = "initializing_provider_plugin_message"
LockInfo string = "lock_info"
DependenciesLockChangesInfo string = "dependencies_lock_changes_info"
ProviderAlreadyInstalledMessage string = "provider_already_installed_message"
BuiltInProviderAvailableMessage string = "built_in_provider_available_message"
ReusingPreviousVersionInfo string = "reusing_previous_version_info"
FindingMatchingVersionMessage string = "finding_matching_version_message"
FindingLatestVersionMessage string = "finding_latest_version_message"
UsingProviderFromCacheDirInfo string = "using_provider_from_cache_dir_info"
InstallingProviderMessage string = "installing_provider_message"
KeyID string = "key_id"
InstalledProviderVersionInfo string = "installed_provider_version_info"
PartnerAndCommunityProvidersMessage string = "partner_and_community_providers_message"
InitConfigError string = "init_config_error"
)
const outputInitEmpty = `
[reset][bold]Terraform initialized in an empty directory![reset]
The directory has no Terraform configuration files. You may begin working
with Terraform immediately by creating Terraform configuration files.
`
const outputInitEmptyJSON = `
Terraform initialized in an empty directory!
The directory has no Terraform configuration files. You may begin working
with Terraform immediately by creating Terraform configuration files.
`
const outputInitSuccess = `
[reset][bold][green]Terraform has been successfully initialized![reset][green]
`
const outputInitSuccessJSON = `
Terraform has been successfully initialized!
`
const outputInitSuccessCloud = `
[reset][bold][green]Terraform Cloud has been successfully initialized![reset][green]
`
const outputInitSuccessCloudJSON = `
Terraform Cloud has been successfully initialized!
`
const outputInitSuccessCLI = `[reset][green]
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
`
const outputInitSuccessCLI_JSON = `
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
`
const outputInitSuccessCLICloud = `[reset][green]
You may now begin working with Terraform Cloud. Try running "terraform plan" to
see any changes that are required for your infrastructure.
If you ever set or change modules or Terraform Settings, run "terraform init"
again to reinitialize your working directory.
`
const outputInitSuccessCLICloudJSON = `
You may now begin working with Terraform Cloud. Try running "terraform plan" to
see any changes that are required for your infrastructure.
If you ever set or change modules or Terraform Settings, run "terraform init"
again to reinitialize your working directory.
`
// providerProtocolTooOld is a message sent to the CLI UI if the provider's
// supported protocol versions are too old for the user's version of terraform,
// but a newer version of the provider is compatible.
const providerProtocolTooOld = `Provider %q v%s is not compatible with Terraform %s.
Provider version %s is the latest compatible version. Select it with the following version constraint:
version = %q
Terraform checked all of the plugin versions matching the given constraint:
%s
Consult the documentation for this provider for more information on compatibility between provider and Terraform versions.
`
// providerProtocolTooNew is a message sent to the CLI UI if the provider's
// supported protocol versions are too new for the user's version of terraform,
// and the user could either upgrade terraform or choose an older version of the
// provider.
const providerProtocolTooNew = `Provider %q v%s is not compatible with Terraform %s.
You need to downgrade to v%s or earlier. Select it with the following constraint:
version = %q
Terraform checked all of the plugin versions matching the given constraint:
%s
Consult the documentation for this provider for more information on compatibility between provider and Terraform versions.
Alternatively, upgrade to the latest version of Terraform for compatibility with newer provider releases.
`
// incompleteLockFileInformationHeader is the summary displayed to users when
// the lock file has only recorded local hashes.
const incompleteLockFileInformationHeader = `Incomplete lock file information for providers`
// incompleteLockFileInformationBody is the body of text displayed to users when
// the lock file has only recorded local hashes.
const incompleteLockFileInformationBody = `Due to your customized provider installation methods, Terraform was forced to calculate lock file checksums locally for the following providers:
- %s
The current .terraform.lock.hcl file only includes checksums for %s, so Terraform running on another platform will fail to install these providers.
To calculate additional checksums for another platform, run:
terraform providers lock -platform=linux_amd64
(where linux_amd64 is the platform to generate)`
const previousLockInfoHuman = `
Terraform has created a lock file [bold].terraform.lock.hcl[reset] to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.`
const previousLockInfoJSON = `
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.`
const dependenciesLockChangesInfo = `
Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.`
const partnerAndCommunityProvidersInfo = "\nPartner and community providers are signed by their developers.\n" +
"If you'd like to know more about provider signing, you can read about it here:\n" +
"https://www.terraform.io/docs/cli/plugins/signing.html"
const errInitConfigError = `
[reset]Terraform encountered problems during initialisation, including problems
with the configuration, described below.
The Terraform configuration must be valid before initialization so that
Terraform can determine which modules and providers need to be installed.
`
const errInitConfigErrorJSON = `
Terraform encountered problems during initialisation, including problems
with the configuration, described below.
The Terraform configuration must be valid before initialization so that
Terraform can determine which modules and providers need to be installed.
`

Loading…
Cancel
Save