Browse Source

Convert keyring command to use base.Command

pull/2723/head
Kyle Havlovitz 8 years ago
parent
commit
abdf1fbab3
No known key found for this signature in database
GPG Key ID: 8A5E6B173056AD6C
  1. 16
      api/api.go
  2. 9
      command/agent/operator_endpoint.go
  3. 150
      command/keyring.go
  4. 54
      command/keyring_test.go
  5. 15
      commands.go
  6. 12
      website/source/docs/commands/keyring.html.markdown.erb

16
api/api.go

@ -79,6 +79,11 @@ type QueryOptions struct {
// metadata key/value pairs. Currently, only one key/value pair can
// be provided for filtering.
NodeMeta map[string]string
// RelayFactor is used in keyring operations to cause reponses to be
// relayed back to the sender through N other random nodes. Must be
// a value from 0 to 5 (inclusive).
RelayFactor uint8
}
// WriteOptions are used to parameterize a write
@ -90,6 +95,11 @@ type WriteOptions struct {
// Token is used to provide a per-request ACL token
// which overrides the agent's default token.
Token string
// RelayFactor is used in keyring operations to cause reponses to be
// relayed back to the sender through N other random nodes. Must be
// a value from 0 to 5 (inclusive).
RelayFactor uint8
}
// QueryMeta is used to return meta data about a query
@ -396,6 +406,9 @@ func (r *request) setQueryOptions(q *QueryOptions) {
r.params.Add("node-meta", key+":"+value)
}
}
if q.RelayFactor != 0 {
r.params.Set("relay-factor", strconv.Itoa(int(q.RelayFactor)))
}
}
// durToMsec converts a duration to a millisecond specified string. If the
@ -437,6 +450,9 @@ func (r *request) setWriteOptions(q *WriteOptions) {
if q.Token != "" {
r.header.Set("X-Consul-Token", q.Token)
}
if q.RelayFactor != 0 {
r.header.Set("relay-factor", strconv.Itoa(int(q.RelayFactor)))
}
}
// toHTTP converts the request to an HTTP request

9
command/agent/operator_endpoint.go

@ -154,7 +154,14 @@ func keyringErrorsOrNil(responses []*structs.KeyringResponse) error {
var errs error
for _, response := range responses {
if response.Error != "" {
errs = multierror.Append(errs, fmt.Errorf(response.Error))
pool := response.Datacenter + " (LAN)"
if response.WAN {
pool = "WAN"
}
errs = multierror.Append(errs, fmt.Errorf("%s error: %s", pool, response.Error))
for key, message := range response.Messages {
errs = multierror.Append(errs, fmt.Errorf("%s: %s", key, message))
}
}
}
return errs

150
command/keyring.go

@ -1,37 +1,46 @@
package command
import (
"flag"
"fmt"
"strings"
consulapi "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/agent"
"github.com/hashicorp/consul/command/base"
"github.com/mitchellh/cli"
)
// KeyringCommand is a Command implementation that handles querying, installing,
// and removing gossip encryption keys from a keyring.
type KeyringCommand struct {
Ui cli.Ui
base.Command
}
func (c *KeyringCommand) Run(args []string) int {
var installKey, useKey, removeKey, token string
var installKey, useKey, removeKey string
var listKeys bool
var relay int
cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError)
cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
cmdFlags.StringVar(&installKey, "install", "", "install key")
cmdFlags.StringVar(&useKey, "use", "", "use key")
cmdFlags.StringVar(&removeKey, "remove", "", "remove key")
cmdFlags.BoolVar(&listKeys, "list", false, "list keys")
cmdFlags.StringVar(&token, "token", "", "acl token")
cmdFlags.IntVar(&relay, "relay-factor", 0, "relay factor")
rpcAddr := RPCAddrFlag(cmdFlags)
if err := cmdFlags.Parse(args); err != nil {
f := c.Command.NewFlagSet(c)
f.StringVar(&installKey, "install", "",
"Install a new encryption key. This will broadcast the new key to "+
"all members in the cluster.")
f.StringVar(&useKey, "use", "",
"Change the primary encryption key, which is used to encrypt "+
"messages. The key must already be installed before this operation "+
"can succeed.")
f.StringVar(&removeKey, "remove", "",
"Remove the given key from the cluster. This operation may only be "+
"performed on keys which are not currently the primary key.")
f.BoolVar(&listKeys, "list", false,
"List all keys currently in use within the cluster.")
f.IntVar(&relay, "relay-factor", 0,
"Added in Consul 0.7.4, 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 cluster. The maximum allowed value is 5.")
if err := c.Command.Parse(args); err != nil {
return 1
}
@ -66,124 +75,69 @@ func (c *KeyringCommand) Run(args []string) int {
}
// All other operations will require a client connection
client, err := RPCClient(*rpcAddr)
client, err := c.Command.HTTPClient()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
}
defer client.Close()
if listKeys {
c.Ui.Info("Gathering installed encryption keys...")
r, err := client.ListKeys(token, relayFactor)
responses, err := client.Operator().KeyringList(&consulapi.QueryOptions{RelayFactor: relayFactor})
if err != nil {
c.Ui.Error(fmt.Sprintf("error: %s", err))
return 1
}
if rval := c.handleResponse(r.Info, r.Messages); rval != 0 {
return rval
}
c.handleList(r.Info, r.Keys)
c.handleList(responses)
return 0
}
opts := &consulapi.WriteOptions{RelayFactor: relayFactor}
if installKey != "" {
c.Ui.Info("Installing new gossip encryption key...")
r, err := client.InstallKey(installKey, token, relayFactor)
err := client.Operator().KeyringInstall(installKey, opts)
if err != nil {
c.Ui.Error(fmt.Sprintf("error: %s", err))
return 1
}
return c.handleResponse(r.Info, r.Messages)
return 0
}
if useKey != "" {
c.Ui.Info("Changing primary gossip encryption key...")
r, err := client.UseKey(useKey, token, relayFactor)
err := client.Operator().KeyringUse(useKey, opts)
if err != nil {
c.Ui.Error(fmt.Sprintf("error: %s", err))
return 1
}
return c.handleResponse(r.Info, r.Messages)
return 0
}
if removeKey != "" {
c.Ui.Info("Removing gossip encryption key...")
r, err := client.RemoveKey(removeKey, token, relayFactor)
err := client.Operator().KeyringRemove(removeKey, opts)
if err != nil {
c.Ui.Error(fmt.Sprintf("error: %s", err))
return 1
}
return c.handleResponse(r.Info, r.Messages)
return 0
}
// Should never make it here
return 0
}
func (c *KeyringCommand) handleResponse(
info []agent.KeyringInfo,
messages []agent.KeyringMessage) int {
var rval int
for _, i := range info {
if i.Error != "" {
pool := i.Pool
if pool != "WAN" {
pool = i.Datacenter + " (LAN)"
}
c.Ui.Error("")
c.Ui.Error(fmt.Sprintf("%s error: %s", pool, i.Error))
for _, msg := range messages {
if msg.Datacenter != i.Datacenter || msg.Pool != i.Pool {
continue
}
c.Ui.Error(fmt.Sprintf(" %s: %s", msg.Node, msg.Message))
}
rval = 1
func (c *KeyringCommand) handleList(responses []*consulapi.KeyringResponse) {
for _, response := range responses {
pool := response.Datacenter + " (LAN)"
if response.WAN {
pool = "WAN"
}
}
if rval == 0 {
c.Ui.Info("Done!")
}
return rval
}
func (c *KeyringCommand) handleList(
info []agent.KeyringInfo,
keys []agent.KeyringEntry) {
installed := make(map[string]map[string][]int)
for _, key := range keys {
var nodes int
for _, i := range info {
if i.Datacenter == key.Datacenter && i.Pool == key.Pool {
nodes = i.NumNodes
}
}
pool := key.Pool
if pool != "WAN" {
pool = key.Datacenter + " (LAN)"
}
if _, ok := installed[pool]; !ok {
installed[pool] = map[string][]int{key.Key: []int{key.Count, nodes}}
} else {
installed[pool][key.Key] = []int{key.Count, nodes}
}
}
for pool, keys := range installed {
c.Ui.Output("")
c.Ui.Output(pool + ":")
for key, num := range keys {
c.Ui.Output(fmt.Sprintf(" %s [%d/%d]", key, num[0], num[1]))
for key, num := range response.Keys {
c.Ui.Output(fmt.Sprintf(" %s [%d/%d]", key, num, response.NumNodes))
}
}
}
@ -205,26 +159,8 @@ Usage: consul keyring [options]
are no errors. If any node fails to reply or reports failure, the exit code
will be 1.
Options:
-install=<key> Install a new encryption key. This will broadcast
the new key to all members in the cluster.
-list List all keys currently in use within the cluster.
-remove=<key> Remove the given key from the cluster. This
operation may only be performed on keys which are
not currently the primary key.
-token="" ACL token to use during requests. Defaults to that
of the agent.
-relay-factor Added in Consul 0.7.4, 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 cluster. The maximum allowed
value is 5.
-use=<key> Change the primary encryption key, which is used to
encrypt messages. The key must already be installed
before this operation can succeed.
-rpc-addr=127.0.0.1:8400 RPC address of the Consul agent.
`
` + c.Command.Help()
return strings.TrimSpace(helpText)
}

54
command/keyring_test.go

@ -5,9 +5,20 @@ import (
"testing"
"github.com/hashicorp/consul/command/agent"
"github.com/hashicorp/consul/command/base"
"github.com/mitchellh/cli"
)
func testKeyringCommand(t *testing.T) (*cli.MockUi, *KeyringCommand) {
ui := new(cli.MockUi)
return ui, &KeyringCommand{
Command: base.Command{
Ui: ui,
Flags: base.FlagSetClientHTTP,
},
}
}
func TestKeyringCommand_implements(t *testing.T) {
var _ cli.Command = &KeyringCommand{}
}
@ -23,7 +34,7 @@ func TestKeyringCommandRun(t *testing.T) {
defer a1.Shutdown()
// The LAN and WAN keyrings were initialized with key1
out := listKeys(t, a1.addr)
out := listKeys(t, a1.httpAddr)
if !strings.Contains(out, "dc1 (LAN):\n "+key1) {
t.Fatalf("bad: %#v", out)
}
@ -35,10 +46,10 @@ func TestKeyringCommandRun(t *testing.T) {
}
// Install the second key onto the keyring
installKey(t, a1.addr, key2)
installKey(t, a1.httpAddr, key2)
// Both keys should be present
out = listKeys(t, a1.addr)
out = listKeys(t, a1.httpAddr)
for _, key := range []string{key1, key2} {
if !strings.Contains(out, key) {
t.Fatalf("bad: %#v", out)
@ -46,11 +57,11 @@ func TestKeyringCommandRun(t *testing.T) {
}
// Rotate to key2, remove key1
useKey(t, a1.addr, key2)
removeKey(t, a1.addr, key1)
useKey(t, a1.httpAddr, key2)
removeKey(t, a1.httpAddr, key1)
// Only key2 is present now
out = listKeys(t, a1.addr)
out = listKeys(t, a1.httpAddr)
if !strings.Contains(out, "dc1 (LAN):\n "+key2) {
t.Fatalf("bad: %#v", out)
}
@ -63,8 +74,7 @@ func TestKeyringCommandRun(t *testing.T) {
}
func TestKeyringCommandRun_help(t *testing.T) {
ui := new(cli.MockUi)
c := &KeyringCommand{Ui: ui}
ui, c := testKeyringCommand(t)
code := c.Run(nil)
if code != 1 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
@ -77,9 +87,8 @@ func TestKeyringCommandRun_help(t *testing.T) {
}
func TestKeyringCommandRun_failedConnection(t *testing.T) {
ui := new(cli.MockUi)
c := &KeyringCommand{Ui: ui}
args := []string{"-list", "-rpc-addr=127.0.0.1:0"}
ui, c := testKeyringCommand(t)
args := []string{"-list", "-http-addr=127.0.0.1:0"}
code := c.Run(args)
if code != 1 {
t.Fatalf("bad: %d, %#v", code, ui.ErrorWriter.String())
@ -90,8 +99,7 @@ func TestKeyringCommandRun_failedConnection(t *testing.T) {
}
func TestKeyringCommandRun_invalidRelayFactor(t *testing.T) {
ui := new(cli.MockUi)
c := &KeyringCommand{Ui: ui}
ui, c := testKeyringCommand(t)
args := []string{"-list", "-relay-factor=6"}
code := c.Run(args)
@ -101,10 +109,9 @@ func TestKeyringCommandRun_invalidRelayFactor(t *testing.T) {
}
func listKeys(t *testing.T, addr string) string {
ui := new(cli.MockUi)
c := &KeyringCommand{Ui: ui}
ui, c := testKeyringCommand(t)
args := []string{"-list", "-rpc-addr=" + addr}
args := []string{"-list", "-http-addr=" + addr}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
@ -114,10 +121,9 @@ func listKeys(t *testing.T, addr string) string {
}
func installKey(t *testing.T, addr string, key string) {
ui := new(cli.MockUi)
c := &KeyringCommand{Ui: ui}
ui, c := testKeyringCommand(t)
args := []string{"-install=" + key, "-rpc-addr=" + addr}
args := []string{"-install=" + key, "-http-addr=" + addr}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
@ -125,10 +131,9 @@ func installKey(t *testing.T, addr string, key string) {
}
func useKey(t *testing.T, addr string, key string) {
ui := new(cli.MockUi)
c := &KeyringCommand{Ui: ui}
ui, c := testKeyringCommand(t)
args := []string{"-use=" + key, "-rpc-addr=" + addr}
args := []string{"-use=" + key, "-http-addr=" + addr}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
@ -136,10 +141,9 @@ func useKey(t *testing.T, addr string, key string) {
}
func removeKey(t *testing.T, addr string, key string) {
ui := new(cli.MockUi)
c := &KeyringCommand{Ui: ui}
ui, c := testKeyringCommand(t)
args := []string{"-remove=" + key, "-rpc-addr=" + addr}
args := []string{"-remove=" + key, "-http-addr=" + addr}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())

15
commands.go

@ -94,6 +94,15 @@ func init() {
}, nil
},
"keyring": func() (cli.Command, error) {
return &command.KeyringCommand{
Command: base.Command{
Ui: ui,
Flags: base.FlagSetClientHTTP,
},
}, nil
},
"kv": func() (cli.Command, error) {
return &command.KVCommand{
Ui: ui,
@ -130,12 +139,6 @@ func init() {
}, nil
},
"keyring": func() (cli.Command, error) {
return &command.KeyringCommand{
Ui: ui,
}, nil
},
"leave": func() (cli.Command, error) {
return &command.LeaveCommand{
Ui: ui,

12
website/source/docs/commands/keyring.html.markdown → website/source/docs/commands/keyring.html.markdown.erb

@ -33,7 +33,11 @@ Usage: `consul keyring [options]`
Only one actionable argument may be specified per run, including `-list`,
`-install`, `-remove`, and `-use`.
The list of available flags are:
#### API Options
<%= partial "docs/commands/http_api_options_client" %>
#### Command Options
* `-list` - List all keys currently in use within the cluster.
@ -46,16 +50,10 @@ The list of available flags are:
* `-remove` - Remove the given key from the cluster. This operation may only be
performed on keys which are not currently the primary key.
* `-token=""` - ACL token to use during requests. Defaults to that of the agent.
* `-relay-factor` - Added in Consul 0.7.4, 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 cluster. The maximum allowed value is 5.
* `-rpc-addr` - Address to the RPC server of the agent you want to contact
to send this command. If this isn't specified, the command will contact
"127.0.0.1:8400" which is the default RPC address of a Consul agent.
## Output
The output of the `consul keyring -list` command consolidates information from
Loading…
Cancel
Save