Update status command

This commit is contained in:
Seth Vargo 2017-09-05 00:04:39 -04:00
parent 1d91e96c8e
commit 530144f7f7
No known key found for this signature in database
GPG Key ID: C921994F9C27E0FF
2 changed files with 167 additions and 64 deletions

View File

@ -5,33 +5,79 @@ import (
"strings"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/meta"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
// StatusCommand is a Command that outputs the status of whether
// Vault is sealed or not as well as HA information.
// Ensure we are implementing the right interfaces.
var _ cli.Command = (*StatusCommand)(nil)
var _ cli.CommandAutocomplete = (*StatusCommand)(nil)
// StatusCommand is a Command that outputs the status of whether Vault is sealed
// or not as well as HA information.
type StatusCommand struct {
meta.Meta
*BaseCommand
}
func (c *StatusCommand) Synopsis() string {
return "Prints seal and HA status"
}
func (c *StatusCommand) Help() string {
helpText := `
Usage: vault status [options]
Prints the current state of Vault including whether it is sealed and if HA
mode is enabled. This command prints regardless of whether the Vault is
sealed.
The exit code reflects the seal status:
- 0 - unsealed
- 1 - error
- 2 - sealed
` + c.Flags().Help()
return strings.TrimSpace(helpText)
}
func (c *StatusCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
}
func (c *StatusCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (c *StatusCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *StatusCommand) Run(args []string) int {
flags := c.Meta.FlagSet("status", meta.FlagSetDefault)
flags.Usage = func() { c.Ui.Error(c.Help()) }
if err := flags.Parse(args); err != nil {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
if len(args) > 0 {
c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", len(args)))
return 1
}
client, err := c.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error initializing client: %s", err))
c.UI.Error(err.Error())
// We return 2 everywhere else, but 2 is reserved for "sealed" here
return 1
}
sealStatus, err := client.Sys().SealStatus()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error checking seal status: %s", err))
c.UI.Error(fmt.Sprintf("Error checking seal status: %s", err))
return 1
}
@ -53,33 +99,32 @@ func (c *StatusCommand) Run(args []string) int {
outStr = fmt.Sprintf("%s\nCluster Name: %s\nCluster ID: %s", outStr, sealStatus.ClusterName, sealStatus.ClusterID)
}
c.Ui.Output(outStr)
c.UI.Output(outStr)
// Mask the 'Vault is sealed' error, since this means HA is enabled,
// but that we cannot query for the leader since we are sealed.
// Mask the 'Vault is sealed' error, since this means HA is enabled, but that
// we cannot query for the leader since we are sealed.
leaderStatus, err := client.Sys().Leader()
if err != nil && strings.Contains(err.Error(), "Vault is sealed") {
leaderStatus = &api.LeaderResponse{HAEnabled: true}
err = nil
}
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error checking leader status: %s", err))
c.UI.Error(fmt.Sprintf("Error checking leader status: %s", err))
return 1
}
// Output if HA is enabled
c.Ui.Output("")
c.Ui.Output(fmt.Sprintf("High-Availability Enabled: %v", leaderStatus.HAEnabled))
c.UI.Output("")
c.UI.Output(fmt.Sprintf("High-Availability Enabled: %v", leaderStatus.HAEnabled))
if leaderStatus.HAEnabled {
if sealStatus.Sealed {
c.Ui.Output("\tMode: sealed")
c.UI.Output("\tMode: sealed")
} else {
mode := "standby"
if leaderStatus.IsSelf {
mode = "active"
}
c.Ui.Output(fmt.Sprintf("\tMode: %s", mode))
c.UI.Output(fmt.Sprintf("\tMode: %s", mode))
if leaderStatus.LeaderAddress == "" {
leaderStatus.LeaderAddress = "<none>"
@ -87,31 +132,13 @@ func (c *StatusCommand) Run(args []string) int {
if leaderStatus.LeaderClusterAddress == "" {
leaderStatus.LeaderClusterAddress = "<none>"
}
c.Ui.Output(fmt.Sprintf("\tLeader Cluster Address: %s", leaderStatus.LeaderClusterAddress))
c.UI.Output(fmt.Sprintf("\tLeader Cluster Address: %s", leaderStatus.LeaderClusterAddress))
}
}
if sealStatus.Sealed {
return 2
} else {
return 0
}
}
func (c *StatusCommand) Synopsis() string {
return "Outputs status of whether Vault is sealed and if HA mode is enabled"
}
func (c *StatusCommand) Help() string {
helpText := `
Usage: vault status [options]
Outputs the state of the Vault, sealed or unsealed and if HA is enabled.
This command outputs whether or not the Vault is sealed. The exit
code also reflects the seal status (0 unsealed, 2 sealed, 1 error).
General Options:
` + meta.GeneralOptionsUsage()
return strings.TrimSpace(helpText)
return 0
}

View File

@ -1,39 +1,115 @@
package command
import (
"strings"
"testing"
"github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/meta"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli"
)
func TestStatus(t *testing.T) {
ui := new(cli.MockUi)
c := &StatusCommand{
Meta: meta.Meta{
Ui: ui,
func testStatusCommand(tb testing.TB) (*cli.MockUi, *StatusCommand) {
tb.Helper()
ui := cli.NewMockUi()
return ui, &StatusCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
}
func TestStatusCommand_Run(t *testing.T) {
t.Parallel()
cases := []struct {
name string
args []string
sealed bool
out string
code int
}{
{
"unsealed",
nil,
false,
"Sealed: false",
0,
},
{
"sealed",
nil,
true,
"Sealed: true",
2,
},
{
"args",
[]string{"foo"},
false,
"Too many arguments",
1,
},
}
core := vault.TestCore(t)
keys, _ := vault.TestCoreInit(t, core)
ln, addr := http.TestServer(t, core)
defer ln.Close()
t.Run("validations", func(t *testing.T) {
t.Parallel()
args := []string{"-address", addr}
if code := c.Run(args); code != 2 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
for _, tc := range cases {
tc := tc
for _, key := range keys {
if _, err := core.Unseal(key); err != nil {
t.Fatalf("err: %s", err)
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
if tc.sealed {
if err := client.Sys().Seal(); err != nil {
t.Fatal(err)
}
}
ui, cmd := testStatusCommand(t)
cmd.client = client
code := cmd.Run(tc.args)
if code != tc.code {
t.Errorf("expected %d to be %d", code, tc.code)
}
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, tc.out) {
t.Errorf("expected %q to contain %q", combined, tc.out)
}
})
}
}
})
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
t.Run("communication_failure", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testStatusCommand(t)
cmd.client = client
code := cmd.Run([]string{})
if exp := 1; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error checking seal status: "
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
})
t.Run("no_tabs", func(t *testing.T) {
t.Parallel()
_, cmd := testStatusCommand(t)
assertNoTabs(t, cmd)
})
}