add -list-primary to `consul keyring` command (#8692)
* add -list-primary * add docs * use builder * fix multiple actions
This commit is contained in:
parent
e2223b84b8
commit
6467eb08dd
|
@ -3,6 +3,7 @@ package keyring
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/agent"
|
||||
consulapi "github.com/hashicorp/consul/api"
|
||||
|
@ -23,12 +24,13 @@ type cmd struct {
|
|||
help string
|
||||
|
||||
// flags
|
||||
installKey string
|
||||
useKey string
|
||||
removeKey string
|
||||
listKeys bool
|
||||
relay int
|
||||
local bool
|
||||
installKey string
|
||||
useKey string
|
||||
removeKey string
|
||||
listKeys bool
|
||||
listPrimaryKeys bool
|
||||
relay int
|
||||
local bool
|
||||
}
|
||||
|
||||
func (c *cmd) init() {
|
||||
|
@ -45,6 +47,8 @@ func (c *cmd) init() {
|
|||
"performed on keys which are not currently the primary key.")
|
||||
c.flags.BoolVar(&c.listKeys, "list", false,
|
||||
"List all keys currently in use within the cluster.")
|
||||
c.flags.BoolVar(&c.listPrimaryKeys, "list-primary", false,
|
||||
"List all primary keys currently in use within the cluster.")
|
||||
c.flags.IntVar(&c.relay, "relay-factor", 0,
|
||||
"Setting this to a non-zero value will cause nodes to relay their response "+
|
||||
"to the operation through this many randomly-chosen other nodes in the "+
|
||||
|
@ -58,6 +62,22 @@ func (c *cmd) init() {
|
|||
c.help = flags.Usage(help, c.flags)
|
||||
}
|
||||
|
||||
func numberActions(listKeys, listPrimaryKeys bool, installKey, useKey, removeKey string) int {
|
||||
count := 0
|
||||
if listKeys {
|
||||
count++
|
||||
}
|
||||
if listPrimaryKeys {
|
||||
count++
|
||||
}
|
||||
for _, arg := range []string{installKey, useKey, removeKey} {
|
||||
if len(arg) > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (c *cmd) Run(args []string) int {
|
||||
if err := c.flags.Parse(args); err != nil {
|
||||
return 1
|
||||
|
@ -70,21 +90,15 @@ func (c *cmd) Run(args []string) int {
|
|||
Ui: c.UI,
|
||||
}
|
||||
|
||||
// Only accept a single argument
|
||||
found := c.listKeys
|
||||
for _, arg := range []string{c.installKey, c.useKey, c.removeKey} {
|
||||
if found && len(arg) > 0 {
|
||||
c.UI.Error("Only a single action is allowed")
|
||||
return 1
|
||||
}
|
||||
found = found || len(arg) > 0
|
||||
}
|
||||
|
||||
// Fail fast if no actionable args were passed
|
||||
if !found {
|
||||
num := numberActions(c.listKeys, c.listPrimaryKeys, c.installKey, c.useKey, c.removeKey)
|
||||
if num == 0 {
|
||||
c.UI.Error(c.Help())
|
||||
return 1
|
||||
}
|
||||
if num > 1 {
|
||||
c.UI.Error("Only a single action is allowed")
|
||||
return 1
|
||||
}
|
||||
|
||||
// Validate the relay factor
|
||||
relayFactor, err := agent.ParseRelayFactor(c.relay)
|
||||
|
@ -114,7 +128,22 @@ func (c *cmd) Run(args []string) int {
|
|||
c.UI.Error(fmt.Sprintf("error: %s", err))
|
||||
return 1
|
||||
}
|
||||
c.handleList(responses)
|
||||
for _, response := range responses {
|
||||
c.UI.Output(formatResponse(response, response.Keys))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
if c.listPrimaryKeys {
|
||||
c.UI.Info("Gathering installed primary encryption keys...")
|
||||
responses, err := client.Operator().KeyringList(&consulapi.QueryOptions{RelayFactor: relayFactor, LocalOnly: c.local})
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("error: %s", err))
|
||||
return 1
|
||||
}
|
||||
for _, response := range responses {
|
||||
c.UI.Output(formatResponse(response, response.PrimaryKeys))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -153,27 +182,40 @@ func (c *cmd) Run(args []string) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (c *cmd) handleList(responses []*consulapi.KeyringResponse) {
|
||||
for _, response := range responses {
|
||||
pool := response.Datacenter + " (LAN)"
|
||||
if response.Segment != "" {
|
||||
pool += fmt.Sprintf(" [%s]", response.Segment)
|
||||
}
|
||||
if response.WAN {
|
||||
pool = "WAN"
|
||||
}
|
||||
func formatResponse(response *consulapi.KeyringResponse, keys map[string]int) string {
|
||||
b := new(strings.Builder)
|
||||
b.WriteString("\n")
|
||||
b.WriteString(poolName(response.Datacenter, response.WAN, response.Segment))
|
||||
b.WriteString(formatMessages(response.Messages))
|
||||
b.WriteString(formatKeys(keys, response.NumNodes))
|
||||
return strings.TrimRight(b.String(), "\n")
|
||||
}
|
||||
|
||||
c.UI.Output("")
|
||||
c.UI.Output(pool + ":")
|
||||
|
||||
for from, msg := range response.Messages {
|
||||
c.UI.Output(fmt.Sprintf(" ===> %s: %s", from, msg))
|
||||
}
|
||||
|
||||
for key, num := range response.Keys {
|
||||
c.UI.Output(fmt.Sprintf(" %s [%d/%d]", key, num, response.NumNodes))
|
||||
}
|
||||
func poolName(dc string, wan bool, segment string) string {
|
||||
pool := fmt.Sprintf("%s (LAN)", dc)
|
||||
if wan {
|
||||
pool = "WAN"
|
||||
}
|
||||
if segment != "" {
|
||||
segment = fmt.Sprintf(" [%s]", segment)
|
||||
}
|
||||
return fmt.Sprintf("%s%s:\n", pool, segment)
|
||||
}
|
||||
|
||||
func formatMessages(messages map[string]string) string {
|
||||
b := new(strings.Builder)
|
||||
for from, msg := range messages {
|
||||
b.WriteString(fmt.Sprintf(" ===> %s: %s\n", from, msg))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatKeys(keys map[string]int, total int) string {
|
||||
b := new(strings.Builder)
|
||||
for key, num := range keys {
|
||||
b.WriteString(fmt.Sprintf(" %s [%d/%d]\n", key, num, total))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c *cmd) Synopsis() string {
|
||||
|
|
|
@ -5,7 +5,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/agent"
|
||||
consulapi "github.com/hashicorp/consul/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestKeyringCommand_noTabs(t *testing.T) {
|
||||
|
@ -51,6 +53,16 @@ func TestKeyringCommand(t *testing.T) {
|
|||
|
||||
// Rotate to key2, remove key1
|
||||
useKey(t, a1.HTTPAddr(), key2)
|
||||
|
||||
// New key should be present
|
||||
out = listPrimaryKeys(t, a1.HTTPAddr())
|
||||
if strings.Contains(out, key1) {
|
||||
t.Fatalf("bad: %#v", out)
|
||||
}
|
||||
if !strings.Contains(out, key2) {
|
||||
t.Fatalf("bad: %#v", out)
|
||||
}
|
||||
|
||||
removeKey(t, a1.HTTPAddr(), key1)
|
||||
|
||||
// Only key2 is present now
|
||||
|
@ -132,6 +144,19 @@ func listKeys(t *testing.T, addr string) string {
|
|||
return ui.OutputWriter.String()
|
||||
}
|
||||
|
||||
func listPrimaryKeys(t *testing.T, addr string) string {
|
||||
ui := cli.NewMockUi()
|
||||
c := New(ui)
|
||||
|
||||
args := []string{"-list-primary", "-http-addr=" + addr}
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
return ui.OutputWriter.String()
|
||||
}
|
||||
|
||||
func installKey(t *testing.T, addr string, key string) {
|
||||
ui := cli.NewMockUi()
|
||||
c := New(ui)
|
||||
|
@ -164,3 +189,42 @@ func removeKey(t *testing.T, addr string, key string) {
|
|||
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyringCommand_poolName(t *testing.T) {
|
||||
require.Equal(t, "dc1 (LAN):\n", poolName("dc1", false, ""))
|
||||
require.Equal(t, "dc1 (LAN) [segment1]:\n", poolName("dc1", false, "segment1"))
|
||||
require.Equal(t, "WAN:\n", poolName("dc1", true, ""))
|
||||
}
|
||||
|
||||
func TestKeyringCommand_formatKeys(t *testing.T) {
|
||||
require.Equal(t, "", formatKeys(map[string]int{}, 0))
|
||||
keys := formatKeys(map[string]int{"key1": 1, "key2": 2}, 2)
|
||||
require.Contains(t, keys, " key1 [1/2]\n")
|
||||
require.Contains(t, keys, " key2 [2/2]\n")
|
||||
}
|
||||
|
||||
func TestKeyringCommand_formatMessages(t *testing.T) {
|
||||
require.Equal(t, "", formatMessages(map[string]string{}))
|
||||
messages := formatMessages(map[string]string{"n1": "hello", "n2": "world"})
|
||||
require.Contains(t, messages, " ===> n1: hello\n")
|
||||
require.Contains(t, messages, " ===> n2: world\n")
|
||||
}
|
||||
|
||||
func TestKeyringCommand_formatResponse(t *testing.T) {
|
||||
response := &consulapi.KeyringResponse{Datacenter: "dc1", NumNodes: 1}
|
||||
keys := map[string]int{"key1": 1}
|
||||
require.Equal(t, "\ndc1 (LAN):\n key1 [1/1]", formatResponse(response, keys))
|
||||
|
||||
response = &consulapi.KeyringResponse{WAN: true, Datacenter: "dc1", NumNodes: 1}
|
||||
keys = map[string]int{"key1": 1}
|
||||
require.Equal(t, "\nWAN:\n key1 [1/1]", formatResponse(response, keys))
|
||||
}
|
||||
|
||||
func TestKeyringCommand_numActions(t *testing.T) {
|
||||
require.Equal(t, 0, numberActions(false, false, "", "", ""))
|
||||
require.Equal(t, 1, numberActions(true, false, "", "", ""))
|
||||
require.Equal(t, 1, numberActions(false, true, "", "", ""))
|
||||
require.Equal(t, 1, numberActions(false, false, "1", "", ""))
|
||||
require.Equal(t, 2, numberActions(true, false, "1", "", ""))
|
||||
require.Equal(t, 2, numberActions(false, false, "1", "1", ""))
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ Only one actionable argument may be specified per run, including `-list`,
|
|||
|
||||
- `-list` - List all keys currently in use within the cluster.
|
||||
|
||||
- `-list-primary` - List all primary keys currently in use within the cluster.
|
||||
|
||||
- `-install` - Install a new encryption key. This will broadcast the new key to
|
||||
all members in the cluster.
|
||||
|
||||
|
|
Loading…
Reference in New Issue