Skip to content

Commit

Permalink
feat(cli): add initial peering cli commands
Browse files Browse the repository at this point in the history
  • Loading branch information
DanStough committed Sep 1, 2022
1 parent 81d7cc4 commit e617e7d
Show file tree
Hide file tree
Showing 25 changed files with 1,780 additions and 16 deletions.
3 changes: 3 additions & 0 deletions .changelog/14423.txt
@@ -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.
```
4 changes: 4 additions & 0 deletions command/flags/http.go
Expand Up @@ -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
Expand Down
91 changes: 91 additions & 0 deletions command/peering/delete/delete.go
@@ -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
`
)
70 changes: 70 additions & 0 deletions command/peering/delete/delete_test.go
@@ -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")
})
}
109 changes: 109 additions & 0 deletions command/peering/establish/establish.go
@@ -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 comments on commit e617e7d

Please sign in to comment.