Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

terraform init -json #34886

Merged
merged 13 commits into from
Apr 17, 2024
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ENHANCEMENTS:

If an entered line contains opening paretheses/etc that are not closed, Terraform will await another line of input to complete the expression. This initial implementation is primarily intended to support pasting in multi-line expressions from elsewhere, rather than for manual multi-line editing, so the interactive editing support is currently limited.
* `cli`: Updates the Terraform CLI output to show logical separation between OPA and Sentinel policy evaluations
* `terraform init` now accepts a `-json` option. If specified, enables the machine readable JSON output. ([#34886](https://github.com/hashicorp/terraform/pull/34886))

BUG FIXES:

Expand All @@ -19,7 +20,7 @@ Experiments are only enabled in alpha releases of Terraform CLI. The following f
* `variable_validation_crossref`: This [language experiment](https://developer.hashicorp.com/terraform/language/settings#experimental-language-features) allows `validation` blocks inside input variable declarations to refer to other objects inside the module where the variable is declared, including to the values of other input variables in the same module.
* `terraform test` accepts a new option `-junit-xml=FILENAME`. If specified, and if the test configuration is valid enough to begin executing, then Terraform writes a JUnit XML test result report to the given filename, describing similar information as included in the normal test output. ([#34291](https://github.com/hashicorp/terraform/issues/34291))
* The new command `terraform rpcapi` exposes some Terraform Core functionality through an RPC interface compatible with [`go-plugin`](https://github.com/hashicorp/go-plugin). The exact RPC API exposed here is currently subject to change at any time, because it's here primarily as a vehicle to support the [Terraform Stacks](https://www.hashicorp.com/blog/terraform-stacks-explained) private preview and so will be broken if necessary to respond to feedback from private preview participants, or possibly for other reasons. Do not use this mechanism yet outside of Terraform Stacks private preview.
* The experimental "deferred actions" feature, enabled by passing the `-allow-deferral` option to `terraform plan`, permits `count` and `for_each` arguments in `module`, `resource`, and `data` blocks to have unknown values and allows providers to react more flexibly to unknown values. This experiment is under active development, and so it's not yet useful to participate in this experiment.
* The experimental "deferred actions" feature, enabled by passing the `-allow-deferral` option to `terraform plan`, permits `count` and `for_each` arguments in `module`, `resource`, and `data` blocks to have unknown values and allows providers to react more flexibly to unknown values. This experiment is under active development, and so it's not yet useful to participate in this experiment.

## Previous Releases

Expand Down
4 changes: 2 additions & 2 deletions internal/command/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,12 @@ func (c *ApplyCommand) GatherVariables(opReq *backendrun.Operation, args *argume
// package directly, removing this shim layer.

varArgs := args.All()
items := make([]rawFlag, len(varArgs))
items := make([]arguments.FlagNameValue, len(varArgs))
for i := range varArgs {
items[i].Name = varArgs[i].Name
items[i].Value = varArgs[i].Value
}
c.Meta.variableArgs = rawFlags{items: &items}
c.Meta.variableArgs = arguments.FlagNameValueSlice{Items: &items}
opReq.Variables, diags = c.collectVariableValues()

return diags
Expand Down
12 changes: 6 additions & 6 deletions internal/command/arguments/extended.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,13 @@ func (o *Operation) Parse() tfdiags.Diagnostics {
}

// Vars describes arguments which specify non-default variable values. This
// interfce is unfortunately obscure, because the order of the CLI arguments
// interface is unfortunately obscure, because the order of the CLI arguments
// determines the final value of the gathered variables. In future it might be
// desirable for the arguments package to handle the gathering of variables
// directly, returning a map of variable values.
type Vars struct {
vars *flagNameValueSlice
varFiles *flagNameValueSlice
vars *FlagNameValueSlice
varFiles *FlagNameValueSlice
}

func (v *Vars) All() []FlagNameValue {
Expand Down Expand Up @@ -239,14 +239,14 @@ func extendedFlagSet(name string, state *State, operation *Operation, vars *Vars
f.BoolVar(&operation.Refresh, "refresh", true, "refresh")
f.BoolVar(&operation.destroyRaw, "destroy", false, "destroy")
f.BoolVar(&operation.refreshOnlyRaw, "refresh-only", false, "refresh-only")
f.Var((*flagStringSlice)(&operation.targetsRaw), "target", "target")
f.Var((*flagStringSlice)(&operation.forceReplaceRaw), "replace", "replace")
f.Var((*FlagStringSlice)(&operation.targetsRaw), "target", "target")
f.Var((*FlagStringSlice)(&operation.forceReplaceRaw), "replace", "replace")
}

// Gather all -var and -var-file arguments into one heterogenous structure
// to preserve the overall order.
if vars != nil {
varsFlags := newFlagNameValueSlice("-var")
varsFlags := NewFlagNameValueSlice("-var")
varFilesFlags := varsFlags.Alias("-var-file")
vars.vars = &varsFlags
vars.varFiles = &varFilesFlags
Expand Down
60 changes: 28 additions & 32 deletions internal/command/arguments/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,72 +8,68 @@ import (
"fmt"
)

// flagStringSlice is a flag.Value implementation which allows collecting
// FlagStringSlice is a flag.Value implementation which allows collecting
// multiple instances of a single flag into a slice. This is used for flags
// such as -target=aws_instance.foo and -var x=y.
type flagStringSlice []string
type FlagStringSlice []string

var _ flag.Value = (*flagStringSlice)(nil)
var _ flag.Value = (*FlagStringSlice)(nil)

func (v *flagStringSlice) String() string {
func (v *FlagStringSlice) String() string {
return ""
}
func (v *flagStringSlice) Set(raw string) error {
func (v *FlagStringSlice) Set(raw string) error {
*v = append(*v, raw)

return nil
}

// flagNameValueSlice is a flag.Value implementation that appends raw flag
// FlagNameValueSlice is a flag.Value implementation that appends raw flag
// names and values to a slice. This is used to collect a sequence of flags
// with possibly different names, preserving the overall order.
//
// FIXME: this is a copy of rawFlags from command/meta_config.go, with the
// eventual aim of replacing it altogether by gathering variables in the
// arguments package.
type flagNameValueSlice struct {
flagName string
items *[]FlagNameValue
type FlagNameValueSlice struct {
FlagName string
Items *[]FlagNameValue
}

var _ flag.Value = flagNameValueSlice{}
var _ flag.Value = FlagNameValueSlice{}

func newFlagNameValueSlice(flagName string) flagNameValueSlice {
func NewFlagNameValueSlice(flagName string) FlagNameValueSlice {
var items []FlagNameValue
return flagNameValueSlice{
flagName: flagName,
items: &items,
return FlagNameValueSlice{
FlagName: flagName,
Items: &items,
}
}

func (f flagNameValueSlice) Empty() bool {
if f.items == nil {
func (f FlagNameValueSlice) Empty() bool {
if f.Items == nil {
return true
}
return len(*f.items) == 0
return len(*f.Items) == 0
}

func (f flagNameValueSlice) AllItems() []FlagNameValue {
if f.items == nil {
func (f FlagNameValueSlice) AllItems() []FlagNameValue {
if f.Items == nil {
return nil
}
return *f.items
return *f.Items
}

func (f flagNameValueSlice) Alias(flagName string) flagNameValueSlice {
return flagNameValueSlice{
flagName: flagName,
items: f.items,
func (f FlagNameValueSlice) Alias(flagName string) FlagNameValueSlice {
return FlagNameValueSlice{
FlagName: flagName,
Items: f.Items,
}
}

func (f flagNameValueSlice) String() string {
func (f FlagNameValueSlice) String() string {
return ""
}

func (f flagNameValueSlice) Set(str string) error {
*f.items = append(*f.items, FlagNameValue{
Name: f.flagName,
func (f FlagNameValueSlice) Set(str string) error {
*f.Items = append(*f.Items, FlagNameValue{
Name: f.FlagName,
Value: str,
})
return nil
Expand Down
159 changes: 159 additions & 0 deletions internal/command/arguments/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package arguments

import (
"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

BackendConfig FlagNameValueSlice

Vars *Vars

// InputEnabled is used to disable interactive input for unspecified
// variable and backend config values. Default is true.
InputEnabled bool

TargetFlags []string

CompactWarnings bool

PluginPath FlagStringSlice

Args []string
}

// 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) (*Init, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
init := &Init{
Vars: &Vars{},
}
init.BackendConfig = NewFlagNameValueSlice("-backend-config")

cmdFlags := extendedFlagSet("init", nil, nil, init.Vars)

cmdFlags.Var((*FlagStringSlice)(&init.TargetFlags), "target", "resource to target")
cmdFlags.BoolVar(&init.InputEnabled, "input", true, "input")
cmdFlags.BoolVar(&init.CompactWarnings, "compact-warnings", false, "use compact warnings")
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")
cmdFlags.Var(&init.BackendConfig, "backend-config", "")
cmdFlags.Var(&init.PluginPath, "plugin-dir", "plugin directory")

if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}

if init.MigrateState && init.Json {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"The -migrate-state and -json options are mutually-exclusive",
"Terraform cannot ask for interactive approval when -json is set. To use the -migrate-state option, disable the -json option.",
))
}

if init.MigrateState && init.Reconfigure {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid init options",
"The -migrate-state and -reconfigure options are mutually-exclusive.",
))
}

init.Args = cmdFlags.Args()

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
}
Loading
Loading