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 (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent"
|
"github.com/hashicorp/consul/agent"
|
||||||
consulapi "github.com/hashicorp/consul/api"
|
consulapi "github.com/hashicorp/consul/api"
|
||||||
|
@ -23,12 +24,13 @@ type cmd struct {
|
||||||
help string
|
help string
|
||||||
|
|
||||||
// flags
|
// flags
|
||||||
installKey string
|
installKey string
|
||||||
useKey string
|
useKey string
|
||||||
removeKey string
|
removeKey string
|
||||||
listKeys bool
|
listKeys bool
|
||||||
relay int
|
listPrimaryKeys bool
|
||||||
local bool
|
relay int
|
||||||
|
local bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cmd) init() {
|
func (c *cmd) init() {
|
||||||
|
@ -45,6 +47,8 @@ func (c *cmd) init() {
|
||||||
"performed on keys which are not currently the primary key.")
|
"performed on keys which are not currently the primary key.")
|
||||||
c.flags.BoolVar(&c.listKeys, "list", false,
|
c.flags.BoolVar(&c.listKeys, "list", false,
|
||||||
"List all keys currently in use within the cluster.")
|
"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,
|
c.flags.IntVar(&c.relay, "relay-factor", 0,
|
||||||
"Setting this to a non-zero value will cause nodes to relay their response "+
|
"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 "+
|
"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)
|
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 {
|
func (c *cmd) Run(args []string) int {
|
||||||
if err := c.flags.Parse(args); err != nil {
|
if err := c.flags.Parse(args); err != nil {
|
||||||
return 1
|
return 1
|
||||||
|
@ -70,21 +90,15 @@ func (c *cmd) Run(args []string) int {
|
||||||
Ui: c.UI,
|
Ui: c.UI,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only accept a single argument
|
num := numberActions(c.listKeys, c.listPrimaryKeys, c.installKey, c.useKey, c.removeKey)
|
||||||
found := c.listKeys
|
if num == 0 {
|
||||||
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 {
|
|
||||||
c.UI.Error(c.Help())
|
c.UI.Error(c.Help())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
if num > 1 {
|
||||||
|
c.UI.Error("Only a single action is allowed")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
// Validate the relay factor
|
// Validate the relay factor
|
||||||
relayFactor, err := agent.ParseRelayFactor(c.relay)
|
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))
|
c.UI.Error(fmt.Sprintf("error: %s", err))
|
||||||
return 1
|
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
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,27 +182,40 @@ func (c *cmd) Run(args []string) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cmd) handleList(responses []*consulapi.KeyringResponse) {
|
func formatResponse(response *consulapi.KeyringResponse, keys map[string]int) string {
|
||||||
for _, response := range responses {
|
b := new(strings.Builder)
|
||||||
pool := response.Datacenter + " (LAN)"
|
b.WriteString("\n")
|
||||||
if response.Segment != "" {
|
b.WriteString(poolName(response.Datacenter, response.WAN, response.Segment))
|
||||||
pool += fmt.Sprintf(" [%s]", response.Segment)
|
b.WriteString(formatMessages(response.Messages))
|
||||||
}
|
b.WriteString(formatKeys(keys, response.NumNodes))
|
||||||
if response.WAN {
|
return strings.TrimRight(b.String(), "\n")
|
||||||
pool = "WAN"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
c.UI.Output("")
|
func poolName(dc string, wan bool, segment string) string {
|
||||||
c.UI.Output(pool + ":")
|
pool := fmt.Sprintf("%s (LAN)", dc)
|
||||||
|
if wan {
|
||||||
for from, msg := range response.Messages {
|
pool = "WAN"
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 {
|
func (c *cmd) Synopsis() string {
|
||||||
|
|
|
@ -5,7 +5,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent"
|
"github.com/hashicorp/consul/agent"
|
||||||
|
consulapi "github.com/hashicorp/consul/api"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestKeyringCommand_noTabs(t *testing.T) {
|
func TestKeyringCommand_noTabs(t *testing.T) {
|
||||||
|
@ -51,6 +53,16 @@ func TestKeyringCommand(t *testing.T) {
|
||||||
|
|
||||||
// Rotate to key2, remove key1
|
// Rotate to key2, remove key1
|
||||||
useKey(t, a1.HTTPAddr(), key2)
|
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)
|
removeKey(t, a1.HTTPAddr(), key1)
|
||||||
|
|
||||||
// Only key2 is present now
|
// Only key2 is present now
|
||||||
|
@ -132,6 +144,19 @@ func listKeys(t *testing.T, addr string) string {
|
||||||
return ui.OutputWriter.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) {
|
func installKey(t *testing.T, addr string, key string) {
|
||||||
ui := cli.NewMockUi()
|
ui := cli.NewMockUi()
|
||||||
c := New(ui)
|
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())
|
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` - 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
|
- `-install` - Install a new encryption key. This will broadcast the new key to
|
||||||
all members in the cluster.
|
all members in the cluster.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue