mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-18 18:29:44 -05:00
Merge pull request #38186 from hashicorp/modernize-commands-to-use-arguments
Refactoring: Modernize various commands to use arguments
This commit is contained in:
commit
cbc30de3ed
44 changed files with 2958 additions and 327 deletions
108
internal/command/arguments/import.go
Normal file
108
internal/command/arguments/import.go
Normal 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
|
||||
}
|
||||
271
internal/command/arguments/import_test.go
Normal file
271
internal/command/arguments/import_test.go
Normal 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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
42
internal/command/arguments/providers.go
Normal file
42
internal/command/arguments/providers.go
Normal 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
|
||||
}
|
||||
52
internal/command/arguments/providers_lock.go
Normal file
52
internal/command/arguments/providers_lock.go
Normal 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
|
||||
}
|
||||
124
internal/command/arguments/providers_lock_test.go
Normal file
124
internal/command/arguments/providers_lock_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
48
internal/command/arguments/providers_mirror.go
Normal file
48
internal/command/arguments/providers_mirror.go
Normal 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
|
||||
}
|
||||
113
internal/command/arguments/providers_mirror_test.go
Normal file
113
internal/command/arguments/providers_mirror_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
50
internal/command/arguments/providers_schema.go
Normal file
50
internal/command/arguments/providers_schema.go
Normal 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
|
||||
}
|
||||
96
internal/command/arguments/providers_schema_test.go
Normal file
96
internal/command/arguments/providers_schema_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
88
internal/command/arguments/providers_test.go
Normal file
88
internal/command/arguments/providers_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
49
internal/command/arguments/state_list.go
Normal file
49
internal/command/arguments/state_list.go
Normal 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
|
||||
}
|
||||
104
internal/command/arguments/state_list_test.go
Normal file
104
internal/command/arguments/state_list_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
91
internal/command/arguments/state_mv.go
Normal file
91
internal/command/arguments/state_mv.go
Normal 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
|
||||
}
|
||||
165
internal/command/arguments/state_mv_test.go
Normal file
165
internal/command/arguments/state_mv_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
32
internal/command/arguments/state_pull.go
Normal file
32
internal/command/arguments/state_pull.go
Normal 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
|
||||
}
|
||||
64
internal/command/arguments/state_pull_test.go
Normal file
64
internal/command/arguments/state_pull_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
67
internal/command/arguments/state_push.go
Normal file
67
internal/command/arguments/state_push.go
Normal 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
|
||||
}
|
||||
138
internal/command/arguments/state_push_test.go
Normal file
138
internal/command/arguments/state_push_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
79
internal/command/arguments/state_replace_provider.go
Normal file
79
internal/command/arguments/state_replace_provider.go
Normal 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
|
||||
}
|
||||
135
internal/command/arguments/state_replace_provider_test.go
Normal file
135
internal/command/arguments/state_replace_provider_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
74
internal/command/arguments/state_rm.go
Normal file
74
internal/command/arguments/state_rm.go
Normal 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
|
||||
}
|
||||
118
internal/command/arguments/state_rm_test.go
Normal file
118
internal/command/arguments/state_rm_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
54
internal/command/arguments/state_show.go
Normal file
54
internal/command/arguments/state_show.go
Normal 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
|
||||
}
|
||||
102
internal/command/arguments/state_show_test.go
Normal file
102
internal/command/arguments/state_show_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
86
internal/command/arguments/taint.go
Normal file
86
internal/command/arguments/taint.go
Normal 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
|
||||
}
|
||||
177
internal/command/arguments/taint_test.go
Normal file
177
internal/command/arguments/taint_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
86
internal/command/arguments/untaint.go
Normal file
86
internal/command/arguments/untaint.go
Normal 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
|
||||
}
|
||||
177
internal/command/arguments/untaint_test.go
Normal file
177
internal/command/arguments/untaint_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{}{}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue