Merge pull request #38186 from hashicorp/modernize-commands-to-use-arguments

Refactoring: Modernize various commands to use arguments
This commit is contained in:
Daniel Banck 2026-02-17 17:19:15 +01:00 committed by GitHub
commit cbc30de3ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 2958 additions and 327 deletions

View file

@ -0,0 +1,108 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"os"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// getwd is a package-level variable that defaults to os.Getwd.
// It can be overridden in tests to provide a mock implementation.
var getwd = os.Getwd
// Import represents the command-line arguments for the import command.
type Import struct {
// State, Vars are the common extended flags
State *State
Vars *Vars
// ConfigPath is the path to a directory of Terraform configuration files
// to use to configure the provider. An empty string means the caller
// should use the current working directory.
ConfigPath string
// Parallelism is the limit Terraform places on total parallel operations
// as it walks the dependency graph.
Parallelism int
// IgnoreRemoteVersion controls whether to suppress the error when the
// configured Terraform version on the remote workspace does not match the
// local Terraform version.
IgnoreRemoteVersion bool
// InputEnabled is used to disable interactive input for unspecified
// variable and backend config values. Default is true.
InputEnabled bool
// CompactWarnings enables compact warning output.
CompactWarnings bool
// TargetFlags are the raw -target flag values.
TargetFlags []string
// Addr is the resource address to import into.
Addr string
// ID is the provider-specific ID of the resource to import.
ID string
}
// ParseImport processes CLI arguments, returning an Import value and errors.
// If errors are encountered, an Import value is still returned representing
// the best effort interpretation of the arguments.
func ParseImport(args []string) (*Import, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
imp := &Import{
State: &State{
Lock: true,
},
Vars: &Vars{},
}
// Get the pwd since its our default -config flag value
pwd, err := getwd()
if err != nil {
return nil, diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Error getting pwd",
err.Error(),
))
}
cmdFlags := extendedFlagSet("import", imp.State, nil, imp.Vars)
cmdFlags.BoolVar(&imp.IgnoreRemoteVersion, "ignore-remote-version", false, "ignore-remote-version")
cmdFlags.IntVar(&imp.Parallelism, "parallelism", DefaultParallelism, "parallelism")
cmdFlags.StringVar(&imp.ConfigPath, "config", pwd, "config")
cmdFlags.BoolVar(&imp.InputEnabled, "input", true, "input")
cmdFlags.BoolVar(&imp.CompactWarnings, "compact-warnings", false, "compact-warnings")
cmdFlags.Var((*FlagStringSlice)(&imp.TargetFlags), "target", "target")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) != 2 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Wrong number of arguments",
"The import command expects two arguments: ADDR and ID.",
))
}
if len(args) > 0 {
imp.Addr = args[0]
}
if len(args) > 1 {
imp.ID = args[1]
}
return imp, diags
}

View file

@ -0,0 +1,271 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func init() {
// Mock getwd for tests to return empty string
getwd = func() (string, error) {
return "", nil
}
}
func TestParseImport_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *Import
}{
"defaults": {
[]string{"test_instance.foo", "bar"},
&Import{
State: &State{Lock: true},
Vars: &Vars{},
ConfigPath: "",
Parallelism: DefaultParallelism,
InputEnabled: true,
Addr: "test_instance.foo",
ID: "bar",
},
},
"state flag": {
[]string{"-state", "mystate.tfstate", "test_instance.foo", "bar"},
&Import{
State: &State{Lock: true, StatePath: "mystate.tfstate"},
Vars: &Vars{},
Parallelism: DefaultParallelism,
InputEnabled: true,
Addr: "test_instance.foo",
ID: "bar",
},
},
"state-out and backup flags": {
[]string{"-state-out", "out.tfstate", "-backup", "backup.tfstate", "test_instance.foo", "bar"},
&Import{
State: &State{Lock: true, StateOutPath: "out.tfstate", BackupPath: "backup.tfstate"},
Vars: &Vars{},
Parallelism: DefaultParallelism,
InputEnabled: true,
Addr: "test_instance.foo",
ID: "bar",
},
},
"lock disabled": {
[]string{"-lock=false", "test_instance.foo", "bar"},
&Import{
State: &State{Lock: false},
Vars: &Vars{},
Parallelism: DefaultParallelism,
InputEnabled: true,
Addr: "test_instance.foo",
ID: "bar",
},
},
"config path": {
[]string{"-config=/tmp/config", "test_instance.foo", "bar"},
&Import{
State: &State{Lock: true},
Vars: &Vars{},
ConfigPath: "/tmp/config",
Parallelism: DefaultParallelism,
InputEnabled: true,
Addr: "test_instance.foo",
ID: "bar",
},
},
"parallelism": {
[]string{"-parallelism=5", "test_instance.foo", "bar"},
&Import{
State: &State{Lock: true},
Vars: &Vars{},
Parallelism: 5,
InputEnabled: true,
Addr: "test_instance.foo",
ID: "bar",
},
},
"ignore remote version": {
[]string{"-ignore-remote-version", "test_instance.foo", "bar"},
&Import{
State: &State{Lock: true},
Vars: &Vars{},
Parallelism: DefaultParallelism,
IgnoreRemoteVersion: true,
InputEnabled: true,
Addr: "test_instance.foo",
ID: "bar",
},
},
"input disabled": {
[]string{"-input=false", "test_instance.foo", "bar"},
&Import{
State: &State{Lock: true},
Vars: &Vars{},
Parallelism: DefaultParallelism,
InputEnabled: false,
Addr: "test_instance.foo",
ID: "bar",
},
},
}
cmpOpts := cmpopts.IgnoreUnexported(Vars{}, State{})
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseImport(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if diff := cmp.Diff(tc.want, got, cmpOpts); diff != "" {
t.Errorf("unexpected result\n%s", diff)
}
})
}
}
func TestParseImport_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *Import
wantDiags tfdiags.Diagnostics
}{
"unknown flag": {
[]string{"-boop", "test_instance.foo", "bar"},
&Import{
State: &State{Lock: true},
Vars: &Vars{},
Parallelism: DefaultParallelism,
InputEnabled: true,
Addr: "test_instance.foo",
ID: "bar",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
},
},
"missing all arguments": {
nil,
&Import{
State: &State{Lock: true},
Vars: &Vars{},
Parallelism: DefaultParallelism,
InputEnabled: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Wrong number of arguments",
"The import command expects two arguments: ADDR and ID.",
),
},
},
"only one argument": {
[]string{"test_instance.foo"},
&Import{
State: &State{Lock: true},
Vars: &Vars{},
Parallelism: DefaultParallelism,
InputEnabled: true,
Addr: "test_instance.foo",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Wrong number of arguments",
"The import command expects two arguments: ADDR and ID.",
),
},
},
"too many arguments": {
[]string{"test_instance.foo", "bar", "baz"},
&Import{
State: &State{Lock: true},
Vars: &Vars{},
Parallelism: DefaultParallelism,
InputEnabled: true,
Addr: "test_instance.foo",
ID: "bar",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Wrong number of arguments",
"The import command expects two arguments: ADDR and ID.",
),
},
},
}
cmpOpts := cmpopts.IgnoreUnexported(Vars{}, State{})
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseImport(tc.args)
if diff := cmp.Diff(tc.want, got, cmpOpts); diff != "" {
t.Errorf("unexpected result\n%s", diff)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}
func TestParseImport_vars(t *testing.T) {
testCases := map[string]struct {
args []string
want []FlagNameValue
}{
"no var flags by default": {
args: []string{"test_instance.foo", "bar"},
want: nil,
},
"one var": {
args: []string{"-var", "foo=bar", "test_instance.foo", "bar"},
want: []FlagNameValue{
{Name: "-var", Value: "foo=bar"},
},
},
"one var-file": {
args: []string{"-var-file", "cool.tfvars", "test_instance.foo", "bar"},
want: []FlagNameValue{
{Name: "-var-file", Value: "cool.tfvars"},
},
},
"ordering preserved": {
args: []string{
"-var", "foo=bar",
"-var-file", "cool.tfvars",
"-var", "boop=beep",
"test_instance.foo", "bar",
},
want: []FlagNameValue{
{Name: "-var", Value: "foo=bar"},
{Name: "-var-file", Value: "cool.tfvars"},
{Name: "-var", Value: "boop=beep"},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseImport(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if vars := got.Vars.All(); !cmp.Equal(vars, tc.want) {
t.Fatalf("unexpected result\n%s", cmp.Diff(vars, tc.want))
}
})
}
}

View file

@ -0,0 +1,42 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import "github.com/hashicorp/terraform/internal/tfdiags"
// Providers represents the command-line arguments for the providers command.
type Providers struct {
// TestsDirectory is the directory containing Terraform test files.
TestsDirectory string
}
// ParseProviders processes CLI arguments, returning a Providers value and
// errors. If errors are encountered, a Providers value is still returned
// representing the best effort interpretation of the arguments.
func ParseProviders(args []string) (*Providers, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
providers := &Providers{}
cmdFlags := defaultFlagSet("providers")
cmdFlags.StringVar(&providers.TestsDirectory, "test-directory", "tests", "test-directory")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) > 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Too many command line arguments",
"Did you mean to use -chdir?",
))
}
return providers, diags
}

View file

@ -0,0 +1,52 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import "github.com/hashicorp/terraform/internal/tfdiags"
// ProvidersLock represents the command-line arguments for the providers lock
// command.
type ProvidersLock struct {
Platforms FlagStringSlice
FSMirrorDir string
NetMirrorURL string
TestsDirectory string
EnablePluginCache bool
Providers []string
}
// ParseProvidersLock processes CLI arguments, returning a ProvidersLock value
// and errors. If errors are encountered, a ProvidersLock value is still
// returned representing the best effort interpretation of the arguments.
func ParseProvidersLock(args []string) (*ProvidersLock, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
providersLock := &ProvidersLock{}
cmdFlags := defaultFlagSet("providers lock")
cmdFlags.Var(&providersLock.Platforms, "platform", "target platform")
cmdFlags.StringVar(&providersLock.FSMirrorDir, "fs-mirror", "", "filesystem mirror directory")
cmdFlags.StringVar(&providersLock.NetMirrorURL, "net-mirror", "", "network mirror base URL")
cmdFlags.StringVar(&providersLock.TestsDirectory, "test-directory", "tests", "test-directory")
cmdFlags.BoolVar(&providersLock.EnablePluginCache, "enable-plugin-cache", false, "")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
if providersLock.FSMirrorDir != "" && providersLock.NetMirrorURL != "" {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid installation method options",
"The -fs-mirror and -net-mirror command line options are mutually-exclusive.",
))
}
providersLock.Providers = cmdFlags.Args()
return providersLock, diags
}

View file

@ -0,0 +1,124 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseProvidersLock_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *ProvidersLock
}{
"defaults": {
nil,
&ProvidersLock{
TestsDirectory: "tests",
},
},
"all options": {
[]string{
"-platform=linux_amd64",
"-platform=darwin_arm64",
"-fs-mirror=mirror",
"-test-directory=integration-tests",
"-enable-plugin-cache",
"hashicorp/test",
},
&ProvidersLock{
Platforms: FlagStringSlice{"linux_amd64", "darwin_arm64"},
FSMirrorDir: "mirror",
TestsDirectory: "integration-tests",
EnablePluginCache: true,
Providers: []string{"hashicorp/test"},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseProvidersLock(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("unexpected result\n%s", diff)
}
})
}
}
func TestParseProvidersLock_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *ProvidersLock
wantDiags tfdiags.Diagnostics
}{
"mirror collision": {
[]string{
"-fs-mirror=foo",
"-net-mirror=https://example.com",
},
&ProvidersLock{
FSMirrorDir: "foo",
NetMirrorURL: "https://example.com",
TestsDirectory: "tests",
Providers: []string{},
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Invalid installation method options",
"The -fs-mirror and -net-mirror command line options are mutually-exclusive.",
),
},
},
"unknown flag": {
[]string{"-wat"},
&ProvidersLock{
TestsDirectory: "tests",
Providers: []string{},
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -wat",
),
},
},
"unknown flag and mirror collision": {
[]string{
"-wat",
"-fs-mirror=foo",
"-net-mirror=https://example.com",
},
&ProvidersLock{
TestsDirectory: "tests",
Providers: []string{"-fs-mirror=foo", "-net-mirror=https://example.com"},
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -wat",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseProvidersLock(tc.args)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("unexpected result\n%s", diff)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,48 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import "github.com/hashicorp/terraform/internal/tfdiags"
// ProvidersMirror represents the command-line arguments for the providers
// mirror command.
type ProvidersMirror struct {
Platforms FlagStringSlice
LockFile bool
OutputDir string
}
// ParseProvidersMirror processes CLI arguments, returning a ProvidersMirror
// value and errors. If errors are encountered, a ProvidersMirror value is
// still returned representing the best effort interpretation of the arguments.
func ParseProvidersMirror(args []string) (*ProvidersMirror, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
providersMirror := &ProvidersMirror{}
cmdFlags := defaultFlagSet("providers mirror")
cmdFlags.Var(&providersMirror.Platforms, "platform", "target platform")
cmdFlags.BoolVar(&providersMirror.LockFile, "lock-file", true, "use lock file")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) != 1 {
return providersMirror, diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No output directory specified",
"The providers mirror command requires an output directory as a command-line argument.",
))
}
providersMirror.OutputDir = args[0]
return providersMirror, diags
}

View file

@ -0,0 +1,113 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseProvidersMirror_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *ProvidersMirror
}{
"defaults": {
[]string{"./mirror"},
&ProvidersMirror{
LockFile: true,
OutputDir: "./mirror",
},
},
"all options": {
[]string{
"-platform=linux_amd64",
"-platform=darwin_arm64",
"-lock-file=false",
"./mirror",
},
&ProvidersMirror{
Platforms: FlagStringSlice{"linux_amd64", "darwin_arm64"},
OutputDir: "./mirror",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseProvidersMirror(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("unexpected result\n%s", diff)
}
})
}
}
func TestParseProvidersMirror_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *ProvidersMirror
wantDiags tfdiags.Diagnostics
}{
"missing output directory": {
nil,
&ProvidersMirror{
LockFile: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"No output directory specified",
"The providers mirror command requires an output directory as a command-line argument.",
),
},
},
"too many arguments": {
[]string{"./mirror", "./extra"},
&ProvidersMirror{
LockFile: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"No output directory specified",
"The providers mirror command requires an output directory as a command-line argument.",
),
},
},
"unknown flag and missing output directory": {
[]string{"-wat"},
&ProvidersMirror{
LockFile: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -wat",
),
tfdiags.Sourceless(
tfdiags.Error,
"No output directory specified",
"The providers mirror command requires an output directory as a command-line argument.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseProvidersMirror(tc.args)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("unexpected result\n%s", diff)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,50 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import "github.com/hashicorp/terraform/internal/tfdiags"
// ProvidersSchema represents the command-line arguments for the providers
// schema command.
type ProvidersSchema struct {
JSON bool
}
// ParseProvidersSchema processes CLI arguments, returning a ProvidersSchema
// value and errors. If errors are encountered, a ProvidersSchema value is
// still returned representing the best effort interpretation of the arguments.
func ParseProvidersSchema(args []string) (*ProvidersSchema, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
providersSchema := &ProvidersSchema{}
cmdFlags := defaultFlagSet("providers schema")
cmdFlags.BoolVar(&providersSchema.JSON, "json", false, "produce JSON output")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) > 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Too many command line arguments",
"Expected no positional arguments.",
))
}
if !providersSchema.JSON {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"The -json flag is required",
"The `terraform providers schema` command requires the `-json` flag.",
))
}
return providersSchema, diags
}

View file

@ -0,0 +1,96 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseProvidersSchema_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *ProvidersSchema
}{
"json": {
[]string{"-json"},
&ProvidersSchema{
JSON: true,
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseProvidersSchema(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("unexpected result\n%s", diff)
}
})
}
}
func TestParseProvidersSchema_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *ProvidersSchema
wantDiags tfdiags.Diagnostics
}{
"missing json": {
nil,
&ProvidersSchema{},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"The -json flag is required",
"The `terraform providers schema` command requires the `-json` flag.",
),
},
},
"too many positional arguments": {
[]string{"-json", "extra"},
&ProvidersSchema{
JSON: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Too many command line arguments",
"Expected no positional arguments.",
),
},
},
"unknown flag and missing json": {
[]string{"-wat"},
&ProvidersSchema{},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -wat",
),
tfdiags.Sourceless(
tfdiags.Error,
"The -json flag is required",
"The `terraform providers schema` command requires the `-json` flag.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseProvidersSchema(tc.args)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("unexpected result\n%s", diff)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,88 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseProviders_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *Providers
}{
"defaults": {
nil,
&Providers{
TestsDirectory: "tests",
},
},
"test directory": {
[]string{"-test-directory=integration-tests"},
&Providers{
TestsDirectory: "integration-tests",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseProviders(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("unexpected result\n%s", diff)
}
})
}
}
func TestParseProviders_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *Providers
wantDiags tfdiags.Diagnostics
}{
"unknown flag": {
[]string{"-wat"},
&Providers{
TestsDirectory: "tests",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -wat",
),
},
},
"too many positional arguments": {
[]string{"foo"},
&Providers{
TestsDirectory: "tests",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Too many command line arguments",
"Did you mean to use -chdir?",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseProviders(tc.args)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("unexpected result\n%s", diff)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,49 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StateList represents the command-line arguments for the state list command.
type StateList struct {
// StatePath is an optional path to a state file, overriding the default.
StatePath string
// ID filters the results to include only instances whose resource types
// have an attribute named "id" whose value equals this string.
ID string
// Addrs are optional resource or module addresses used to filter the
// listed instances.
Addrs []string
}
// ParseStateList processes CLI arguments, returning a StateList value and
// diagnostics. If errors are encountered, a StateList value is still returned
// representing the best effort interpretation of the arguments.
func ParseStateList(args []string) (*StateList, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
list := &StateList{}
var statePath, id string
cmdFlags := defaultFlagSet("state list")
cmdFlags.StringVar(&statePath, "state", "", "path")
cmdFlags.StringVar(&id, "id", "", "Restrict output to paths with a resource having the specified ID.")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
list.StatePath = statePath
list.ID = id
list.Addrs = cmdFlags.Args()
return list, diags
}

View file

@ -0,0 +1,104 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStateList_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateList
}{
"defaults": {
nil,
&StateList{},
},
"state path": {
[]string{"-state=foobar.tfstate"},
&StateList{
StatePath: "foobar.tfstate",
},
},
"id filter": {
[]string{"-id=bar"},
&StateList{
ID: "bar",
},
},
"with addresses": {
[]string{"module.example", "aws_instance.foo"},
&StateList{
Addrs: []string{"module.example", "aws_instance.foo"},
},
},
"all options": {
[]string{"-state=foobar.tfstate", "-id=bar", "module.example"},
&StateList{
StatePath: "foobar.tfstate",
ID: "bar",
Addrs: []string{"module.example"},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStateList(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if got.StatePath != tc.want.StatePath {
t.Fatalf("unexpected StatePath\n got: %q\nwant: %q", got.StatePath, tc.want.StatePath)
}
if got.ID != tc.want.ID {
t.Fatalf("unexpected ID\n got: %q\nwant: %q", got.ID, tc.want.ID)
}
if len(got.Addrs) != len(tc.want.Addrs) {
t.Fatalf("unexpected Addrs length\n got: %d\nwant: %d", len(got.Addrs), len(tc.want.Addrs))
}
for i := range got.Addrs {
if got.Addrs[i] != tc.want.Addrs[i] {
t.Fatalf("unexpected Addrs[%d]\n got: %q\nwant: %q", i, got.Addrs[i], tc.want.Addrs[i])
}
}
})
}
}
func TestParseStateList_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateList
wantDiags tfdiags.Diagnostics
}{
"unknown flag": {
[]string{"-boop"},
&StateList{},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStateList(tc.args)
if got.StatePath != tc.want.StatePath {
t.Fatalf("unexpected StatePath\n got: %q\nwant: %q", got.StatePath, tc.want.StatePath)
}
if got.ID != tc.want.ID {
t.Fatalf("unexpected ID\n got: %q\nwant: %q", got.ID, tc.want.ID)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,91 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StateMv represents the command-line arguments for the state mv command.
type StateMv struct {
// DryRun, if true, prints out what would be moved without actually
// moving anything.
DryRun bool
// BackupPath is the path where Terraform should write the backup state.
BackupPath string
// BackupOutPath is the path where Terraform should write the backup of
// the destination state.
BackupOutPath string
// StateLock, if true, requests that the backend lock the state for this
// operation.
StateLock bool
// StateLockTimeout is the duration to retry a state lock.
StateLockTimeout time.Duration
// StatePath is an optional path to a local state file.
StatePath string
// StateOutPath is an optional path to write the destination state.
StateOutPath string
// IgnoreRemoteVersion, if true, continues even if remote and local
// Terraform versions are incompatible.
IgnoreRemoteVersion bool
// SourceAddr is the source resource address.
SourceAddr string
// DestAddr is the destination resource address.
DestAddr string
}
// ParseStateMv processes CLI arguments, returning a StateMv value and
// diagnostics. If errors are encountered, a StateMv value is still returned
// representing the best effort interpretation of the arguments.
func ParseStateMv(args []string) (*StateMv, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
mv := &StateMv{}
cmdFlags := defaultFlagSet("state mv")
cmdFlags.BoolVar(&mv.DryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&mv.BackupPath, "backup", "-", "backup")
cmdFlags.StringVar(&mv.BackupOutPath, "backup-out", "-", "backup")
cmdFlags.BoolVar(&mv.StateLock, "lock", true, "lock states")
cmdFlags.DurationVar(&mv.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&mv.StatePath, "state", "", "path")
cmdFlags.StringVar(&mv.StateOutPath, "state-out", "", "path")
cmdFlags.BoolVar(&mv.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) != 2 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the source and destination addresses.",
))
}
if len(args) > 0 {
mv.SourceAddr = args[0]
}
if len(args) > 1 {
mv.DestAddr = args[1]
}
return mv, diags
}

View file

@ -0,0 +1,165 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStateMv_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateMv
}{
"addresses only": {
[]string{"test_instance.foo", "test_instance.bar"},
&StateMv{
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
SourceAddr: "test_instance.foo",
DestAddr: "test_instance.bar",
},
},
"dry run": {
[]string{"-dry-run", "test_instance.foo", "test_instance.bar"},
&StateMv{
DryRun: true,
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
SourceAddr: "test_instance.foo",
DestAddr: "test_instance.bar",
},
},
"all options": {
[]string{
"-dry-run",
"-backup=backup.tfstate",
"-backup-out=backup-out.tfstate",
"-lock=false",
"-lock-timeout=5s",
"-state=state.tfstate",
"-state-out=state-out.tfstate",
"-ignore-remote-version",
"test_instance.foo",
"test_instance.bar",
},
&StateMv{
DryRun: true,
BackupPath: "backup.tfstate",
BackupOutPath: "backup-out.tfstate",
StateLock: false,
StateLockTimeout: 5 * time.Second,
StatePath: "state.tfstate",
StateOutPath: "state-out.tfstate",
IgnoreRemoteVersion: true,
SourceAddr: "test_instance.foo",
DestAddr: "test_instance.bar",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStateMv(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseStateMv_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateMv
wantDiags tfdiags.Diagnostics
}{
"no arguments": {
nil,
&StateMv{
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the source and destination addresses.",
),
},
},
"one argument": {
[]string{"test_instance.foo"},
&StateMv{
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
SourceAddr: "test_instance.foo",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the source and destination addresses.",
),
},
},
"too many arguments": {
[]string{"a", "b", "c"},
&StateMv{
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
SourceAddr: "a",
DestAddr: "b",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the source and destination addresses.",
),
},
},
"unknown flag": {
[]string{"-boop"},
&StateMv{
BackupPath: "-",
BackupOutPath: "-",
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the source and destination addresses.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStateMv(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,32 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StatePull represents the command-line arguments for the state pull command.
type StatePull struct {
}
// ParseStatePull processes CLI arguments, returning a StatePull value and
// diagnostics. If errors are encountered, a StatePull value is still returned
// representing the best effort interpretation of the arguments.
func ParseStatePull(args []string) (*StatePull, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
pull := &StatePull{}
cmdFlags := defaultFlagSet("state pull")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
return pull, diags
}

View file

@ -0,0 +1,64 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStatePull_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StatePull
}{
"defaults": {
nil,
&StatePull{},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStatePull(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseStatePull_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StatePull
wantDiags tfdiags.Diagnostics
}{
"unknown flag": {
[]string{"-boop"},
&StatePull{},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStatePull(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,67 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StatePush represents the command-line arguments for the state push command.
type StatePush struct {
// Force writes the state even if lineages don't match or the remote
// serial is higher.
Force bool
// StateLock, if true, requests that the backend lock the state for this
// operation.
StateLock bool
// StateLockTimeout is the duration to retry a state lock.
StateLockTimeout time.Duration
// IgnoreRemoteVersion, if true, continues even if remote and local
// Terraform versions are incompatible.
IgnoreRemoteVersion bool
// Path is the path to the state file to push, or "-" for stdin.
Path string
}
// ParseStatePush processes CLI arguments, returning a StatePush value and
// diagnostics. If errors are encountered, a StatePush value is still returned
// representing the best effort interpretation of the arguments.
func ParseStatePush(args []string) (*StatePush, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
push := &StatePush{}
cmdFlags := defaultFlagSet("state push")
cmdFlags.BoolVar(&push.Force, "force", false, "")
cmdFlags.BoolVar(&push.StateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&push.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.BoolVar(&push.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) != 1 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the path to a Terraform state file.",
))
return push, diags
}
push.Path = args[0]
return push, diags
}

View file

@ -0,0 +1,138 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStatePush_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StatePush
}{
"path only": {
[]string{"replace.tfstate"},
&StatePush{
StateLock: true,
Path: "replace.tfstate",
},
},
"stdin": {
[]string{"-"},
&StatePush{
StateLock: true,
Path: "-",
},
},
"force": {
[]string{"-force", "replace.tfstate"},
&StatePush{
Force: true,
StateLock: true,
Path: "replace.tfstate",
},
},
"lock disabled": {
[]string{"-lock=false", "replace.tfstate"},
&StatePush{
Path: "replace.tfstate",
},
},
"lock timeout": {
[]string{"-lock-timeout=5s", "replace.tfstate"},
&StatePush{
StateLock: true,
StateLockTimeout: 5 * time.Second,
Path: "replace.tfstate",
},
},
"ignore remote version": {
[]string{"-ignore-remote-version", "replace.tfstate"},
&StatePush{
StateLock: true,
IgnoreRemoteVersion: true,
Path: "replace.tfstate",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStatePush(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseStatePush_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StatePush
wantDiags tfdiags.Diagnostics
}{
"no arguments": {
nil,
&StatePush{
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the path to a Terraform state file.",
),
},
},
"too many arguments": {
[]string{"foo.tfstate", "bar.tfstate"},
&StatePush{
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the path to a Terraform state file.",
),
},
},
"unknown flag": {
[]string{"-boop"},
&StatePush{
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the path to a Terraform state file.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStatePush(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,79 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StateReplaceProvider represents the command-line arguments for the state
// replace-provider command.
type StateReplaceProvider struct {
// AutoApprove, if true, skips the interactive approval step.
AutoApprove bool
// BackupPath is the path where Terraform should write the backup state.
BackupPath string
// StateLock, if true, requests that the backend lock the state for this
// operation.
StateLock bool
// StateLockTimeout is the duration to retry a state lock.
StateLockTimeout time.Duration
// StatePath is an optional path to a local state file.
StatePath string
// IgnoreRemoteVersion, if true, continues even if remote and local
// Terraform versions are incompatible.
IgnoreRemoteVersion bool
// FromProviderAddr is the provider address to replace.
FromProviderAddr string
// ToProviderAddr is the replacement provider address.
ToProviderAddr string
}
// ParseStateReplaceProvider processes CLI arguments, returning a
// StateReplaceProvider value and diagnostics. If errors are encountered, a
// StateReplaceProvider value is still returned representing the best effort
// interpretation of the arguments.
func ParseStateReplaceProvider(args []string) (*StateReplaceProvider, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
rp := &StateReplaceProvider{}
cmdFlags := defaultFlagSet("state replace-provider")
cmdFlags.BoolVar(&rp.AutoApprove, "auto-approve", false, "skip interactive approval of replacements")
cmdFlags.StringVar(&rp.BackupPath, "backup", "-", "backup")
cmdFlags.BoolVar(&rp.StateLock, "lock", true, "lock states")
cmdFlags.DurationVar(&rp.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&rp.StatePath, "state", "", "path")
cmdFlags.BoolVar(&rp.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) != 2 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the from and to provider addresses.",
))
return rp, diags
}
rp.FromProviderAddr = args[0]
rp.ToProviderAddr = args[1]
return rp, diags
}

View file

@ -0,0 +1,135 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStateReplaceProvider_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateReplaceProvider
}{
"provider addresses only": {
[]string{"hashicorp/aws", "acmecorp/aws"},
&StateReplaceProvider{
BackupPath: "-",
StateLock: true,
FromProviderAddr: "hashicorp/aws",
ToProviderAddr: "acmecorp/aws",
},
},
"auto approve": {
[]string{"-auto-approve", "hashicorp/aws", "acmecorp/aws"},
&StateReplaceProvider{
AutoApprove: true,
BackupPath: "-",
StateLock: true,
FromProviderAddr: "hashicorp/aws",
ToProviderAddr: "acmecorp/aws",
},
},
"all options": {
[]string{
"-auto-approve",
"-backup=backup.tfstate",
"-lock=false",
"-lock-timeout=5s",
"-state=state.tfstate",
"-ignore-remote-version",
"hashicorp/aws",
"acmecorp/aws",
},
&StateReplaceProvider{
AutoApprove: true,
BackupPath: "backup.tfstate",
StateLock: false,
StateLockTimeout: 5 * time.Second,
StatePath: "state.tfstate",
IgnoreRemoteVersion: true,
FromProviderAddr: "hashicorp/aws",
ToProviderAddr: "acmecorp/aws",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStateReplaceProvider(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseStateReplaceProvider_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateReplaceProvider
wantDiags tfdiags.Diagnostics
}{
"no arguments": {
nil,
&StateReplaceProvider{
BackupPath: "-",
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the from and to provider addresses.",
),
},
},
"too many arguments": {
[]string{"a", "b", "c", "d"},
&StateReplaceProvider{
BackupPath: "-",
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly two arguments expected: the from and to provider addresses.",
),
},
},
"unknown flag": {
[]string{"-invalid", "hashicorp/google", "acmecorp/google"},
&StateReplaceProvider{
BackupPath: "-",
StateLock: true,
FromProviderAddr: "hashicorp/google",
ToProviderAddr: "acmecorp/google",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -invalid",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStateReplaceProvider(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,74 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StateRm represents the command-line arguments for the state rm command.
type StateRm struct {
// DryRun, if true, prints out what would be removed without actually
// removing anything.
DryRun bool
// BackupPath is the path where Terraform should write the backup state.
BackupPath string
// StateLock, if true, requests that the backend lock the state for this
// operation.
StateLock bool
// StateLockTimeout is the duration to retry a state lock.
StateLockTimeout time.Duration
// StatePath is an optional path to a local state file.
StatePath string
// IgnoreRemoteVersion, if true, continues even if remote and local
// Terraform versions are incompatible.
IgnoreRemoteVersion bool
// Addrs are the resource instance addresses to remove.
Addrs []string
}
// ParseStateRm processes CLI arguments, returning a StateRm value and
// diagnostics. If errors are encountered, a StateRm value is still returned
// representing the best effort interpretation of the arguments.
func ParseStateRm(args []string) (*StateRm, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
rm := &StateRm{}
cmdFlags := defaultFlagSet("state rm")
cmdFlags.BoolVar(&rm.DryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&rm.BackupPath, "backup", "-", "backup")
cmdFlags.BoolVar(&rm.StateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&rm.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&rm.StatePath, "state", "", "path")
cmdFlags.BoolVar(&rm.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) < 1 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"At least one address is required.",
))
}
rm.Addrs = args
return rm, diags
}

View file

@ -0,0 +1,118 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStateRm_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateRm
}{
"single address": {
[]string{"test_instance.foo"},
&StateRm{
BackupPath: "-",
StateLock: true,
Addrs: []string{"test_instance.foo"},
},
},
"multiple addresses": {
[]string{"test_instance.foo", "test_instance.bar"},
&StateRm{
BackupPath: "-",
StateLock: true,
Addrs: []string{"test_instance.foo", "test_instance.bar"},
},
},
"all options": {
[]string{"-dry-run", "-backup=backup.tfstate", "-lock=false", "-lock-timeout=5s", "-state=state.tfstate", "-ignore-remote-version", "test_instance.foo"},
&StateRm{
DryRun: true,
BackupPath: "backup.tfstate",
StateLock: false,
StateLockTimeout: 5 * time.Second,
StatePath: "state.tfstate",
IgnoreRemoteVersion: true,
Addrs: []string{"test_instance.foo"},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStateRm(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if got.DryRun != tc.want.DryRun ||
got.BackupPath != tc.want.BackupPath ||
got.StateLock != tc.want.StateLock ||
got.StateLockTimeout != tc.want.StateLockTimeout ||
got.StatePath != tc.want.StatePath ||
got.IgnoreRemoteVersion != tc.want.IgnoreRemoteVersion {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
if len(got.Addrs) != len(tc.want.Addrs) {
t.Fatalf("unexpected Addrs length\n got: %d\nwant: %d", len(got.Addrs), len(tc.want.Addrs))
}
for i := range got.Addrs {
if got.Addrs[i] != tc.want.Addrs[i] {
t.Fatalf("unexpected Addrs[%d]\n got: %q\nwant: %q", i, got.Addrs[i], tc.want.Addrs[i])
}
}
})
}
}
func TestParseStateRm_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
wantAddrs int
wantDiags tfdiags.Diagnostics
}{
"no arguments": {
nil,
0,
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"At least one address is required.",
),
},
},
"unknown flag": {
[]string{"-boop"},
0,
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"At least one address is required.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStateRm(tc.args)
if len(got.Addrs) != tc.wantAddrs {
t.Fatalf("unexpected Addrs length\n got: %d\nwant: %d", len(got.Addrs), tc.wantAddrs)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,54 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"github.com/hashicorp/terraform/internal/tfdiags"
)
// StateShow represents the command-line arguments for the state show command.
type StateShow struct {
// StatePath is an optional path to a state file, overriding the default.
StatePath string
// Address is the resource instance address to show.
Address string
}
// ParseStateShow processes CLI arguments, returning a StateShow value and
// diagnostics. If errors are encountered, a StateShow value is still returned
// representing the best effort interpretation of the arguments.
func ParseStateShow(args []string) (*StateShow, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
show := &StateShow{}
var statePath string
cmdFlags := defaultFlagSet("state show")
cmdFlags.StringVar(&statePath, "state", "", "path")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) != 1 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the address of a resource instance to show.",
))
}
show.StatePath = statePath
if len(args) > 0 {
show.Address = args[0]
}
return show, diags
}

View file

@ -0,0 +1,102 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseStateShow_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateShow
}{
"address only": {
[]string{"test_instance.foo"},
&StateShow{
Address: "test_instance.foo",
},
},
"with state path": {
[]string{"-state=foobar.tfstate", "test_instance.foo"},
&StateShow{
StatePath: "foobar.tfstate",
Address: "test_instance.foo",
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseStateShow(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseStateShow_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *StateShow
wantDiags tfdiags.Diagnostics
}{
"no arguments": {
nil,
&StateShow{},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the address of a resource instance to show.",
),
},
},
"too many arguments": {
[]string{"test_instance.foo", "test_instance.bar"},
&StateShow{
Address: "test_instance.foo",
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the address of a resource instance to show.",
),
},
},
"unknown flag": {
[]string{"-boop"},
&StateShow{},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -boop",
),
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"Exactly one argument expected: the address of a resource instance to show.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseStateShow(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,86 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// Taint represents the command-line arguments for the taint command.
type Taint struct {
// Address is the address of the resource instance to taint.
Address string
// AllowMissing, if true, means the command will succeed even if the
// resource is not found in state.
AllowMissing bool
// BackupPath is the path to backup the existing state file before
// modifying.
BackupPath string
// StateLock, if true, locks the state file during operations.
StateLock bool
// StateLockTimeout is the duration to retry a state lock.
StateLockTimeout time.Duration
// StatePath is the path to the state file to read and modify.
StatePath string
// StateOutPath is the path to write the updated state file.
StateOutPath string
// IgnoreRemoteVersion, if true, continues even if remote and local
// Terraform versions are incompatible.
IgnoreRemoteVersion bool
}
// ParseTaint processes CLI arguments, returning a Taint value and errors.
// If errors are encountered, a Taint value is still returned representing
// the best effort interpretation of the arguments.
func ParseTaint(args []string) (*Taint, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
taint := &Taint{}
cmdFlags := defaultFlagSet("taint")
cmdFlags.BoolVar(&taint.AllowMissing, "allow-missing", false, "allow missing")
cmdFlags.StringVar(&taint.BackupPath, "backup", "", "path")
cmdFlags.BoolVar(&taint.StateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&taint.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&taint.StatePath, "state", "", "path")
cmdFlags.StringVar(&taint.StateOutPath, "state-out", "", "path")
cmdFlags.BoolVar(&taint.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) == 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"The taint command expects exactly one argument: the address of the resource to taint.",
))
} else if len(args) > 1 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Too many command line arguments",
"The taint command expects exactly one argument: the address of the resource to taint.",
))
}
if len(args) > 0 {
taint.Address = args[0]
}
return taint, diags
}

View file

@ -0,0 +1,177 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseTaint_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *Taint
}{
"defaults with address": {
[]string{"test_instance.foo"},
&Taint{
Address: "test_instance.foo",
StateLock: true,
},
},
"allow-missing": {
[]string{"-allow-missing", "test_instance.foo"},
&Taint{
Address: "test_instance.foo",
AllowMissing: true,
StateLock: true,
},
},
"backup": {
[]string{"-backup", "backup.tfstate", "test_instance.foo"},
&Taint{
Address: "test_instance.foo",
BackupPath: "backup.tfstate",
StateLock: true,
},
},
"lock disabled": {
[]string{"-lock=false", "test_instance.foo"},
&Taint{
Address: "test_instance.foo",
},
},
"lock-timeout": {
[]string{"-lock-timeout=10s", "test_instance.foo"},
&Taint{
Address: "test_instance.foo",
StateLock: true,
StateLockTimeout: 10 * time.Second,
},
},
"state": {
[]string{"-state=foo.tfstate", "test_instance.foo"},
&Taint{
Address: "test_instance.foo",
StateLock: true,
StatePath: "foo.tfstate",
},
},
"state-out": {
[]string{"-state-out=foo.tfstate", "test_instance.foo"},
&Taint{
Address: "test_instance.foo",
StateLock: true,
StateOutPath: "foo.tfstate",
},
},
"ignore-remote-version": {
[]string{"-ignore-remote-version", "test_instance.foo"},
&Taint{
Address: "test_instance.foo",
StateLock: true,
IgnoreRemoteVersion: true,
},
},
"all flags": {
[]string{
"-allow-missing",
"-backup=backup.tfstate",
"-lock=false",
"-lock-timeout=10s",
"-state=foo.tfstate",
"-state-out=bar.tfstate",
"-ignore-remote-version",
"module.child.test_instance.foo",
},
&Taint{
Address: "module.child.test_instance.foo",
AllowMissing: true,
BackupPath: "backup.tfstate",
StateLockTimeout: 10 * time.Second,
StatePath: "foo.tfstate",
StateOutPath: "bar.tfstate",
IgnoreRemoteVersion: true,
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseTaint(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseTaint_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *Taint
wantDiags tfdiags.Diagnostics
}{
"unknown flag": {
[]string{"-unknown"},
&Taint{
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -unknown",
),
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"The taint command expects exactly one argument: the address of the resource to taint.",
),
},
},
"missing address": {
nil,
&Taint{
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"The taint command expects exactly one argument: the address of the resource to taint.",
),
},
},
"too many arguments": {
[]string{"test_instance.foo", "test_instance.bar"},
&Taint{
Address: "test_instance.foo",
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Too many command line arguments",
"The taint command expects exactly one argument: the address of the resource to taint.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseTaint(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -0,0 +1,86 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// Untaint represents the command-line arguments for the untaint command.
type Untaint struct {
// Address is the address of the resource instance to untaint.
Address string
// AllowMissing, if true, means the command will succeed even if the
// resource is not found in state.
AllowMissing bool
// BackupPath is the path to backup the existing state file before
// modifying.
BackupPath string
// StateLock, if true, locks the state file during operations.
StateLock bool
// StateLockTimeout is the duration to retry a state lock.
StateLockTimeout time.Duration
// StatePath is the path to the state file to read and modify.
StatePath string
// StateOutPath is the path to write the updated state file.
StateOutPath string
// IgnoreRemoteVersion, if true, continues even if remote and local
// Terraform versions are incompatible.
IgnoreRemoteVersion bool
}
// ParseUntaint processes CLI arguments, returning an Untaint value and errors.
// If errors are encountered, an Untaint value is still returned representing
// the best effort interpretation of the arguments.
func ParseUntaint(args []string) (*Untaint, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
untaint := &Untaint{}
cmdFlags := defaultFlagSet("untaint")
cmdFlags.BoolVar(&untaint.AllowMissing, "allow-missing", false, "allow missing")
cmdFlags.StringVar(&untaint.BackupPath, "backup", "", "path")
cmdFlags.BoolVar(&untaint.StateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&untaint.StateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&untaint.StatePath, "state", "", "path")
cmdFlags.StringVar(&untaint.StateOutPath, "state-out", "", "path")
cmdFlags.BoolVar(&untaint.IgnoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
if err := cmdFlags.Parse(args); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
err.Error(),
))
}
args = cmdFlags.Args()
if len(args) == 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"The untaint command expects exactly one argument: the address of the resource to untaint.",
))
} else if len(args) > 1 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Too many command line arguments",
"The untaint command expects exactly one argument: the address of the resource to untaint.",
))
}
if len(args) > 0 {
untaint.Address = args[0]
}
return untaint, diags
}

View file

@ -0,0 +1,177 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package arguments
import (
"testing"
"time"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestParseUntaint_valid(t *testing.T) {
testCases := map[string]struct {
args []string
want *Untaint
}{
"defaults with address": {
[]string{"test_instance.foo"},
&Untaint{
Address: "test_instance.foo",
StateLock: true,
},
},
"allow-missing": {
[]string{"-allow-missing", "test_instance.foo"},
&Untaint{
Address: "test_instance.foo",
AllowMissing: true,
StateLock: true,
},
},
"backup": {
[]string{"-backup", "backup.tfstate", "test_instance.foo"},
&Untaint{
Address: "test_instance.foo",
BackupPath: "backup.tfstate",
StateLock: true,
},
},
"lock disabled": {
[]string{"-lock=false", "test_instance.foo"},
&Untaint{
Address: "test_instance.foo",
},
},
"lock-timeout": {
[]string{"-lock-timeout=10s", "test_instance.foo"},
&Untaint{
Address: "test_instance.foo",
StateLock: true,
StateLockTimeout: 10 * time.Second,
},
},
"state": {
[]string{"-state=foo.tfstate", "test_instance.foo"},
&Untaint{
Address: "test_instance.foo",
StateLock: true,
StatePath: "foo.tfstate",
},
},
"state-out": {
[]string{"-state-out=foo.tfstate", "test_instance.foo"},
&Untaint{
Address: "test_instance.foo",
StateLock: true,
StateOutPath: "foo.tfstate",
},
},
"ignore-remote-version": {
[]string{"-ignore-remote-version", "test_instance.foo"},
&Untaint{
Address: "test_instance.foo",
StateLock: true,
IgnoreRemoteVersion: true,
},
},
"all flags": {
[]string{
"-allow-missing",
"-backup=backup.tfstate",
"-lock=false",
"-lock-timeout=10s",
"-state=foo.tfstate",
"-state-out=bar.tfstate",
"-ignore-remote-version",
"module.child.test_instance.foo",
},
&Untaint{
Address: "module.child.test_instance.foo",
AllowMissing: true,
BackupPath: "backup.tfstate",
StateLockTimeout: 10 * time.Second,
StatePath: "foo.tfstate",
StateOutPath: "bar.tfstate",
IgnoreRemoteVersion: true,
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, diags := ParseUntaint(tc.args)
if len(diags) > 0 {
t.Fatalf("unexpected diags: %v", diags)
}
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
})
}
}
func TestParseUntaint_invalid(t *testing.T) {
testCases := map[string]struct {
args []string
want *Untaint
wantDiags tfdiags.Diagnostics
}{
"unknown flag": {
[]string{"-unknown"},
&Untaint{
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"flag provided but not defined: -unknown",
),
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"The untaint command expects exactly one argument: the address of the resource to untaint.",
),
},
},
"missing address": {
nil,
&Untaint{
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Required argument missing",
"The untaint command expects exactly one argument: the address of the resource to untaint.",
),
},
},
"too many arguments": {
[]string{"test_instance.foo", "test_instance.bar"},
&Untaint{
Address: "test_instance.foo",
StateLock: true,
},
tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Too many command line arguments",
"The untaint command expects exactly one argument: the address of the resource to untaint.",
),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
got, gotDiags := ParseUntaint(tc.args)
if *got != *tc.want {
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
}
tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags)
})
}
}

View file

@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"log"
"os"
"strings"
"github.com/hashicorp/hcl/v2"
@ -29,41 +28,34 @@ type ImportCommand struct {
}
func (c *ImportCommand) Run(args []string) int {
// Get the pwd since its our default -config flag value
pwd, err := os.Getwd()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
return 1
parsedArgs, diags := arguments.ParseImport(c.Meta.process(args))
// Copy parsed flags back to Meta
c.Meta.statePath = parsedArgs.State.StatePath
c.Meta.stateOutPath = parsedArgs.State.StateOutPath
c.Meta.backupPath = parsedArgs.State.BackupPath
c.Meta.stateLock = parsedArgs.State.Lock
c.Meta.stateLockTimeout = parsedArgs.State.LockTimeout
c.Meta.parallelism = parsedArgs.Parallelism
c.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
c.Meta.input = parsedArgs.InputEnabled
c.Meta.compactWarnings = parsedArgs.CompactWarnings
c.Meta.targetFlags = parsedArgs.TargetFlags
varItems := parsedArgs.Vars.All()
c.Meta.variableArgs = arguments.FlagNameValueSlice{
FlagName: "-var",
Items: &varItems,
}
var configPath string
args = c.Meta.process(args)
cmdFlags := c.Meta.extendedFlagSet("import")
cmdFlags.BoolVar(&c.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
cmdFlags.IntVar(&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
cmdFlags.StringVar(&configPath, "config", pwd, "path")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
if diags.HasErrors() {
c.showDiagnostics(diags)
c.Ui.Error(c.Help())
return 1
}
args = cmdFlags.Args()
if len(args) != 2 {
c.Ui.Error("The import command expects two arguments.")
cmdFlags.Usage()
return 1
}
var diags tfdiags.Diagnostics
// Parse the provided resource address.
traversalSrc := []byte(args[0])
traversalSrc := []byte(parsedArgs.Addr)
traversal, travDiags := hclsyntax.ParseTraversalAbs(traversalSrc, "<import-address>", hcl.Pos{Line: 1, Column: 1})
diags = diags.Append(travDiags)
if travDiags.HasErrors() {
@ -87,13 +79,13 @@ func (c *ImportCommand) Run(args []string) int {
return 1
}
if !c.dirIsConfigPath(configPath) {
if !c.dirIsConfigPath(parsedArgs.ConfigPath) {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "No Terraform configuration files",
Detail: fmt.Sprintf(
"The directory %s does not contain any Terraform configuration files (.tf or .tf.json). To specify a different configuration directory, use the -config=\"...\" command line option.",
configPath,
parsedArgs.ConfigPath,
),
})
c.showDiagnostics(diags)
@ -102,7 +94,7 @@ func (c *ImportCommand) Run(args []string) int {
// Load the full config, so we can verify that the target resource is
// already configured.
config, configDiags := c.loadConfig(configPath)
config, configDiags := c.loadConfig(parsedArgs.ConfigPath)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
c.showDiagnostics(diags)
@ -158,6 +150,7 @@ func (c *ImportCommand) Run(args []string) int {
}
// Check for user-supplied plugin path
var err error
if c.pluginPath, err = c.loadPluginPath(); err != nil {
c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
return 1
@ -184,7 +177,7 @@ func (c *ImportCommand) Run(args []string) int {
// Build the operation
opReq := c.Operation(b, arguments.ViewHuman)
opReq.ConfigDir = configPath
opReq.ConfigDir = parsedArgs.ConfigPath
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {
diags = diags.Append(err)
@ -234,7 +227,7 @@ func (c *ImportCommand) Run(args []string) int {
Targets: []*terraform.ImportTarget{
{
LegacyAddr: addr,
LegacyID: args[1],
LegacyID: parsedArgs.ID,
},
},

View file

@ -575,17 +575,6 @@ func (m *Meta) defaultFlagSet(n string) *flag.FlagSet {
return f
}
// ignoreRemoteVersionFlagSet add the ignore-remote version flag to suppress
// the error when the configured Terraform version on the remote workspace
// does not match the local Terraform version.
func (m *Meta) ignoreRemoteVersionFlagSet(n string) *flag.FlagSet {
f := m.defaultFlagSet(n)
f.BoolVar(&m.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
return f
}
// extendedFlagSet adds custom flags that are mostly used by commands
// that are used to run an operation like plan or apply.
func (m *Meta) extendedFlagSet(n string) *flag.FlagSet {

View file

@ -31,26 +31,19 @@ func (c *ProvidersCommand) Synopsis() string {
}
func (c *ProvidersCommand) Run(args []string) int {
var testsDirectory string
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("providers")
cmdFlags.StringVar(&testsDirectory, "test-directory", "tests", "test-directory")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
parsedArgs, diags := arguments.ParseProviders(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
configPath, err := ModulePath(cmdFlags.Args())
configPath, err := ModulePath(nil)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
var diags tfdiags.Diagnostics
empty, err := configs.IsEmptyDir(configPath, testsDirectory)
empty, err := configs.IsEmptyDir(configPath, parsedArgs.TestsDirectory)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -74,7 +67,7 @@ func (c *ProvidersCommand) Run(args []string) int {
return 1
}
config, configDiags := c.loadConfigWithTests(configPath, testsDirectory)
config, configDiags := c.loadConfigWithTests(configPath, parsedArgs.TestsDirectory)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
c.showDiagnostics(diags)

View file

@ -39,44 +39,18 @@ func (c *ProvidersLockCommand) Synopsis() string {
}
func (c *ProvidersLockCommand) Run(args []string) int {
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("providers lock")
var optPlatforms arguments.FlagStringSlice
var fsMirrorDir string
var netMirrorURL string
var testDirectory string
cmdFlags.Var(&optPlatforms, "platform", "target platform")
cmdFlags.StringVar(&fsMirrorDir, "fs-mirror", "", "filesystem mirror directory")
cmdFlags.StringVar(&netMirrorURL, "net-mirror", "", "network mirror base URL")
cmdFlags.StringVar(&testDirectory, "test-directory", "tests", "test-directory")
pluginCache := cmdFlags.Bool("enable-plugin-cache", false, "")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
return 1
}
var diags tfdiags.Diagnostics
if fsMirrorDir != "" && netMirrorURL != "" {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid installation method options",
"The -fs-mirror and -net-mirror command line options are mutually-exclusive.",
))
parsedArgs, diags := arguments.ParseProvidersLock(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
providerStrs := cmdFlags.Args()
var platforms []getproviders.Platform
if len(optPlatforms) == 0 {
if len(parsedArgs.Platforms) == 0 {
platforms = []getproviders.Platform{getproviders.CurrentPlatform}
} else {
platforms = make([]getproviders.Platform, 0, len(optPlatforms))
for _, platformStr := range optPlatforms {
platforms = make([]getproviders.Platform, 0, len(parsedArgs.Platforms))
for _, platformStr := range parsedArgs.Platforms {
platform, err := getproviders.ParsePlatform(platformStr)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
@ -104,10 +78,10 @@ func (c *ProvidersLockCommand) Run(args []string) int {
// against the upstream checksums.
var source getproviders.Source
switch {
case fsMirrorDir != "":
source = getproviders.NewFilesystemMirrorSource(fsMirrorDir)
case netMirrorURL != "":
u, err := url.Parse(netMirrorURL)
case parsedArgs.FSMirrorDir != "":
source = getproviders.NewFilesystemMirrorSource(parsedArgs.FSMirrorDir)
case parsedArgs.NetMirrorURL != "":
u, err := url.Parse(parsedArgs.NetMirrorURL)
if err != nil || u.Scheme != "https" {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -125,7 +99,7 @@ func (c *ProvidersLockCommand) Run(args []string) int {
source = getproviders.NewRegistrySource(c.Services)
}
config, confDiags := c.loadConfigWithTests(".", testDirectory)
config, confDiags := c.loadConfigWithTests(".", parsedArgs.TestsDirectory)
diags = diags.Append(confDiags)
reqs, hclDiags := config.ProviderRequirements()
diags = diags.Append(hclDiags)
@ -134,9 +108,9 @@ func (c *ProvidersLockCommand) Run(args []string) int {
// we'll modify "reqs" to only include those. Modifying this is okay
// because config.ProviderRequirements generates a fresh map result
// for each call.
if len(providerStrs) != 0 {
if len(parsedArgs.Providers) != 0 {
providers := map[addrs.Provider]struct{}{}
for _, raw := range providerStrs {
for _, raw := range parsedArgs.Providers {
addr, moreDiags := addrs.ParseProviderSourceString(raw)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
@ -253,7 +227,7 @@ func (c *ProvidersLockCommand) Run(args []string) int {
// Use global plugin cache for extra speed if present and flag is set
globalCacheDir := c.providerGlobalCacheDir()
if *pluginCache && globalCacheDir != nil {
if parsedArgs.EnablePluginCache && globalCacheDir != nil {
installer.SetGlobalCacheDir(globalCacheDir.WithPlatform(platform))
installer.SetGlobalCacheDirMayBreakDependencyLockFile(c.PluginCacheMayBreakDependencyLockFile)
}

View file

@ -32,41 +32,18 @@ func (c *ProvidersMirrorCommand) Synopsis() string {
}
func (c *ProvidersMirrorCommand) Run(args []string) int {
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("providers mirror")
var optPlatforms arguments.FlagStringSlice
cmdFlags.Var(&optPlatforms, "platform", "target platform")
var optLockFile bool
cmdFlags.BoolVar(&optLockFile, "lock-file", true, "use lock file")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
return 1
}
var diags tfdiags.Diagnostics
args = cmdFlags.Args()
if len(args) != 1 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No output directory specified",
"The providers mirror command requires an output directory as a command-line argument.",
))
parsedArgs, diags := arguments.ParseProvidersMirror(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
outputDir := args[0]
var platforms []getproviders.Platform
if len(optPlatforms) == 0 {
if len(parsedArgs.Platforms) == 0 {
platforms = []getproviders.Platform{getproviders.CurrentPlatform}
} else {
platforms = make([]getproviders.Platform, 0, len(optPlatforms))
for _, platformStr := range optPlatforms {
platforms = make([]getproviders.Platform, 0, len(parsedArgs.Platforms))
for _, platformStr := range parsedArgs.Platforms {
platform, err := getproviders.ParsePlatform(platformStr)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
@ -94,7 +71,7 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
diags = diags.Append(lockedDepsDiags)
// If lock file is present, validate it against configuration
if !lockedDeps.Empty() && optLockFile {
if !lockedDeps.Empty() && parsedArgs.LockFile {
if errs := config.VerifyDependencySelections(lockedDeps); len(errs) > 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -166,7 +143,7 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
continue
}
selected := candidates.Newest()
if !lockedDeps.Empty() && optLockFile {
if !lockedDeps.Empty() && parsedArgs.LockFile {
selected = lockedDeps.Provider(provider).Version()
c.Ui.Output(fmt.Sprintf(" - Selected v%s to match dependency lock file", selected.String()))
} else if len(constraintsStr) > 0 {
@ -214,7 +191,7 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
// so we can verify its checksums and signatures before making
// it discoverable to mirror clients. (stagingPath intentionally
// does not follow the filesystem mirror file naming convention.)
targetPath := meta.PackedFilePath(outputDir)
targetPath := meta.PackedFilePath(parsedArgs.OutputDir)
stagingPath := filepath.Join(filepath.Dir(targetPath), "."+filepath.Base(targetPath))
err = httpGetter.GetFile(stagingPath, urlObj)
if err != nil {
@ -255,7 +232,7 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
// by relying on the selections we made above, because we want to still
// include in the indices any packages that were already present and
// not affected by the changes we just made.
available, err := getproviders.SearchLocalDirectory(outputDir)
available, err := getproviders.SearchLocalDirectory(parsedArgs.OutputDir)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -273,7 +250,7 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
// we'll ask the getproviders package to build an archive filename
// for a fictitious package and then use the directory portion of it.
indexDir := filepath.Dir(getproviders.PackedFilePathForPackage(
outputDir, provider, versions.Unspecified, getproviders.CurrentPlatform,
parsedArgs.OutputDir, provider, versions.Unspecified, getproviders.CurrentPlatform,
))
indexVersions := map[string]interface{}{}
indexArchives := map[getproviders.Version]map[string]interface{}{}

View file

@ -10,7 +10,6 @@ import (
"github.com/hashicorp/terraform/internal/backend/backendrun"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// ProvidersCommand is a Command implementation that prints out information
@ -28,23 +27,12 @@ func (c *ProvidersSchemaCommand) Synopsis() string {
}
func (c *ProvidersSchemaCommand) Run(args []string) int {
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("providers schema")
var jsonOutput bool
cmdFlags.BoolVar(&jsonOutput, "json", false, "produce JSON output")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
_, diags := arguments.ParseProvidersSchema(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
if !jsonOutput {
c.Ui.Error(
"The `terraform providers schema` command requires the `-json` flag.\n")
cmdFlags.Usage()
return 1
}
viewType := arguments.ViewJSON // See above; enforced use of JSON output
// Check for user-supplied plugin path
@ -53,9 +41,6 @@ func (c *ProvidersSchemaCommand) Run(args []string) int {
c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
return 1
}
var diags tfdiags.Diagnostics
// Load the backend
b, backendDiags := c.backend(".", viewType)
diags = diags.Append(backendDiags)

View file

@ -7,7 +7,6 @@ import (
"fmt"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/states"
@ -21,19 +20,14 @@ type StateListCommand struct {
}
func (c *StateListCommand) Run(args []string) int {
args = c.Meta.process(args)
var statePath string
cmdFlags := c.Meta.defaultFlagSet("state list")
cmdFlags.StringVar(&statePath, "state", "", "path")
lookupId := cmdFlags.String("id", "", "Restrict output to paths with a resource having the specified ID.")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
return cli.RunResultHelp
parsedArgs, diags := arguments.ParseStateList(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
args = cmdFlags.Args()
if statePath != "" {
c.Meta.statePath = statePath
if parsedArgs.StatePath != "" {
c.Meta.statePath = parsedArgs.StatePath
}
// Load the backend
@ -69,10 +63,10 @@ func (c *StateListCommand) Run(args []string) int {
}
var addrs []addrs.AbsResourceInstance
if len(args) == 0 {
if len(parsedArgs.Addrs) == 0 {
addrs, diags = c.lookupAllResourceInstanceAddrs(state)
} else {
addrs, diags = c.lookupResourceInstanceAddrs(state, args...)
addrs, diags = c.lookupResourceInstanceAddrs(state, parsedArgs.Addrs...)
}
if diags.HasErrors() {
c.showDiagnostics(diags)
@ -81,7 +75,7 @@ func (c *StateListCommand) Run(args []string) int {
for _, addr := range addrs {
if is := state.ResourceInstance(addr); is != nil {
if *lookupId == "" || *lookupId == states.LegacyInstanceObjectID(is.Current) {
if parsedArgs.ID == "" || parsedArgs.ID == states.LegacyInstanceObjectID(is.Current) {
c.Ui.Output(addr.String())
}
}

View file

@ -7,8 +7,6 @@ import (
"fmt"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend/backendrun"
"github.com/hashicorp/terraform/internal/command/arguments"
@ -26,28 +24,17 @@ type StateMvCommand struct {
}
func (c *StateMvCommand) Run(args []string) int {
args = c.Meta.process(args)
// We create two metas to track the two states
var backupPathOut, statePathOut string
var dryRun bool
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("state mv")
cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
cmdFlags.StringVar(&backupPathOut, "backup-out", "-", "backup")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock states")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&c.statePath, "state", "", "path")
cmdFlags.StringVar(&statePathOut, "state-out", "", "path")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
parsedArgs, parseDiags := arguments.ParseStateMv(c.Meta.process(args))
if parseDiags.HasErrors() {
c.showDiagnostics(parseDiags)
return 1
}
args = cmdFlags.Args()
if len(args) != 2 {
c.Ui.Error("Exactly two arguments expected.\n")
return cli.RunResultHelp
}
c.backupPath = parsedArgs.BackupPath
c.Meta.stateLock = parsedArgs.StateLock
c.Meta.stateLockTimeout = parsedArgs.StateLockTimeout
c.statePath = parsedArgs.StatePath
c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
@ -58,7 +45,7 @@ func (c *StateMvCommand) Run(args []string) int {
// and the state option is not set, make sure
// the backend is local
backupOptionSetWithoutStateOption := c.backupPath != "-" && c.statePath == ""
backupOutOptionSetWithoutStateOption := backupPathOut != "-" && c.statePath == ""
backupOutOptionSetWithoutStateOption := parsedArgs.BackupOutPath != "-" && c.statePath == ""
var setLegacyLocalBackendOptions []string
if backupOptionSetWithoutStateOption {
@ -127,9 +114,9 @@ func (c *StateMvCommand) Run(args []string) int {
stateToMgr := stateFromMgr
stateTo := stateFrom
if statePathOut != "" {
c.statePath = statePathOut
c.backupPath = backupPathOut
if parsedArgs.StateOutPath != "" {
c.statePath = parsedArgs.StateOutPath
c.backupPath = parsedArgs.BackupOutPath
stateToMgr, err = c.State(view)
if err != nil {
@ -162,9 +149,9 @@ func (c *StateMvCommand) Run(args []string) int {
}
var diags tfdiags.Diagnostics
sourceAddr, moreDiags := c.lookupSingleStateObjectAddr(stateFrom, args[0])
sourceAddr, moreDiags := c.lookupSingleStateObjectAddr(stateFrom, parsedArgs.SourceAddr)
diags = diags.Append(moreDiags)
destAddr, moreDiags := c.lookupSingleStateObjectAddr(stateFrom, args[1])
destAddr, moreDiags := c.lookupSingleStateObjectAddr(stateFrom, parsedArgs.DestAddr)
diags = diags.Append(moreDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
@ -172,7 +159,7 @@ func (c *StateMvCommand) Run(args []string) int {
}
prefix := "Move"
if dryRun {
if parsedArgs.DryRun {
prefix = "Would move"
}
@ -231,7 +218,7 @@ func (c *StateMvCommand) Run(args []string) int {
moved++
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), addrTo.String()))
if !dryRun {
if !parsedArgs.DryRun {
ssFrom.RemoveModule(addrFrom)
// Update the address before adding it to the state.
@ -276,7 +263,7 @@ func (c *StateMvCommand) Run(args []string) int {
moved++
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), addrTo.String()))
if !dryRun {
if !parsedArgs.DryRun {
ssFrom.RemoveResource(addrFrom)
// Update the address before adding it to the state.
@ -329,8 +316,8 @@ func (c *StateMvCommand) Run(args []string) int {
}
moved++
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), args[1]))
if !dryRun {
c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), parsedArgs.DestAddr))
if !parsedArgs.DryRun {
fromResourceAddr := addrFrom.ContainingResource()
fromResource := ssFrom.Resource(fromResourceAddr)
fromProviderAddr := fromResource.ProviderConfig
@ -385,7 +372,7 @@ func (c *StateMvCommand) Run(args []string) int {
}
}
if dryRun {
if parsedArgs.DryRun {
if moved == 0 {
c.Ui.Output("Would have moved nothing.")
}

View file

@ -21,10 +21,9 @@ type StatePullCommand struct {
}
func (c *StatePullCommand) Run(args []string) int {
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("state pull")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
_, diags := arguments.ParseStatePull(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

View file

@ -9,7 +9,6 @@ import (
"os"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/clistate"
"github.com/hashicorp/terraform/internal/command/views"
@ -26,22 +25,15 @@ type StatePushCommand struct {
}
func (c *StatePushCommand) Run(args []string) int {
args = c.Meta.process(args)
var flagForce bool
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("state push")
cmdFlags.BoolVar(&flagForce, "force", false, "")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
parsedArgs, diags := arguments.ParseStatePush(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
args = cmdFlags.Args()
if len(args) != 1 {
c.Ui.Error("Exactly one argument expected.\n")
return cli.RunResultHelp
}
c.Meta.stateLock = parsedArgs.StateLock
c.Meta.stateLockTimeout = parsedArgs.StateLockTimeout
c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
@ -51,8 +43,8 @@ func (c *StatePushCommand) Run(args []string) int {
// Determine our reader for the input state. This is the filepath
// or stdin if "-" is given.
var r io.Reader = os.Stdin
if args[0] != "-" {
f, err := os.Open(args[0])
if parsedArgs.Path != "-" {
f, err := os.Open(parsedArgs.Path)
if err != nil {
c.Ui.Error(err.Error())
return 1
@ -71,7 +63,7 @@ func (c *StatePushCommand) Run(args []string) int {
c.Close()
}
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", parsedArgs.Path, err))
return 1
}
@ -128,7 +120,7 @@ func (c *StatePushCommand) Run(args []string) int {
}
// Import it, forcing through the lineage/serial if requested and possible.
if err := statemgr.Import(srcStateFile, stateMgr, flagForce); err != nil {
if err := statemgr.Import(srcStateFile, stateMgr, parsedArgs.Force); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1
}

View file

@ -7,7 +7,6 @@ import (
"fmt"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/clistate"
@ -27,24 +26,17 @@ type StateReplaceProviderCommand struct {
}
func (c *StateReplaceProviderCommand) Run(args []string) int {
args = c.Meta.process(args)
parsedArgs, parseDiags := arguments.ParseStateReplaceProvider(c.Meta.process(args))
if parseDiags.HasErrors() {
c.showDiagnostics(parseDiags)
return 1
}
var autoApprove bool
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("state replace-provider")
cmdFlags.BoolVar(&autoApprove, "auto-approve", false, "skip interactive approval of replacements")
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock states")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&c.statePath, "state", "", "path")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
return cli.RunResultHelp
}
args = cmdFlags.Args()
if len(args) != 2 {
c.Ui.Error("Exactly two arguments expected.\n")
return cli.RunResultHelp
}
c.backupPath = parsedArgs.BackupPath
c.Meta.stateLock = parsedArgs.StateLock
c.Meta.stateLockTimeout = parsedArgs.StateLockTimeout
c.statePath = parsedArgs.StatePath
c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
@ -54,19 +46,19 @@ func (c *StateReplaceProviderCommand) Run(args []string) int {
var diags tfdiags.Diagnostics
// Parse from/to arguments into providers
from, fromDiags := addrs.ParseProviderSourceString(args[0])
from, fromDiags := addrs.ParseProviderSourceString(parsedArgs.FromProviderAddr)
if fromDiags.HasErrors() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
fmt.Sprintf(`Invalid "from" provider %q`, args[0]),
fmt.Sprintf(`Invalid "from" provider %q`, parsedArgs.FromProviderAddr),
fromDiags.Err().Error(),
))
}
to, toDiags := addrs.ParseProviderSourceString(args[1])
to, toDiags := addrs.ParseProviderSourceString(parsedArgs.ToProviderAddr)
if toDiags.HasErrors() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
fmt.Sprintf(`Invalid "to" provider %q`, args[1]),
fmt.Sprintf(`Invalid "to" provider %q`, parsedArgs.ToProviderAddr),
toDiags.Err().Error(),
))
}
@ -144,7 +136,7 @@ func (c *StateReplaceProviderCommand) Run(args []string) int {
}
// Confirm
if !autoApprove {
if !parsedArgs.AutoApprove {
c.Ui.Output(colorize.Color(
"\n[bold]Do you want to make these changes?[reset]\n" +
"Only 'yes' will be accepted to continue.\n",

View file

@ -225,7 +225,7 @@ func TestStateReplaceProvider(t *testing.T) {
t.Fatalf("successful exit; want error")
}
if got, want := ui.ErrorWriter.String(), "Error parsing command-line flags"; !strings.Contains(got, want) {
if got, want := ui.ErrorWriter.String(), "Failed to parse command-line flags"; !strings.Contains(got, want) {
t.Fatalf("missing expected error message\nwant: %s\nfull output:\n%s", want, got)
}
})

View file

@ -7,7 +7,6 @@ import (
"fmt"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/clistate"
@ -23,24 +22,17 @@ type StateRmCommand struct {
}
func (c *StateRmCommand) Run(args []string) int {
args = c.Meta.process(args)
var dryRun bool
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("state rm")
cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run")
cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&c.statePath, "state", "", "path")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
parsedArgs, diags := arguments.ParseStateRm(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
args = cmdFlags.Args()
if len(args) < 1 {
c.Ui.Error("At least one address is required.\n")
return cli.RunResultHelp
}
c.backupPath = parsedArgs.BackupPath
c.Meta.stateLock = parsedArgs.StateLock
c.Meta.stateLockTimeout = parsedArgs.StateLockTimeout
c.statePath = parsedArgs.StatePath
c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
@ -82,8 +74,7 @@ func (c *StateRmCommand) Run(args []string) int {
// This command primarily works with resource instances, though it will
// also clean up any modules and resources left empty by actions it takes.
var addrs []addrs.AbsResourceInstance
var diags tfdiags.Diagnostics
for _, addrStr := range args {
for _, addrStr := range parsedArgs.Addrs {
moreAddrs, moreDiags := c.lookupResourceInstanceAddr(state, true, addrStr)
addrs = append(addrs, moreAddrs...)
diags = diags.Append(moreDiags)
@ -94,7 +85,7 @@ func (c *StateRmCommand) Run(args []string) int {
}
prefix := "Removed "
if dryRun {
if parsedArgs.DryRun {
prefix = "Would remove "
}
@ -103,13 +94,13 @@ func (c *StateRmCommand) Run(args []string) int {
for _, addr := range addrs {
isCount++
c.Ui.Output(prefix + addr.String())
if !dryRun {
if !parsedArgs.DryRun {
ss.ForgetResourceInstanceAll(addr)
ss.RemoveResourceIfEmpty(addr.ContainingResource())
}
}
if dryRun {
if parsedArgs.DryRun {
if isCount == 0 {
c.Ui.Output("Would have removed nothing.")
}

View file

@ -8,8 +8,6 @@ import (
"os"
"strings"
"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend/backendrun"
"github.com/hashicorp/terraform/internal/command/arguments"
@ -27,18 +25,13 @@ type StateShowCommand struct {
}
func (c *StateShowCommand) Run(args []string) int {
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("state show")
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
if err := cmdFlags.Parse(args); err != nil {
c.Streams.Eprintf("Error parsing command-line flags: %s\n", err.Error())
parsedArgs, diags := arguments.ParseStateShow(c.Meta.process(args))
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
args = cmdFlags.Args()
if len(args) != 1 {
c.Streams.Eprint("Exactly one argument expected.\n")
return cli.RunResultHelp
}
c.Meta.statePath = parsedArgs.StatePath
// Check for user-supplied plugin path
var err error
@ -66,9 +59,9 @@ func (c *StateShowCommand) Run(args []string) int {
c.ignoreRemoteVersionConflict(b)
// Check if the address can be parsed
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(parsedArgs.Address)
if addrDiags.HasErrors() {
c.Streams.Eprintln(fmt.Sprintf(errParsingAddress, args[0]))
c.Streams.Eprintln(fmt.Sprintf(errParsingAddress, parsedArgs.Address))
return 1
}

View file

@ -22,33 +22,24 @@ type TaintCommand struct {
Meta
}
func (c *TaintCommand) Run(args []string) int {
args = c.Meta.process(args)
var allowMissing bool
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("taint")
cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "allow missing")
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
func (c *TaintCommand) Run(rawArgs []string) int {
parsedArgs, parseDiags := arguments.ParseTaint(c.Meta.process(rawArgs))
if parseDiags.HasErrors() {
c.showDiagnostics(parseDiags)
return 1
}
// Copy parsed flags to Meta
c.Meta.backupPath = parsedArgs.BackupPath
c.Meta.stateLock = parsedArgs.StateLock
c.Meta.stateLockTimeout = parsedArgs.StateLockTimeout
c.Meta.statePath = parsedArgs.StatePath
c.Meta.stateOutPath = parsedArgs.StateOutPath
c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
var diags tfdiags.Diagnostics
// Require the one argument for the resource to taint
args = cmdFlags.Args()
if len(args) != 1 {
c.Ui.Error("The taint command expects exactly one argument.")
cmdFlags.Usage()
return 1
}
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(parsedArgs.Address)
diags = diags.Append(addrDiags)
if addrDiags.HasErrors() {
c.showDiagnostics(diags)
@ -117,7 +108,7 @@ func (c *TaintCommand) Run(args []string) int {
// Get the actual state structure
state := stateMgr.State()
if state.Empty() {
if allowMissing {
if parsedArgs.AllowMissing {
return c.allowMissingExit(addr)
}
@ -144,7 +135,7 @@ func (c *TaintCommand) Run(args []string) int {
rs := ss.Resource(addr.ContainingResource())
is := ss.ResourceInstance(addr)
if is == nil {
if allowMissing {
if parsedArgs.AllowMissing {
return c.allowMissingExit(addr)
}

View file

@ -22,33 +22,24 @@ type UntaintCommand struct {
Meta
}
func (c *UntaintCommand) Run(args []string) int {
args = c.Meta.process(args)
var allowMissing bool
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("untaint")
cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "allow missing")
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
func (c *UntaintCommand) Run(rawArgs []string) int {
parsedArgs, parseDiags := arguments.ParseUntaint(c.Meta.process(rawArgs))
if parseDiags.HasErrors() {
c.showDiagnostics(parseDiags)
return 1
}
// Copy parsed flags to Meta
c.Meta.backupPath = parsedArgs.BackupPath
c.Meta.stateLock = parsedArgs.StateLock
c.Meta.stateLockTimeout = parsedArgs.StateLockTimeout
c.Meta.statePath = parsedArgs.StatePath
c.Meta.stateOutPath = parsedArgs.StateOutPath
c.Meta.ignoreRemoteVersion = parsedArgs.IgnoreRemoteVersion
var diags tfdiags.Diagnostics
// Require the one argument for the resource to untaint
args = cmdFlags.Args()
if len(args) != 1 {
c.Ui.Error("The untaint command expects exactly one argument.")
cmdFlags.Usage()
return 1
}
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(parsedArgs.Address)
diags = diags.Append(addrDiags)
if addrDiags.HasErrors() {
c.showDiagnostics(diags)
@ -107,7 +98,7 @@ func (c *UntaintCommand) Run(args []string) int {
// Get the actual state structure
state := stateMgr.State()
if state.Empty() {
if allowMissing {
if parsedArgs.AllowMissing {
return c.allowMissingExit(addr)
}
@ -126,7 +117,7 @@ func (c *UntaintCommand) Run(args []string) int {
rs := ss.Resource(addr.ContainingResource())
is := ss.ResourceInstance(addr)
if is == nil {
if allowMissing {
if parsedArgs.AllowMissing {
return c.allowMissingExit(addr)
}