feat(cli): add initial peering cli commands

This commit is contained in:
DanStough 2022-08-31 12:58:41 -04:00 committed by Dan Stough
parent 3cfea70273
commit 1fe98bbe0b
25 changed files with 1780 additions and 16 deletions

3
.changelog/14423.txt Normal file
View File

@ -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.
```

View File

@ -98,6 +98,10 @@ func (f *HTTPFlags) Datacenter() string {
return f.datacenter.String()
}
func (f *HTTPFlags) Partition() string {
return f.partition.String()
}
func (f *HTTPFlags) Stale() bool {
if f.stale.v == nil {
return false

View File

@ -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
`
)

View File

@ -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")
})
}

View File

@ -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>
`
)

View File

@ -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)
})
}

View File

@ -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
`
)

View File

@ -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\"")
})
}

View File

@ -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] }

View File

@ -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)
})
}

View File

@ -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.
`

View File

@ -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
`
)

View File

@ -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"])
})
}

View File

@ -96,6 +96,12 @@ import (
operraft "github.com/hashicorp/consul/command/operator/raft"
operraftlist "github.com/hashicorp/consul/command/operator/raft/listpeers"
operraftremove "github.com/hashicorp/consul/command/operator/raft/removepeer"
"github.com/hashicorp/consul/command/peering"
peerdelete "github.com/hashicorp/consul/command/peering/delete"
peerestablish "github.com/hashicorp/consul/command/peering/establish"
peergenerate "github.com/hashicorp/consul/command/peering/generate"
peerlist "github.com/hashicorp/consul/command/peering/list"
peerread "github.com/hashicorp/consul/command/peering/read"
"github.com/hashicorp/consul/command/reload"
"github.com/hashicorp/consul/command/rtt"
"github.com/hashicorp/consul/command/services"
@ -214,6 +220,12 @@ func RegisteredCommands(ui cli.Ui) map[string]mcli.CommandFactory {
entry{"operator raft", func(cli.Ui) (cli.Command, error) { return operraft.New(), nil }},
entry{"operator raft list-peers", func(ui cli.Ui) (cli.Command, error) { return operraftlist.New(ui), nil }},
entry{"operator raft remove-peer", func(ui cli.Ui) (cli.Command, error) { return operraftremove.New(ui), nil }},
entry{"peering", func(cli.Ui) (cli.Command, error) { return peering.New(), nil }},
entry{"peering delete", func(ui cli.Ui) (cli.Command, error) { return peerdelete.New(ui), nil }},
entry{"peering generate-token", func(ui cli.Ui) (cli.Command, error) { return peergenerate.New(ui), nil }},
entry{"peering establish", func(ui cli.Ui) (cli.Command, error) { return peerestablish.New(ui), nil }},
entry{"peering list", func(ui cli.Ui) (cli.Command, error) { return peerlist.New(ui), nil }},
entry{"peering read", func(ui cli.Ui) (cli.Command, error) { return peerread.New(ui), nil }},
entry{"reload", func(ui cli.Ui) (cli.Command, error) { return reload.New(ui), nil }},
entry{"rtt", func(ui cli.Ui) (cli.Command, error) { return rtt.New(ui), nil }},
entry{"services", func(cli.Ui) (cli.Command, error) { return services.New(), nil }},

View File

@ -11,7 +11,9 @@ import (
type rpcFn func(string, interface{}, interface{}) error
// WaitForLeader ensures we have a leader and a node registration.
// WaitForLeader ensures we have a leader and a node registration. It
// does not wait for the Consul (node) service to be ready. Use `WaitForTestAgent`
// to make sure the Consul service is ready.
//
// Most uses of this would be better served in the agent/consul package by
// using waitForLeaderEstablishment() instead.
@ -91,7 +93,8 @@ func flattenOptions(options []waitOption) waitOption {
return flat
}
// WaitForTestAgent ensures we have a node with serfHealth check registered
// WaitForTestAgent ensures we have a node with serfHealth check registered.
// You'll want to use this if you expect the Consul (node) service to be ready.
func WaitForTestAgent(t *testing.T, rpc rpcFn, dc string, options ...waitOption) {
t.Helper()

View File

@ -42,12 +42,9 @@ The table below shows this endpoint's support for
- `Partition` `(string: "")` - <EnterpriseAlert inline /> The admin partition that the
peering token is generated from. Uses `default` when not specified.
- `Datacenter` `(string: "")` - Specifies the datacenter where the peering token is generated. Defaults to the
agent's datacenter when not specified.
- `Token` `(string: "")` - Specifies the ACL token to use in the request. Takes precedence
over the token specified in the `token` query parameter, `X-Consul-Token` request header,
and `CONSUL_HTTP_TOKEN` environment variable.
- `ServerExternalAddresses` `([]string: <optional>)` - A list of addresses to put
into the generated token. Addresses are 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.
- `Meta` `(map<string|string>: <optional>)` - Specifies KV metadata to associate with
the peering. This parameter is not required and does not directly impact the cluster
@ -116,13 +113,6 @@ The table below shows this endpoint's support for
- `PeeringToken` `(string: <required>)` - The peering token fetched from the
peer cluster.
- `Datacenter` `(string: "")` - Specifies the datacenter where the peering token is generated. Defaults to the
agent's datacenter when not specified.
- `Token` `(string: "")` - Specifies the ACL token to use in the request. Takes precedence
over the token specified in the `token` query parameter, `X-Consul-Token` request header,
and `CONSUL_HTTP_TOKEN` environment variable.
- `Meta` `(map<string|string>: <optional>)` - Specifies KV metadata to associate with
the peering. This parameter is not required and does not directly impact the cluster
peering process.
@ -314,6 +304,6 @@ $ curl --header "X-Consul-Token: 0137db51-5895-4c25-b6cd-d9ed992f4a52" \
},
"CreateIndex": 109,
"ModifyIndex": 119
},
}
]
```

View File

@ -50,6 +50,7 @@ Available commands are:
members Lists the members of a Consul cluster
monitor Stream logs from a Consul agent
operator Provides cluster-level tools for Consul operators
peering Create and manage peering connections between Consul clusters
reload Triggers the agent to reload configuration files
rtt Estimates network round trip time between nodes
services Interact with services

View File

@ -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
```

View File

@ -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
```

View File

@ -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==
```

View File

@ -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)

View File

@ -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
```

View File

@ -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
```

View File

@ -57,6 +57,19 @@ Create a JSON file that contains the first cluster's name and the peering token.
</CodeBlockConfig>
</Tab>
<Tab heading="Consul CLI">
In `cluster-01`, use the [`consul peering generate-token` command](/commands/operator/generate-token) to issue a request for a peering token.
```shell-session
$ consul peering generate-token -name cluster-02
```
The CLI outputs the peering token, which is a base64-encoded string containing the token details.
Save this value to a file or clipboard to be used in the next step on `cluster-02`.
</Tab>
<Tab heading="Consul UI">
1. In the Consul UI for the datacenter associated with `cluster-01`, click **Peers**.
@ -88,6 +101,25 @@ You can dial the `peering/establish` endpoint once per peering token. Peering to
</Tab>
<Tab heading="Consul CLI">
In one of the client agents in "cluster-02," issue the [`consul peering establish` command](/commands/peering/establish) and specify the token generated in the previous step. The command establishes the peering connection.
The commands prints "Successfully established peering connection with cluster-01" after the connection is established.
```shell-session
$ consul peering establish -name cluster-01 -peering-token token-from-generate
```
When you connect server agents through cluster peering, they peer their default partitions.
To establish peering connections for other partitions through server agents, you must add the `-partition` flag to the `establish` command and specify the partitions you want to peer.
For additional configuration information, refer to [`consul peering establish` command](/commands/peering/establish) .
You can run the `peering establish` command once per peering token.
Peering tokens cannot be reused after being used to establish a connection.
If you need to re-establish a connection, you must generate a new peering token.
</Tab>
<Tab heading="Consul UI">
1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**.
@ -213,6 +245,20 @@ $ curl http://127.0.0.1:8500/v1/peerings
```
</Tab>
<Tab heading="Consul CLI">
After you establish a peering connection, run the [`consul peering list`](/commands/peering/list) command to get a list of all peering connections.
For example, the following command requests a list of all peering connections and returns the information in a table:
```shell-session
$ consul peerings list
Name State Imported Svcs Exported Svcs Meta
cluster-02 ACTIVE 0 2 env=production
cluster-03 PENDING 0 0
```
</Tab>
<Tab heading="Consul UI">
In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in a datacenter.
@ -248,6 +294,35 @@ $ curl http://127.0.0.1:8500/v1/peering/cluster-02
```
</Tab>
<Tab heading="Consul CLI">
After you establish a peering connection, run the [`consul peering read`](/commands/peering/list) command to get peering information about for a specific cluster.
For example, the following command requests peering connection information for "cluster-02":
```shell-session
$ 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
```
</Tab>
<Tab heading="Consul UI">
In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter. Click the name of a peered cluster to view additional details about the peering connection.
@ -281,6 +356,17 @@ $ curl --request DELETE http://127.0.0.1:8500/v1/peering/cluster-02
```
</Tab>
<Tab heading="Consul CLI">
In "cluster-01," request the deletion through the [`consul peering delete`](/commands/peering/list) command.
```shell-session
$ consul peering delete -name cluster-02
Successfully submitted peering connection, cluster-02, for deletion
```
</Tab>
<Tab heading="Consul UI">
In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter.

View File

@ -436,6 +436,35 @@
"title": "partition",
"path": "partition"
},
{
"title": "peering",
"routes": [
{
"title": "Overview",
"path": "peering"
},
{
"title": "delete",
"path": "peering/delete"
},
{
"title": "establish",
"path": "peering/establish"
},
{
"title": "generate-token",
"path": "peering/generate-token"
},
{
"title": "list",
"path": "peering/list"
},
{
"title": "read",
"path": "peering/read"
}
]
},
{
"title": "reload",
"path": "reload"