feat(cli): add initial peering cli commands
This commit is contained in:
parent
3cfea70273
commit
1fe98bbe0b
|
@ -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.
|
||||||
|
```
|
|
@ -98,6 +98,10 @@ func (f *HTTPFlags) Datacenter() string {
|
||||||
return f.datacenter.String()
|
return f.datacenter.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *HTTPFlags) Partition() string {
|
||||||
|
return f.partition.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (f *HTTPFlags) Stale() bool {
|
func (f *HTTPFlags) Stale() bool {
|
||||||
if f.stale.v == nil {
|
if f.stale.v == nil {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -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"])
|
||||||
|
})
|
||||||
|
}
|
|
@ -96,6 +96,12 @@ import (
|
||||||
operraft "github.com/hashicorp/consul/command/operator/raft"
|
operraft "github.com/hashicorp/consul/command/operator/raft"
|
||||||
operraftlist "github.com/hashicorp/consul/command/operator/raft/listpeers"
|
operraftlist "github.com/hashicorp/consul/command/operator/raft/listpeers"
|
||||||
operraftremove "github.com/hashicorp/consul/command/operator/raft/removepeer"
|
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/reload"
|
||||||
"github.com/hashicorp/consul/command/rtt"
|
"github.com/hashicorp/consul/command/rtt"
|
||||||
"github.com/hashicorp/consul/command/services"
|
"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", 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 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{"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{"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{"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 }},
|
entry{"services", func(cli.Ui) (cli.Command, error) { return services.New(), nil }},
|
||||||
|
|
|
@ -11,7 +11,9 @@ import (
|
||||||
|
|
||||||
type rpcFn func(string, interface{}, interface{}) error
|
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
|
// Most uses of this would be better served in the agent/consul package by
|
||||||
// using waitForLeaderEstablishment() instead.
|
// using waitForLeaderEstablishment() instead.
|
||||||
|
@ -91,7 +93,8 @@ func flattenOptions(options []waitOption) waitOption {
|
||||||
return flat
|
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) {
|
func WaitForTestAgent(t *testing.T, rpc rpcFn, dc string, options ...waitOption) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|
|
@ -42,12 +42,9 @@ The table below shows this endpoint's support for
|
||||||
- `Partition` `(string: "")` - <EnterpriseAlert inline /> The admin partition that the
|
- `Partition` `(string: "")` - <EnterpriseAlert inline /> The admin partition that the
|
||||||
peering token is generated from. Uses `default` when not specified.
|
peering token is generated from. Uses `default` when not specified.
|
||||||
|
|
||||||
- `Datacenter` `(string: "")` - Specifies the datacenter where the peering token is generated. Defaults to the
|
- `ServerExternalAddresses` `([]string: <optional>)` - A list of addresses to put
|
||||||
agent's datacenter when not specified.
|
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.
|
||||||
- `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
|
- `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
|
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
|
- `PeeringToken` `(string: <required>)` - The peering token fetched from the
|
||||||
peer cluster.
|
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
|
- `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
|
the peering. This parameter is not required and does not directly impact the cluster
|
||||||
peering process.
|
peering process.
|
||||||
|
@ -314,6 +304,6 @@ $ curl --header "X-Consul-Token: 0137db51-5895-4c25-b6cd-d9ed992f4a52" \
|
||||||
},
|
},
|
||||||
"CreateIndex": 109,
|
"CreateIndex": 109,
|
||||||
"ModifyIndex": 119
|
"ModifyIndex": 119
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
|
@ -50,6 +50,7 @@ Available commands are:
|
||||||
members Lists the members of a Consul cluster
|
members Lists the members of a Consul cluster
|
||||||
monitor Stream logs from a Consul agent
|
monitor Stream logs from a Consul agent
|
||||||
operator Provides cluster-level tools for Consul operators
|
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
|
reload Triggers the agent to reload configuration files
|
||||||
rtt Estimates network round trip time between nodes
|
rtt Estimates network round trip time between nodes
|
||||||
services Interact with services
|
services Interact with services
|
||||||
|
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
|
@ -57,6 +57,19 @@ Create a JSON file that contains the first cluster's name and the peering token.
|
||||||
</CodeBlockConfig>
|
</CodeBlockConfig>
|
||||||
</Tab>
|
</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">
|
<Tab heading="Consul UI">
|
||||||
|
|
||||||
1. In the Consul UI for the datacenter associated with `cluster-01`, click **Peers**.
|
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>
|
||||||
|
|
||||||
|
<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">
|
<Tab heading="Consul UI">
|
||||||
|
|
||||||
1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**.
|
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>
|
||||||
|
|
||||||
|
<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">
|
<Tab heading="Consul UI">
|
||||||
|
|
||||||
In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in a datacenter.
|
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>
|
||||||
|
|
||||||
|
<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">
|
<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.
|
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>
|
||||||
|
|
||||||
|
<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">
|
<Tab heading="Consul UI">
|
||||||
|
|
||||||
In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter.
|
In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter.
|
||||||
|
|
|
@ -436,6 +436,35 @@
|
||||||
"title": "partition",
|
"title": "partition",
|
||||||
"path": "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",
|
"title": "reload",
|
||||||
"path": "reload"
|
"path": "reload"
|
||||||
|
|
Loading…
Reference in New Issue