mirror of https://github.com/hashicorp/consul
DanStough
2 years ago
committed by
Dan Stough
25 changed files with 1780 additions and 16 deletions
@ -0,0 +1,3 @@
|
||||
```release-note:feature |
||||
cli: Adds new subcommands for `peering` workflows. Refer to the [CLI docs](https://www.consul.io/commands/peering) for more information. |
||||
``` |
@ -0,0 +1,91 @@
|
||||
package delete |
||||
|
||||
import ( |
||||
"context" |
||||
"flag" |
||||
"fmt" |
||||
|
||||
"github.com/mitchellh/cli" |
||||
|
||||
"github.com/hashicorp/consul/api" |
||||
"github.com/hashicorp/consul/command/flags" |
||||
) |
||||
|
||||
func New(ui cli.Ui) *cmd { |
||||
c := &cmd{UI: ui} |
||||
c.init() |
||||
return c |
||||
} |
||||
|
||||
type cmd struct { |
||||
UI cli.Ui |
||||
flags *flag.FlagSet |
||||
http *flags.HTTPFlags |
||||
help string |
||||
|
||||
name string |
||||
} |
||||
|
||||
func (c *cmd) init() { |
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError) |
||||
|
||||
c.flags.StringVar(&c.name, "name", "", "(Required) The local name assigned to the peer cluster.") |
||||
|
||||
c.http = &flags.HTTPFlags{} |
||||
flags.Merge(c.flags, c.http.ClientFlags()) |
||||
flags.Merge(c.flags, c.http.PartitionFlag()) |
||||
c.help = flags.Usage(help, c.flags) |
||||
} |
||||
|
||||
func (c *cmd) Run(args []string) int { |
||||
if err := c.flags.Parse(args); err != nil { |
||||
return 1 |
||||
} |
||||
|
||||
if c.name == "" { |
||||
c.UI.Error("Missing the required -name flag") |
||||
return 1 |
||||
} |
||||
|
||||
client, err := c.http.APIClient() |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) |
||||
return 1 |
||||
} |
||||
|
||||
peerings := client.Peerings() |
||||
|
||||
_, err = peerings.Delete(context.Background(), c.name, &api.WriteOptions{}) |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error deleting peering for %s: %v", c.name, err)) |
||||
return 1 |
||||
} |
||||
|
||||
c.UI.Info(fmt.Sprintf("Successfully submitted peering connection, %s, for deletion", c.name)) |
||||
return 0 |
||||
} |
||||
|
||||
func (c *cmd) Synopsis() string { |
||||
return synopsis |
||||
} |
||||
|
||||
func (c *cmd) Help() string { |
||||
return flags.Usage(c.help, nil) |
||||
} |
||||
|
||||
const ( |
||||
synopsis = "Delete a peering connection" |
||||
help = ` |
||||
Usage: consul peering delete [options] -name <peer name> |
||||
|
||||
Delete a peering connection. Consul deletes all data imported from the peer
|
||||
in the background. The peering connection is removed after all associated
|
||||
data has been deleted. Operators can still read the peering connections
|
||||
while the data is being removed. A 'DeletedAt' field will be populated with
|
||||
the timestamp of when the peering was marked for deletion. |
||||
|
||||
Example: |
||||
|
||||
$ consul peering delete -name west-dc |
||||
` |
||||
) |
@ -0,0 +1,70 @@
|
||||
package delete |
||||
|
||||
import ( |
||||
"context" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/mitchellh/cli" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/hashicorp/consul/agent" |
||||
"github.com/hashicorp/consul/api" |
||||
"github.com/hashicorp/consul/testrpc" |
||||
) |
||||
|
||||
func TestDeleteCommand_noTabs(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') { |
||||
t.Fatal("help has tabs") |
||||
} |
||||
} |
||||
|
||||
func TestDeleteCommand(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("too slow for testing.Short") |
||||
} |
||||
|
||||
t.Parallel() |
||||
|
||||
acceptor := agent.NewTestAgent(t, ``) |
||||
t.Cleanup(func() { _ = acceptor.Shutdown() }) |
||||
|
||||
testrpc.WaitForTestAgent(t, acceptor.RPC, "dc1") |
||||
|
||||
acceptingClient := acceptor.Client() |
||||
|
||||
t.Run("name is required", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 1, code, "err: %s", ui.ErrorWriter.String()) |
||||
require.Contains(t, ui.ErrorWriter.String(), "Missing the required -name flag") |
||||
}) |
||||
|
||||
t.Run("delete connection", func(t *testing.T) { |
||||
|
||||
req := api.PeeringGenerateTokenRequest{PeerName: "foo"} |
||||
_, _, err := acceptingClient.Peerings().GenerateToken(context.Background(), req, &api.WriteOptions{}) |
||||
require.NoError(t, err, "Could not generate peering token at acceptor") |
||||
|
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
"-name=foo", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
output := ui.OutputWriter.String() |
||||
require.Contains(t, output, "Success") |
||||
}) |
||||
} |
@ -0,0 +1,109 @@
|
||||
package establish |
||||
|
||||
import ( |
||||
"context" |
||||
"flag" |
||||
"fmt" |
||||
|
||||
"github.com/mitchellh/cli" |
||||
|
||||
"github.com/hashicorp/consul/api" |
||||
"github.com/hashicorp/consul/command/flags" |
||||
) |
||||
|
||||
func New(ui cli.Ui) *cmd { |
||||
c := &cmd{UI: ui} |
||||
c.init() |
||||
return c |
||||
} |
||||
|
||||
type cmd struct { |
||||
UI cli.Ui |
||||
flags *flag.FlagSet |
||||
http *flags.HTTPFlags |
||||
help string |
||||
|
||||
name string |
||||
peeringToken string |
||||
meta map[string]string |
||||
} |
||||
|
||||
func (c *cmd) init() { |
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError) |
||||
|
||||
c.flags.StringVar(&c.name, "name", "", "(Required) The local name assigned to the peer cluster.") |
||||
|
||||
c.flags.StringVar(&c.peeringToken, "peering-token", "", "(Required) The peering token from the accepting cluster.") |
||||
|
||||
c.flags.Var((*flags.FlagMapValue)(&c.meta), "meta", |
||||
"Metadata to associate with the peering, formatted as key=value. This flag "+ |
||||
"may be specified multiple times to set multiple meta fields.") |
||||
|
||||
c.http = &flags.HTTPFlags{} |
||||
flags.Merge(c.flags, c.http.ClientFlags()) |
||||
flags.Merge(c.flags, c.http.PartitionFlag()) |
||||
c.help = flags.Usage(help, c.flags) |
||||
} |
||||
|
||||
func (c *cmd) Run(args []string) int { |
||||
if err := c.flags.Parse(args); err != nil { |
||||
return 1 |
||||
} |
||||
|
||||
if c.name == "" { |
||||
c.UI.Error("Missing the required -name flag") |
||||
return 1 |
||||
} |
||||
|
||||
if c.peeringToken == "" { |
||||
c.UI.Error("Missing the required -peering-token flag") |
||||
return 1 |
||||
} |
||||
|
||||
client, err := c.http.APIClient() |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) |
||||
return 1 |
||||
} |
||||
|
||||
peerings := client.Peerings() |
||||
|
||||
req := api.PeeringEstablishRequest{ |
||||
PeerName: c.name, |
||||
PeeringToken: c.peeringToken, |
||||
Partition: c.http.Partition(), |
||||
Meta: c.meta, |
||||
} |
||||
|
||||
_, _, err = peerings.Establish(context.Background(), req, &api.WriteOptions{}) |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error establishing peering for %s: %v", req.PeerName, err)) |
||||
return 1 |
||||
} |
||||
|
||||
c.UI.Info(fmt.Sprintf("Successfully established peering connection with %s", req.PeerName)) |
||||
return 0 |
||||
} |
||||
|
||||
func (c *cmd) Synopsis() string { |
||||
return synopsis |
||||
} |
||||
|
||||
func (c *cmd) Help() string { |
||||
return flags.Usage(c.help, nil) |
||||
} |
||||
|
||||
const ( |
||||
synopsis = "Establish a peering connection" |
||||
help = ` |
||||
Usage: consul peering establish [options] -name <peer name> -peering-token <token> |
||||
|
||||
Establish a peering connection. The name provided will be used locally by |
||||
this cluster to refer to the peering connection. The peering token can
|
||||
only be used once to establish the connection. |
||||
|
||||
Example: |
||||
|
||||
$ consul peering establish -name west-dc -peering-token <token> |
||||
` |
||||
) |
@ -0,0 +1,127 @@
|
||||
package establish |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/mitchellh/cli" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/hashicorp/consul/agent" |
||||
"github.com/hashicorp/consul/api" |
||||
"github.com/hashicorp/consul/testrpc" |
||||
) |
||||
|
||||
func TestEstablishCommand_noTabs(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') { |
||||
t.Fatal("help has tabs") |
||||
} |
||||
} |
||||
|
||||
func TestEstablishCommand(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("too slow for testing.Short") |
||||
} |
||||
|
||||
t.Parallel() |
||||
|
||||
acceptor := agent.NewTestAgent(t, ``) |
||||
t.Cleanup(func() { _ = acceptor.Shutdown() }) |
||||
|
||||
dialer := agent.NewTestAgent(t, ``) |
||||
t.Cleanup(func() { _ = dialer.Shutdown() }) |
||||
|
||||
testrpc.WaitForTestAgent(t, acceptor.RPC, "dc1") |
||||
testrpc.WaitForTestAgent(t, dialer.RPC, "dc1") |
||||
|
||||
acceptingClient := acceptor.Client() |
||||
dialingClient := dialer.Client() |
||||
|
||||
t.Run("name is required", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + dialer.HTTPAddr(), |
||||
"-peering-token=1234abcde", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 1, code, "err: %s", ui.ErrorWriter.String()) |
||||
require.Contains(t, ui.ErrorWriter.String(), "Missing the required -name flag") |
||||
}) |
||||
|
||||
t.Run("peering token is required", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + dialer.HTTPAddr(), |
||||
"-name=bar", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 1, code, "err: %s", ui.ErrorWriter.String()) |
||||
require.Contains(t, ui.ErrorWriter.String(), "Missing the required -peering-token flag") |
||||
}) |
||||
|
||||
t.Run("establish connection", func(t *testing.T) { |
||||
// Grab the token from the acceptor
|
||||
req := api.PeeringGenerateTokenRequest{PeerName: "foo"} |
||||
res, _, err := acceptingClient.Peerings().GenerateToken(context.Background(), req, &api.WriteOptions{}) |
||||
require.NoError(t, err, "Could not generate peering token at acceptor") |
||||
|
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + dialer.HTTPAddr(), |
||||
"-name=bar", |
||||
fmt.Sprintf("-peering-token=%s", res.PeeringToken), |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
output := ui.OutputWriter.String() |
||||
require.Contains(t, output, "Success") |
||||
}) |
||||
|
||||
t.Run("establish connection with options", func(t *testing.T) { |
||||
// Grab the token from the acceptor
|
||||
req := api.PeeringGenerateTokenRequest{PeerName: "foo"} |
||||
res, _, err := acceptingClient.Peerings().GenerateToken(context.Background(), req, &api.WriteOptions{}) |
||||
require.NoError(t, err, "Could not generate peering token at acceptor") |
||||
|
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + dialer.HTTPAddr(), |
||||
"-name=bar", |
||||
fmt.Sprintf("-peering-token=%s", res.PeeringToken), |
||||
"-meta=env=production", |
||||
"-meta=region=us-west-1", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
output := ui.OutputWriter.String() |
||||
require.Contains(t, output, "Success") |
||||
|
||||
//Meta
|
||||
peering, _, err := dialingClient.Peerings().Read(context.Background(), "bar", &api.QueryOptions{}) |
||||
require.NoError(t, err) |
||||
|
||||
actual, ok := peering.Meta["env"] |
||||
require.True(t, ok) |
||||
require.Equal(t, "production", actual) |
||||
|
||||
actual, ok = peering.Meta["region"] |
||||
require.True(t, ok) |
||||
require.Equal(t, "us-west-1", actual) |
||||
}) |
||||
} |
@ -0,0 +1,139 @@
|
||||
package generate |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"flag" |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"github.com/mitchellh/cli" |
||||
|
||||
"github.com/hashicorp/consul/api" |
||||
"github.com/hashicorp/consul/command/flags" |
||||
"github.com/hashicorp/consul/command/peering" |
||||
) |
||||
|
||||
func New(ui cli.Ui) *cmd { |
||||
c := &cmd{UI: ui} |
||||
c.init() |
||||
return c |
||||
} |
||||
|
||||
type cmd struct { |
||||
UI cli.Ui |
||||
flags *flag.FlagSet |
||||
http *flags.HTTPFlags |
||||
help string |
||||
|
||||
name string |
||||
externalAddresses []string |
||||
meta map[string]string |
||||
format string |
||||
} |
||||
|
||||
func (c *cmd) init() { |
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError) |
||||
|
||||
c.flags.StringVar(&c.name, "name", "", "(Required) The local name assigned to the peer cluster.") |
||||
|
||||
c.flags.Var((*flags.FlagMapValue)(&c.meta), "meta", |
||||
"Metadata to associate with the peering, formatted as key=value. This flag "+ |
||||
"may be specified multiple times to set multiple metadata fields.") |
||||
|
||||
c.flags.Var((*flags.AppendSliceValue)(&c.externalAddresses), "server-external-addresses", |
||||
"A list of addresses to put into the generated token, formatted as a comma-separate list. "+ |
||||
"Addresses are the form of <host or IP>:port. "+ |
||||
"This could be used to specify load balancer(s) or external IPs to reach the servers from "+ |
||||
"the dialing side, and will override any server addresses obtained from the \"consul\" service.") |
||||
|
||||
c.flags.StringVar( |
||||
&c.format, |
||||
"format", |
||||
peering.PeeringFormatPretty, |
||||
fmt.Sprintf("Output format {%s} (default: %s)", strings.Join(peering.GetSupportedFormats(), "|"), peering.PeeringFormatPretty), |
||||
) |
||||
|
||||
c.http = &flags.HTTPFlags{} |
||||
flags.Merge(c.flags, c.http.ClientFlags()) |
||||
flags.Merge(c.flags, c.http.PartitionFlag()) |
||||
c.help = flags.Usage(help, c.flags) |
||||
} |
||||
|
||||
func (c *cmd) Run(args []string) int { |
||||
if err := c.flags.Parse(args); err != nil { |
||||
return 1 |
||||
} |
||||
|
||||
if c.name == "" { |
||||
c.UI.Error("Missing the required -name flag") |
||||
return 1 |
||||
} |
||||
|
||||
if !peering.FormatIsValid(c.format) { |
||||
c.UI.Error(fmt.Sprintf("Invalid format, valid formats are {%s}", strings.Join(peering.GetSupportedFormats(), "|"))) |
||||
return 1 |
||||
} |
||||
|
||||
client, err := c.http.APIClient() |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error connect to Consul agent: %s", err)) |
||||
return 1 |
||||
} |
||||
|
||||
peerings := client.Peerings() |
||||
|
||||
req := api.PeeringGenerateTokenRequest{ |
||||
PeerName: c.name, |
||||
Partition: c.http.Partition(), |
||||
Meta: c.meta, |
||||
ServerExternalAddresses: c.externalAddresses, |
||||
} |
||||
|
||||
res, _, err := peerings.GenerateToken(context.Background(), req, &api.WriteOptions{}) |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error generating peering token for %s: %v", req.PeerName, err)) |
||||
return 1 |
||||
} |
||||
|
||||
if c.format == peering.PeeringFormatJSON { |
||||
output, err := json.Marshal(res) |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error marshalling JSON: %s", err)) |
||||
return 1 |
||||
} |
||||
c.UI.Output(string(output)) |
||||
return 0 |
||||
} |
||||
|
||||
c.UI.Info(res.PeeringToken) |
||||
return 0 |
||||
} |
||||
|
||||
func (c *cmd) Synopsis() string { |
||||
return synopsis |
||||
} |
||||
|
||||
func (c *cmd) Help() string { |
||||
return flags.Usage(c.help, nil) |
||||
} |
||||
|
||||
const ( |
||||
synopsis = "Generate a peering token" |
||||
help = ` |
||||
Usage: consul peering generate-token [options] -name <peer name> |
||||
|
||||
Generate a peering token. The name provided will be used locally by |
||||
this cluster to refer to the peering connection. Re-generating a token
|
||||
for a given name will not interrupt any active connection, but will
|
||||
invalidate any unused token for that name. |
||||
|
||||
Example: |
||||
|
||||
$ consul peering generate-token -name west-dc |
||||
|
||||
Example using a load balancer in front of Consul servers: |
||||
|
||||
$ consul peering generate-token -name west-dc -server-external-addresses load-balancer.elb.us-west-1.amazonaws.com:8502 |
||||
` |
||||
) |
@ -0,0 +1,141 @@
|
||||
package generate |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/base64" |
||||
"encoding/json" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/mitchellh/cli" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/hashicorp/consul/agent" |
||||
"github.com/hashicorp/consul/api" |
||||
"github.com/hashicorp/consul/testrpc" |
||||
) |
||||
|
||||
func TestGenerateCommand_noTabs(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') { |
||||
t.Fatal("help has tabs") |
||||
} |
||||
} |
||||
|
||||
func TestGenerateCommand(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("too slow for testing.Short") |
||||
} |
||||
|
||||
t.Parallel() |
||||
|
||||
a := agent.NewTestAgent(t, ``) |
||||
t.Cleanup(func() { _ = a.Shutdown() }) |
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1") |
||||
|
||||
client := a.Client() |
||||
|
||||
t.Run("name is required", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + a.HTTPAddr(), |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 1, code, "err: %s", ui.ErrorWriter.String()) |
||||
require.Contains(t, ui.ErrorWriter.String(), "Missing the required -name flag") |
||||
}) |
||||
|
||||
t.Run("invalid format", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + a.HTTPAddr(), |
||||
"-name=foo", |
||||
"-format=toml", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 1, code, "exited successfully when it should have failed") |
||||
output := ui.ErrorWriter.String() |
||||
require.Contains(t, output, "Invalid format") |
||||
}) |
||||
|
||||
t.Run("generate token", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + a.HTTPAddr(), |
||||
"-name=foo", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
token, err := base64.StdEncoding.DecodeString(ui.OutputWriter.String()) |
||||
require.NoError(t, err, "error decoding token") |
||||
require.Contains(t, string(token), "\"ServerName\":\"server.dc1.consul\"") |
||||
}) |
||||
|
||||
t.Run("generate token with options", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + a.HTTPAddr(), |
||||
"-name=bar", |
||||
"-server-external-addresses=1.2.3.4,5.6.7.8", |
||||
"-meta=env=production", |
||||
"-meta=region=us-east-1", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
token, err := base64.StdEncoding.DecodeString(ui.OutputWriter.String()) |
||||
require.NoError(t, err, "error decoding token") |
||||
require.Contains(t, string(token), "\"ServerName\":\"server.dc1.consul\"") |
||||
|
||||
//ServerExternalAddresses
|
||||
require.Contains(t, string(token), "1.2.3.4") |
||||
require.Contains(t, string(token), "5.6.7.8") |
||||
|
||||
//Meta
|
||||
peering, _, err := client.Peerings().Read(context.Background(), "bar", &api.QueryOptions{}) |
||||
require.NoError(t, err) |
||||
|
||||
actual, ok := peering.Meta["env"] |
||||
require.True(t, ok) |
||||
require.Equal(t, "production", actual) |
||||
|
||||
actual, ok = peering.Meta["region"] |
||||
require.True(t, ok) |
||||
require.Equal(t, "us-east-1", actual) |
||||
}) |
||||
|
||||
t.Run("read with json", func(t *testing.T) { |
||||
|
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + a.HTTPAddr(), |
||||
"-name=baz", |
||||
"-format=json", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
output := ui.OutputWriter.Bytes() |
||||
|
||||
var outputRes api.PeeringGenerateTokenResponse |
||||
require.NoError(t, json.Unmarshal(output, &outputRes)) |
||||
|
||||
token, err := base64.StdEncoding.DecodeString(outputRes.PeeringToken) |
||||
require.NoError(t, err, "error decoding token") |
||||
require.Contains(t, string(token), "\"ServerName\":\"server.dc1.consul\"") |
||||
}) |
||||
} |
@ -0,0 +1,139 @@
|
||||
package list |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"flag" |
||||
"fmt" |
||||
"sort" |
||||
"strings" |
||||
|
||||
"github.com/mitchellh/cli" |
||||
"github.com/ryanuber/columnize" |
||||
|
||||
"github.com/hashicorp/consul/api" |
||||
"github.com/hashicorp/consul/command/flags" |
||||
"github.com/hashicorp/consul/command/peering" |
||||
) |
||||
|
||||
func New(ui cli.Ui) *cmd { |
||||
c := &cmd{UI: ui} |
||||
c.init() |
||||
return c |
||||
} |
||||
|
||||
type cmd struct { |
||||
UI cli.Ui |
||||
flags *flag.FlagSet |
||||
http *flags.HTTPFlags |
||||
help string |
||||
|
||||
format string |
||||
} |
||||
|
||||
func (c *cmd) init() { |
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError) |
||||
|
||||
c.flags.StringVar( |
||||
&c.format, |
||||
"format", |
||||
peering.PeeringFormatPretty, |
||||
fmt.Sprintf("Output format {%s} (default: %s)", strings.Join(peering.GetSupportedFormats(), "|"), peering.PeeringFormatPretty), |
||||
) |
||||
|
||||
c.http = &flags.HTTPFlags{} |
||||
flags.Merge(c.flags, c.http.ClientFlags()) |
||||
flags.Merge(c.flags, c.http.PartitionFlag()) |
||||
c.help = flags.Usage(help, c.flags) |
||||
} |
||||
|
||||
func (c *cmd) Run(args []string) int { |
||||
if err := c.flags.Parse(args); err != nil { |
||||
return 1 |
||||
} |
||||
|
||||
if !peering.FormatIsValid(c.format) { |
||||
c.UI.Error(fmt.Sprintf("Invalid format, valid formats are {%s}", strings.Join(peering.GetSupportedFormats(), "|"))) |
||||
return 1 |
||||
} |
||||
|
||||
client, err := c.http.APIClient() |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error connect to Consul agent: %s", err)) |
||||
return 1 |
||||
} |
||||
|
||||
peerings := client.Peerings() |
||||
|
||||
res, _, err := peerings.List(context.Background(), &api.QueryOptions{}) |
||||
if err != nil { |
||||
c.UI.Error("Error listing peerings") |
||||
return 1 |
||||
} |
||||
|
||||
list := peeringList(res) |
||||
sort.Sort(list) |
||||
|
||||
if c.format == peering.PeeringFormatJSON { |
||||
output, err := json.Marshal(list) |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error marshalling JSON: %s", err)) |
||||
return 1 |
||||
} |
||||
c.UI.Output(string(output)) |
||||
return 0 |
||||
} |
||||
|
||||
if len(res) == 0 { |
||||
c.UI.Info(fmt.Sprintf("There are no peering connections.")) |
||||
return 0 |
||||
} |
||||
|
||||
result := make([]string, 0, len(list)) |
||||
header := "Name\x1fState\x1fImported Svcs\x1fExported Svcs\x1fMeta" |
||||
result = append(result, header) |
||||
for _, peer := range list { |
||||
metaPairs := make([]string, 0, len(peer.Meta)) |
||||
for k, v := range peer.Meta { |
||||
metaPairs = append(metaPairs, fmt.Sprintf("%s=%s", k, v)) |
||||
} |
||||
meta := strings.Join(metaPairs, ",") |
||||
line := fmt.Sprintf("%s\x1f%s\x1f%d\x1f%d\x1f%s", |
||||
peer.Name, peer.State, peer.ImportedServiceCount, peer.ExportedServiceCount, meta) |
||||
result = append(result, line) |
||||
} |
||||
|
||||
output := columnize.Format(result, &columnize.Config{Delim: string([]byte{0x1f})}) |
||||
c.UI.Output(output) |
||||
|
||||
return 0 |
||||
} |
||||
|
||||
func (c *cmd) Synopsis() string { |
||||
return synopsis |
||||
} |
||||
|
||||
func (c *cmd) Help() string { |
||||
return flags.Usage(c.help, nil) |
||||
} |
||||
|
||||
const ( |
||||
synopsis = "List peering connections" |
||||
help = ` |
||||
Usage: consul peering list [options] |
||||
|
||||
List all peering connections. The results will be filtered according |
||||
to ACL policy configuration.
|
||||
|
||||
Example: |
||||
|
||||
$ consul peering list |
||||
` |
||||
) |
||||
|
||||
// peeringList applies sort.Interface to a list of peering connections for sorting by name.
|
||||
type peeringList []*api.Peering |
||||
|
||||
func (d peeringList) Len() int { return len(d) } |
||||
func (d peeringList) Less(i, j int) bool { return d[i].Name < d[j].Name } |
||||
func (d peeringList) Swap(i, j int) { d[i], d[j] = d[j], d[i] } |
@ -0,0 +1,133 @@
|
||||
package list |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/mitchellh/cli" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/hashicorp/consul/agent" |
||||
"github.com/hashicorp/consul/api" |
||||
"github.com/hashicorp/consul/testrpc" |
||||
) |
||||
|
||||
func TestListCommand_noTabs(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') { |
||||
t.Fatal("help has tabs") |
||||
} |
||||
} |
||||
|
||||
func TestListCommand(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("too slow for testing.Short") |
||||
} |
||||
|
||||
t.Parallel() |
||||
|
||||
acceptor := agent.NewTestAgent(t, ``) |
||||
t.Cleanup(func() { _ = acceptor.Shutdown() }) |
||||
|
||||
testrpc.WaitForTestAgent(t, acceptor.RPC, "dc1") |
||||
|
||||
acceptingClient := acceptor.Client() |
||||
|
||||
t.Run("invalid format", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
"-format=toml", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 1, code, "exited successfully when it should have failed") |
||||
output := ui.ErrorWriter.String() |
||||
require.Contains(t, output, "Invalid format") |
||||
}) |
||||
|
||||
t.Run("no results - pretty", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
output := ui.OutputWriter.String() |
||||
require.Contains(t, output, "no peering connections") |
||||
}) |
||||
|
||||
t.Run("two results for pretty print", func(t *testing.T) { |
||||
|
||||
generateReq := api.PeeringGenerateTokenRequest{PeerName: "foo"} |
||||
_, _, err := acceptingClient.Peerings().GenerateToken(context.Background(), generateReq, &api.WriteOptions{}) |
||||
require.NoError(t, err, "Could not generate peering token at acceptor for \"foo\"") |
||||
|
||||
generateReq = api.PeeringGenerateTokenRequest{PeerName: "bar"} |
||||
_, _, err = acceptingClient.Peerings().GenerateToken(context.Background(), generateReq, &api.WriteOptions{}) |
||||
require.NoError(t, err, "Could not generate peering token at acceptor for \"bar\"") |
||||
|
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
output := ui.OutputWriter.String() |
||||
require.Equal(t, 3, strings.Count(output, "\n")) // There should be three lines including the header
|
||||
|
||||
lines := strings.Split(output, "\n") |
||||
|
||||
require.Contains(t, lines[0], "Name") |
||||
require.Contains(t, lines[1], "bar") |
||||
require.Contains(t, lines[2], "foo") |
||||
}) |
||||
|
||||
t.Run("no results - json", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
"-format=json", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
output := ui.OutputWriter.String() |
||||
require.Contains(t, output, "[]") |
||||
}) |
||||
|
||||
t.Run("two results for JSON print", func(t *testing.T) { |
||||
|
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
"-format=json", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
output := ui.OutputWriter.Bytes() |
||||
|
||||
var outputList []*api.Peering |
||||
require.NoError(t, json.Unmarshal(output, &outputList)) |
||||
|
||||
require.Len(t, outputList, 2) |
||||
require.Equal(t, "bar", outputList[0].Name) |
||||
require.Equal(t, "foo", outputList[1].Name) |
||||
}) |
||||
} |
@ -0,0 +1,69 @@
|
||||
package peering |
||||
|
||||
import ( |
||||
"github.com/mitchellh/cli" |
||||
|
||||
"github.com/hashicorp/consul/command/flags" |
||||
) |
||||
|
||||
const ( |
||||
PeeringFormatJSON = "json" |
||||
PeeringFormatPretty = "pretty" |
||||
) |
||||
|
||||
func GetSupportedFormats() []string { |
||||
return []string{PeeringFormatJSON, PeeringFormatPretty} |
||||
} |
||||
|
||||
func FormatIsValid(f string) bool { |
||||
return f == PeeringFormatPretty || f == PeeringFormatJSON |
||||
} |
||||
|
||||
func New() *cmd { |
||||
return &cmd{} |
||||
} |
||||
|
||||
type cmd struct{} |
||||
|
||||
func (c *cmd) Run(args []string) int { |
||||
return cli.RunResultHelp |
||||
} |
||||
|
||||
func (c *cmd) Synopsis() string { |
||||
return synopsis |
||||
} |
||||
|
||||
func (c *cmd) Help() string { |
||||
return flags.Usage(help, nil) |
||||
} |
||||
|
||||
const synopsis = "Create and manage peering connections between Consul clusters" |
||||
const help = ` |
||||
Usage: consul peering <subcommand> [options] [args] |
||||
|
||||
This command has subcommands for interacting with Cluster Peering
|
||||
connections. Here are some simple examples, and more detailed |
||||
examples are available in the subcommands or the documentation. |
||||
|
||||
Generate a peering token: |
||||
|
||||
$ consul peering generate-token -name west-dc |
||||
|
||||
Establish a peering connection: |
||||
|
||||
$ consul peering establish -name east-dc -peering-token <token> |
||||
|
||||
List all the local peering connections: |
||||
|
||||
$ consul peering list |
||||
|
||||
Print the status of a peering connection: |
||||
|
||||
$ consul peering read -name west-dc |
||||
|
||||
Delete and close a peering connection: |
||||
|
||||
$ consul peering delete -name west-dc |
||||
|
||||
For more examples, ask for subcommand help or view the documentation. |
||||
` |
@ -0,0 +1,164 @@
|
||||
package read |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"encoding/json" |
||||
"flag" |
||||
"fmt" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/mitchellh/cli" |
||||
|
||||
"github.com/hashicorp/consul/api" |
||||
"github.com/hashicorp/consul/command/flags" |
||||
"github.com/hashicorp/consul/command/peering" |
||||
) |
||||
|
||||
func New(ui cli.Ui) *cmd { |
||||
c := &cmd{UI: ui} |
||||
c.init() |
||||
return c |
||||
} |
||||
|
||||
type cmd struct { |
||||
UI cli.Ui |
||||
flags *flag.FlagSet |
||||
http *flags.HTTPFlags |
||||
help string |
||||
|
||||
name string |
||||
format string |
||||
} |
||||
|
||||
func (c *cmd) init() { |
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError) |
||||
|
||||
c.flags.StringVar(&c.name, "name", "", "(Required) The local name assigned to the peer cluster.") |
||||
|
||||
c.flags.StringVar( |
||||
&c.format, |
||||
"format", |
||||
peering.PeeringFormatPretty, |
||||
fmt.Sprintf("Output format {%s} (default: %s)", strings.Join(peering.GetSupportedFormats(), "|"), peering.PeeringFormatPretty), |
||||
) |
||||
|
||||
c.http = &flags.HTTPFlags{} |
||||
flags.Merge(c.flags, c.http.ClientFlags()) |
||||
flags.Merge(c.flags, c.http.PartitionFlag()) |
||||
c.help = flags.Usage(help, c.flags) |
||||
} |
||||
|
||||
func (c *cmd) Run(args []string) int { |
||||
if err := c.flags.Parse(args); err != nil { |
||||
return 1 |
||||
} |
||||
|
||||
if c.name == "" { |
||||
c.UI.Error("Missing the required -name flag") |
||||
return 1 |
||||
} |
||||
|
||||
if !peering.FormatIsValid(c.format) { |
||||
c.UI.Error(fmt.Sprintf("Invalid format, valid formats are {%s}", strings.Join(peering.GetSupportedFormats(), "|"))) |
||||
return 1 |
||||
} |
||||
|
||||
client, err := c.http.APIClient() |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error connect to Consul agent: %s", err)) |
||||
return 1 |
||||
} |
||||
|
||||
peerings := client.Peerings() |
||||
|
||||
res, _, err := peerings.Read(context.Background(), c.name, &api.QueryOptions{}) |
||||
if err != nil { |
||||
c.UI.Error("Error reading peerings") |
||||
return 1 |
||||
} |
||||
|
||||
if res == nil { |
||||
c.UI.Error(fmt.Sprintf("No peering with name %s found.", c.name)) |
||||
return 1 |
||||
} |
||||
|
||||
if c.format == peering.PeeringFormatJSON { |
||||
output, err := json.Marshal(res) |
||||
if err != nil { |
||||
c.UI.Error(fmt.Sprintf("Error marshalling JSON: %s", err)) |
||||
return 1 |
||||
} |
||||
c.UI.Output(string(output)) |
||||
return 0 |
||||
} |
||||
|
||||
c.UI.Output(formatPeering(res)) |
||||
|
||||
return 0 |
||||
} |
||||
|
||||
func formatPeering(peering *api.Peering) string { |
||||
var buffer bytes.Buffer |
||||
|
||||
buffer.WriteString(fmt.Sprintf("Name: %s\n", peering.Name)) |
||||
buffer.WriteString(fmt.Sprintf("ID: %s\n", peering.ID)) |
||||
if peering.Partition != "" { |
||||
buffer.WriteString(fmt.Sprintf("Partition: %s\n", peering.Partition)) |
||||
} |
||||
if peering.DeletedAt != nil { |
||||
buffer.WriteString(fmt.Sprintf("DeletedAt: %s\n", peering.DeletedAt.Format(time.RFC3339))) |
||||
} |
||||
buffer.WriteString(fmt.Sprintf("State: %s\n", peering.State)) |
||||
if peering.Meta != nil && len(peering.Meta) > 0 { |
||||
buffer.WriteString("Meta:\n") |
||||
for k, v := range peering.Meta { |
||||
buffer.WriteString(fmt.Sprintf(" %s=%s\n", k, v)) |
||||
} |
||||
} |
||||
|
||||
buffer.WriteString("\n") |
||||
buffer.WriteString(fmt.Sprintf("Peer ID: %s\n", peering.PeerID)) |
||||
buffer.WriteString(fmt.Sprintf("Peer Server Name: %s\n", peering.PeerServerName)) |
||||
buffer.WriteString(fmt.Sprintf("Peer CA Pems: %d\n", len(peering.PeerCAPems))) |
||||
if peering.PeerServerAddresses != nil && len(peering.PeerServerAddresses) > 0 { |
||||
buffer.WriteString("Peer Server Addresses:\n") |
||||
for _, v := range peering.PeerServerAddresses { |
||||
buffer.WriteString(fmt.Sprintf(" %s", v)) |
||||
} |
||||
} |
||||
|
||||
buffer.WriteString("\n") |
||||
buffer.WriteString(fmt.Sprintf("Imported Services: %d\n", peering.ImportedServiceCount)) |
||||
buffer.WriteString(fmt.Sprintf("Exported Services: %d\n", peering.ExportedServiceCount)) |
||||
|
||||
buffer.WriteString("\n") |
||||
buffer.WriteString(fmt.Sprintf("Create Index: %d\n", peering.CreateIndex)) |
||||
buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", peering.ModifyIndex)) |
||||
|
||||
return buffer.String() |
||||
} |
||||
|
||||
func (c *cmd) Synopsis() string { |
||||
return synopsis |
||||
} |
||||
|
||||
func (c *cmd) Help() string { |
||||
return flags.Usage(c.help, nil) |
||||
} |
||||
|
||||
const ( |
||||
synopsis = "Read a peering connection" |
||||
help = ` |
||||
Usage: consul peering read [options] -name <peer name> |
||||
|
||||
Read a peering connection with the provided name. If one is not found, |
||||
the command will exit with a non-zero code. The result will be filtered according |
||||
to ACL policy configuration. |
||||
|
||||
Example: |
||||
|
||||
$ consul peering read -name west-dc |
||||
` |
||||
) |
@ -0,0 +1,135 @@
|
||||
package read |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/mitchellh/cli" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/hashicorp/consul/agent" |
||||
"github.com/hashicorp/consul/api" |
||||
"github.com/hashicorp/consul/testrpc" |
||||
) |
||||
|
||||
func TestReadCommand_noTabs(t *testing.T) { |
||||
t.Parallel() |
||||
|
||||
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') { |
||||
t.Fatal("help has tabs") |
||||
} |
||||
} |
||||
|
||||
func TestReadCommand(t *testing.T) { |
||||
if testing.Short() { |
||||
t.Skip("too slow for testing.Short") |
||||
} |
||||
|
||||
t.Parallel() |
||||
|
||||
acceptor := agent.NewTestAgent(t, ``) |
||||
t.Cleanup(func() { _ = acceptor.Shutdown() }) |
||||
|
||||
testrpc.WaitForTestAgent(t, acceptor.RPC, "dc1") |
||||
|
||||
acceptingClient := acceptor.Client() |
||||
|
||||
t.Run("no name flag", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 1, code, "err: %s", ui.ErrorWriter.String()) |
||||
require.Contains(t, ui.ErrorWriter.String(), "Missing the required -name flag") |
||||
}) |
||||
|
||||
t.Run("invalid format", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
"-name=foo", |
||||
"-format=toml", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 1, code, "exited successfully when it should have failed") |
||||
output := ui.ErrorWriter.String() |
||||
require.Contains(t, output, "Invalid format") |
||||
}) |
||||
|
||||
t.Run("peering does not exist", func(t *testing.T) { |
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
"-name=foo", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 1, code, "err: %s", ui.ErrorWriter.String()) |
||||
require.Contains(t, ui.ErrorWriter.String(), "No peering with name") |
||||
}) |
||||
|
||||
t.Run("read with pretty print", func(t *testing.T) { |
||||
|
||||
generateReq := api.PeeringGenerateTokenRequest{ |
||||
PeerName: "foo", |
||||
Meta: map[string]string{ |
||||
"env": "production", |
||||
}, |
||||
} |
||||
_, _, err := acceptingClient.Peerings().GenerateToken(context.Background(), generateReq, &api.WriteOptions{}) |
||||
require.NoError(t, err, "Could not generate peering token at acceptor for \"foo\"") |
||||
|
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
"-name=foo", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
output := ui.OutputWriter.String() |
||||
require.Greater(t, strings.Count(output, "\n"), 0) // Checking for some kind of empty output
|
||||
|
||||
// Spot check some fields and values
|
||||
require.Contains(t, output, "foo") |
||||
require.Contains(t, output, api.PeeringStatePending) |
||||
require.Contains(t, output, "env=production") |
||||
require.Contains(t, output, "Imported Services") |
||||
require.Contains(t, output, "Exported Services") |
||||
}) |
||||
|
||||
t.Run("read with json", func(t *testing.T) { |
||||
|
||||
ui := cli.NewMockUi() |
||||
cmd := New(ui) |
||||
|
||||
args := []string{ |
||||
"-http-addr=" + acceptor.HTTPAddr(), |
||||
"-name=foo", |
||||
"-format=json", |
||||
} |
||||
|
||||
code := cmd.Run(args) |
||||
require.Equal(t, 0, code) |
||||
output := ui.OutputWriter.Bytes() |
||||
|
||||
var outputPeering api.Peering |
||||
require.NoError(t, json.Unmarshal(output, &outputPeering)) |
||||
|
||||
require.Equal(t, "foo", outputPeering.Name) |
||||
require.Equal(t, "production", outputPeering.Meta["env"]) |
||||
}) |
||||
} |
@ -0,0 +1,50 @@
|
||||
--- |
||||
layout: commands |
||||
page_title: 'Commands: Peering Delete' |
||||
description: Learn how to use the consul peering delete command to remove a peering connection between Consul clusters. |
||||
--- |
||||
|
||||
# Consul Peering Delete |
||||
|
||||
Command: `consul peering delete` |
||||
|
||||
Corresponding HTTP API Endpoint: [\[DELETE\] /v1/peering/:name](/api-docs/peering#delete-a-peering-connection) |
||||
|
||||
The `peering delete` removes a peering connection with another cluster. |
||||
Consul deletes all data imported from the peer in the background. |
||||
The peering connection is removed after all associated data has been deleted. |
||||
Operators can still read the peering connections while the data is being removed. |
||||
The command adds a `DeletedAt` field to the peering connection object with the timestamp of when the peering was marked for deletion. |
||||
You can only use a peering token to establish the connection once. If you need to reestablish a peering connection, you must generate a new token. |
||||
|
||||
The table below shows this command's [required ACLs](/api#authentication). |
||||
|
||||
| ACL Required | |
||||
| ------------ | |
||||
| `peering:write` | |
||||
|
||||
## Usage |
||||
|
||||
Usage: `consul peering delete [options] -name <peer name>` |
||||
|
||||
#### Command Options |
||||
|
||||
- `-name=<string>` - (Required) The name of the peer. |
||||
|
||||
#### Enterprise Options |
||||
|
||||
@include 'http_api_partition_options.mdx' |
||||
|
||||
#### API Options |
||||
|
||||
@include 'http_api_options_client.mdx' |
||||
|
||||
## Examples |
||||
|
||||
The following examples deletes a peering connection to a cluster locally referred to as "cluster-02": |
||||
|
||||
```shell-session hideClipboard |
||||
$ consul peering delete -name cluster-02 |
||||
Successfully submitted peering connection, cluster-02, for deletion |
||||
``` |
||||
|
@ -0,0 +1,52 @@
|
||||
--- |
||||
layout: commands |
||||
page_title: 'Commands: Peering Establish' |
||||
description: Learn how to use the consul peering establish command to establish a peering connection between Consul clusters. |
||||
--- |
||||
|
||||
# Consul Peering Establish |
||||
|
||||
Command: `consul peering establish` |
||||
|
||||
Corresponding HTTP API Endpoint: [\[POST\] /v1/peering/establish](/api-docs/peering#establish-a-peering-connection) |
||||
|
||||
The `peering establish` starts a peering connection with the cluster that generated the peering token. |
||||
You can generate cluster peering tokens using the [`consul peering generate-token`](/commands/operator/generate-token) command or the [HTTP API](https://www.consul.io/api-docs/peering#generate-a-peering-token). |
||||
|
||||
You can only use a peering token to establish the connection once. If you need to reestablish a peering connection, you must generate a new token. |
||||
|
||||
The table below shows this command's [required ACLs](/api#authentication). |
||||
|
||||
| ACL Required | |
||||
| ------------ | |
||||
| `peering:write` | |
||||
|
||||
## Usage |
||||
|
||||
Usage: `consul peering establish [options] -name <peer name> -peering-token <token>` |
||||
|
||||
#### Command Options |
||||
|
||||
- `-name=<string>` - (Required) Specifies a local name for the cluster you are establishing a connection with. The `name` is only used to identify the connection with the peer. |
||||
|
||||
- `-peering-token=<string>` - (Required) Specifies the peering token from the cluster that generated the token. |
||||
|
||||
- `-meta=<string>=<string>` - Specifies key/value pairs to associate with the peering connection in `-meta="key"="value"` format. You can use the flag multiple times to set multiple metadata fields. |
||||
|
||||
#### Enterprise Options |
||||
|
||||
@include 'http_api_partition_options.mdx' |
||||
|
||||
#### API Options |
||||
|
||||
@include 'http_api_options_client.mdx' |
||||
|
||||
## Examples |
||||
|
||||
The following examples establishes a peering connection with a cluster locally referred to as "cluster-01": |
||||
|
||||
```shell-session hideClipboard |
||||
$ consul peering establish -name cluster-01 -peering-token eyJDQSI6bnVs...5Yi0wNzk5NTA1YTRmYjYifQ== |
||||
Successfully established peering connection with cluster-01 |
||||
``` |
||||
|
@ -0,0 +1,68 @@
|
||||
--- |
||||
layout: commands |
||||
page_title: 'Commands: Peering Generate Token' |
||||
description: Learn how to use the consul peering generate-token command to generate token that enables you to peer Consul clusters. |
||||
--- |
||||
|
||||
# Consul Peering Generate Token |
||||
|
||||
Command: `consul peering generate-token` |
||||
|
||||
Corresponding HTTP API Endpoint: [\[POST\] /v1/peering/token](/api-docs/peering#generate-a-peering-token) |
||||
|
||||
The `peering generate-token` generates a peering token. The token is base 64-encoded string containing the token details. |
||||
This token should be transferred to the other cluster being peered and consumed using [`consul peering establish`](/commands/peering/establish). |
||||
|
||||
Generating a token and specifying the same local name associated with a previously-generated token does not affect active connections established with the original token. If the previously-generated token is not actively being used for a peer connection, however, it will become invalid when the new token with the same local name is generated. |
||||
|
||||
The table below shows this command's [required ACLs](/api#authentication). |
||||
|
||||
| ACL Required | |
||||
| ------------ | |
||||
| `peering:write` | |
||||
|
||||
## Usage |
||||
|
||||
Usage: `consul peering generate-token [options] -name <peer name>` |
||||
|
||||
#### Command Options |
||||
|
||||
- `-name=<string>` - (Required) Specifies a local name for the cluster that the token is intended for. |
||||
The `name` is only used to identify the connection with the peer. |
||||
Generating a token and specifying the same local name associated with a previously-generated token does not affect active connections established with the original token. |
||||
If the previously-generated token is not actively being used for a peer connection, however, it will become invalid when the new token with the same local name is generated. |
||||
|
||||
- `-meta=<string>=<string>` - Specifies key/value pairs to associate with the peering connection token in `-meta="key"="value"` format. You can use the flag multiple times to set multiple metadata fields. |
||||
|
||||
<<<<<<< HEAD |
||||
- `-server-external-addresses=<string>[,string,...]` - Specifies a comma-separated list of addresses |
||||
to put into the generated token. Addresses are of the form of `{host or IP}:port`. |
||||
You can specify one or more load balancers or external IPs that route external traffic to this cluster's Consul servers. |
||||
|
||||
- `-format={pretty|json}` - Command output format. The default value is `pretty`. |
||||
|
||||
#### Enterprise Options |
||||
|
||||
@include 'http_api_partition_options.mdx' |
||||
|
||||
#### API Options |
||||
|
||||
@include 'http_api_options_client.mdx' |
||||
|
||||
## Examples |
||||
|
||||
The following example generates a peering token for a cluster called "cluster-02": |
||||
|
||||
```shell-session hideClipboard |
||||
$ consul peering generate-token -name cluster-02 |
||||
eyJDQSI6bnVs...5Yi0wNzk5NTA1YTRmYjYifQ== |
||||
``` |
||||
|
||||
### Using a Load Balancer for Consul Servers |
||||
|
||||
The following example generates a token for a cluster where servers are proxied by a load balancer: |
||||
|
||||
```shell-session hideClipboard |
||||
$ consul peering generate-token -server-external-addresses my-load-balancer-1234567890abcdef.elb.us-east-2.amazonaws.com -name cluster-02 |
||||
eyJDQSI6bnVs...5Yi0wNzk5NTA1YTRmYjYifQ== |
||||
``` |
@ -0,0 +1,40 @@
|
||||
--- |
||||
layout: commands |
||||
page_title: 'Commands: Peering' |
||||
--- |
||||
|
||||
# Consul Peering |
||||
|
||||
Command: `consul peering` |
||||
|
||||
Use the `peering` command to create and manage peering connections between Consul clusters, including token generation and consumption. Refer to |
||||
[Create and Manage Peerings Connections](/docs/connect/cluster-peering/create-manage-peering) for an |
||||
overview of the CLI workflow for cluster peering. |
||||
|
||||
## Usage |
||||
|
||||
```text |
||||
Usage: consul peering <subcommand> [options] |
||||
|
||||
# ... |
||||
|
||||
Subcommands: |
||||
|
||||
delete Close and delete a peering connection |
||||
establish Consume a peering token and establish a connection with the accepting cluster |
||||
generate-token Generate a peering token for use by a dialing cluster |
||||
list List the local cluster's peering connections |
||||
read Read detailed information on a peering connection |
||||
``` |
||||
|
||||
For more information, examples, and usage about a subcommand, click on the name |
||||
of the subcommand in the sidebar or one of the links below: |
||||
|
||||
- [delete](/commands/peering/delete) |
||||
- [establish](/commands/peering/establish) |
||||
- [generate-token](/commands/peering/generate-token) |
||||
- [list](/commands/peering/list) |
||||
- [read](/commands/peering/read) |
||||
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
--- |
||||
layout: commands |
||||
page_title: 'Commands: Peering List' |
||||
--- |
||||
|
||||
# Consul Peering List |
||||
|
||||
Command: `consul peering List` |
||||
|
||||
Corresponding HTTP API Endpoint: [\[GET\] /v1/peerings](/api-docs/peering#list-all-peerings) |
||||
|
||||
The `peering list` lists all peering connections. |
||||
The results are filtered according to ACL policy configuration. |
||||
|
||||
The table below shows this command's [required ACLs](/api#authentication). |
||||
|
||||
| ACL Required | |
||||
| ------------ | |
||||
| `peering:read` | |
||||
|
||||
## Usage |
||||
|
||||
Usage: `consul peering list [options]` |
||||
|
||||
#### Command Options |
||||
|
||||
- `-format={pretty|json}` - Command output format. The default value is `pretty`. |
||||
|
||||
#### Enterprise Options |
||||
|
||||
@include 'http_api_partition_options.mdx' |
||||
|
||||
#### API Options |
||||
|
||||
@include 'http_api_options_client.mdx' |
||||
|
||||
## Examples |
||||
|
||||
The following example lists all peering connections associated with the cluster: |
||||
|
||||
```shell-session hideClipboard |
||||
$ consul peering list |
||||
Name State Imported Svcs Exported Svcs Meta |
||||
cluster-02 ACTIVE 0 2 env=production |
||||
cluster-03 PENDING 0 0 |
||||
``` |
||||
|
@ -0,0 +1,62 @@
|
||||
--- |
||||
layout: commands |
||||
page_title: 'Commands: Peering Read' |
||||
--- |
||||
|
||||
# Consul Peering Read |
||||
|
||||
Command: `consul peering read` |
||||
|
||||
Corresponding HTTP API Endpoint: [\[GET\] /v1/peering/:name](/api-docs/peering#read-a-peering-connection) |
||||
|
||||
The `peering read` displays information on the status of a peering connection. |
||||
|
||||
The table below shows this command's [required ACLs](/api#authentication). |
||||
|
||||
| ACL Required | |
||||
| ------------ | |
||||
| `peering:read` | |
||||
|
||||
## Usage |
||||
|
||||
Usage: `consul peering read [options] -name <peer name>` |
||||
|
||||
#### Command Options |
||||
|
||||
- `-name=<string>` - (Required) The name of the peer associated with a connection that you want to read. |
||||
|
||||
- `-format={pretty|json}` - Command output format. The default value is `pretty`. |
||||
|
||||
#### Enterprise Options |
||||
|
||||
@include 'http_api_partition_options.mdx' |
||||
|
||||
#### API Options |
||||
|
||||
@include 'http_api_options_client.mdx' |
||||
|
||||
## Examples |
||||
|
||||
The following example outputs information about a peering connection locally referred to as "cluster-02": |
||||
|
||||
```shell-session hideClipboard |
||||
$ consul peering read -name cluster-02 |
||||
Name: cluster-02 |
||||
ID: 3b001063-8079-b1a6-764c-738af5a39a97 |
||||
State: ACTIVE |
||||
Meta: |
||||
env=production |
||||
|
||||
Peer ID: e83a315c-027e-bcb1-7c0c-a46650904a05 |
||||
Peer Server Name: server.dc1.consul |
||||
Peer CA Pems: 0 |
||||
Peer Server Addresses: |
||||
10.0.0.1:8300 |
||||
|
||||
Imported Services: 0 |
||||
Exported Services: 2 |
||||
|
||||
Create Index: 89 |
||||
Modify Index: 89 |
||||
``` |
||||
|
Loading…
Reference in new issue