mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-18 18:29:44 -05:00
refactor state-push command argument parsing
This commit is contained in:
parent
b9f6f14003
commit
442126553b
3 changed files with 234 additions and 18 deletions
69
internal/command/arguments/state_push.go
Normal file
69
internal/command/arguments/state_push.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// 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{
|
||||
StateLock: true,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
155
internal/command/arguments/state_push_test.go
Normal file
155
internal/command/arguments/state_push_test.go
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// 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{
|
||||
Force: false,
|
||||
StateLock: true,
|
||||
StateLockTimeout: 0,
|
||||
IgnoreRemoteVersion: false,
|
||||
Path: "replace.tfstate",
|
||||
},
|
||||
},
|
||||
"stdin": {
|
||||
[]string{"-"},
|
||||
&StatePush{
|
||||
Force: false,
|
||||
StateLock: true,
|
||||
StateLockTimeout: 0,
|
||||
IgnoreRemoteVersion: false,
|
||||
Path: "-",
|
||||
},
|
||||
},
|
||||
"force": {
|
||||
[]string{"-force", "replace.tfstate"},
|
||||
&StatePush{
|
||||
Force: true,
|
||||
StateLock: true,
|
||||
StateLockTimeout: 0,
|
||||
IgnoreRemoteVersion: false,
|
||||
Path: "replace.tfstate",
|
||||
},
|
||||
},
|
||||
"lock disabled": {
|
||||
[]string{"-lock=false", "replace.tfstate"},
|
||||
&StatePush{
|
||||
Force: false,
|
||||
StateLock: false,
|
||||
StateLockTimeout: 0,
|
||||
IgnoreRemoteVersion: false,
|
||||
Path: "replace.tfstate",
|
||||
},
|
||||
},
|
||||
"lock timeout": {
|
||||
[]string{"-lock-timeout=5s", "replace.tfstate"},
|
||||
&StatePush{
|
||||
Force: false,
|
||||
StateLock: true,
|
||||
StateLockTimeout: 5 * time.Second,
|
||||
IgnoreRemoteVersion: false,
|
||||
Path: "replace.tfstate",
|
||||
},
|
||||
},
|
||||
"ignore remote version": {
|
||||
[]string{"-ignore-remote-version", "replace.tfstate"},
|
||||
&StatePush{
|
||||
Force: false,
|
||||
StateLock: true,
|
||||
StateLockTimeout: 0,
|
||||
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,
|
||||
Path: "",
|
||||
},
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue