mirror of https://github.com/hashicorp/terraform
parent
492e98ab75
commit
6b83486498
@ -0,0 +1,88 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// 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{
|
||||
StateLock: true,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// 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",
|
||||
StateLock: false,
|
||||
},
|
||||
},
|
||||
"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",
|
||||
StateLock: false,
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// 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{
|
||||
StateLock: true,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// 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",
|
||||
StateLock: false,
|
||||
},
|
||||
},
|
||||
"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",
|
||||
StateLock: false,
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue