Skip to content

Commit

Permalink
Add proxy list and proxy read commands to Consul on Kubernetes CLI
Browse files Browse the repository at this point in the history
* Add a `PortForward` struct which enables the CLI to port forward `localhost` to Kubernetes Pods.
* Add a command, `consul-k8s proxy list`, which lists all Kubernetes Pods running Envoy proxies managed by Consul.
* Add a command, `consul-k8s proxy read <podname>`, which prints a summary of the Envoy configuration for the proxy running on a given Pod.
* Add behavior testing the new commands to the existing Connect Inject acceptance tests.
  • Loading branch information
Thomas Eckert authored Aug 9, 2022
1 parent bfd6407 commit 2d3ca40
Show file tree
Hide file tree
Showing 34 changed files with 5,842 additions and 151 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
FEATURES:
* Transparent Proxy Egress
* Add support for Destinations on the Service Defaults CRD. [[GH-1352](https://github.com/hashicorp/consul-k8s/pull/1352)]
* CLI:
* Add `consul-k8s proxy list` command for displaying Pods running Envoy managed by Consul. [[GH-1271](https://github.com/hashicorp/consul-k8s/pull/1271)
* Add `consul-k8s proxy read podname` command for displaying Envoy configuration for a given Pod. [[GH-1271](https://github.com/hashicorp/consul-k8s/pull/1271)
* [Experimental] Cluster Peering:
* Add support for ACLs and TLS. [[GH-1343](https://github.com/hashicorp/consul-k8s/pull/1343)] [[GH-1366](https://github.com/hashicorp/consul-k8s/pull/1366)]
* Add support for Load Balancers or external addresses in front of Consul servers for peering stream.
Expand Down
31 changes: 31 additions & 0 deletions acceptance/framework/cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cli

import (
"fmt"
"os/exec"

"github.com/hashicorp/consul-k8s/acceptance/framework/config"
)

// CLI provides access to compile and execute commands with the `consul-k8s` CLI.
type CLI struct {
initialized bool
}

// NewCLI compiles the `consul-k8s` CLI and returns a handle to execute commands
// with the binary.
func NewCLI() (*CLI, error) {
cmd := exec.Command("go", "install", ".")
cmd.Dir = config.CLIPath
_, err := cmd.Output()
return &CLI{true}, err
}

// Run runs the CLI with the given args.
func (c *CLI) Run(args ...string) ([]byte, error) {
if !c.initialized {
return nil, fmt.Errorf("CLI must be initialized before calling Run, use `cli.NewCLI()` to initialize.")
}
cmd := exec.Command("cli", args...)
return cmd.Output()
}
26 changes: 13 additions & 13 deletions acceptance/framework/consul/cli_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package consul
import (
"context"
"fmt"
"os/exec"
"strings"
"testing"
"time"

"github.com/gruntwork-io/terratest/modules/helm"
terratestk8s "github.com/gruntwork-io/terratest/modules/k8s"
terratestLogger "github.com/gruntwork-io/terratest/modules/logger"
"github.com/hashicorp/consul-k8s/acceptance/framework/cli"
"github.com/hashicorp/consul-k8s/acceptance/framework/config"
"github.com/hashicorp/consul-k8s/acceptance/framework/environment"
"github.com/hashicorp/consul-k8s/acceptance/framework/helpers"
Expand Down Expand Up @@ -44,6 +44,7 @@ type CLICluster struct {
noCleanupOnFailure bool
debugDirectory string
logger terratestLogger.TestLogger
cli cli.CLI
}

// NewCLICluster creates a new Consul cluster struct which can be used to create
Expand Down Expand Up @@ -90,6 +91,9 @@ func NewCLICluster(
Logger: logger,
}

cli, err := cli.NewCLI()
require.NoError(t, err)

return &CLICluster{
ctx: ctx,
helmOptions: hopts,
Expand All @@ -103,6 +107,7 @@ func NewCLICluster(
noCleanupOnFailure: cfg.NoCleanupOnFailure,
debugDirectory: cfg.DebugDirectory,
logger: logger,
cli: *cli,
}
}

Expand All @@ -129,7 +134,7 @@ func (c *CLICluster) Create(t *testing.T) {
args = append(args, "-timeout", "15m")
args = append(args, "-auto-approve")

out, err := c.runCLI(args)
out, err := c.cli.Run(args...)
if err != nil {
c.logger.Logf(t, "error running command `consul-k8s %s`: %s", strings.Join(args, " "), err.Error())
c.logger.Logf(t, "command stdout: %s", string(out))
Expand Down Expand Up @@ -162,7 +167,7 @@ func (c *CLICluster) Upgrade(t *testing.T, helmValues map[string]string) {
args = append(args, "-timeout", "15m")
args = append(args, "-auto-approve")

out, err := c.runCLI(args)
out, err := c.cli.Run(args...)
if err != nil {
c.logger.Logf(t, "error running command `consul-k8s %s`: %s", strings.Join(args, " "), err.Error())
c.logger.Logf(t, "command stdout: %s", string(out))
Expand All @@ -186,7 +191,7 @@ func (c *CLICluster) Destroy(t *testing.T) {
// Use `go run` so that the CLI is recompiled and therefore uses the local
// charts directory rather than the directory from whenever it was last
// compiled.
out, err := c.runCLI(args)
out, err := c.cli.Run(args...)
if err != nil {
c.logger.Logf(t, "error running command `consul-k8s %s`: %s", strings.Join(args, " "), err.Error())
c.logger.Logf(t, "command stdout: %s", string(out))
Expand Down Expand Up @@ -267,6 +272,10 @@ func (c *CLICluster) SetupConsulClient(t *testing.T, secure bool) (*api.Client,
return consulClient, config.Address
}

func (c *CLICluster) CLI() cli.CLI {
return c.cli
}

func createOrUpdateNamespace(t *testing.T, client kubernetes.Interface, namespace string) {
_, err := client.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{})
if errors.IsNotFound(err) {
Expand Down Expand Up @@ -296,12 +305,3 @@ func (c *CLICluster) setKube(args []string) []string {

return args
}

// runCLI runs the CLI with the given args.
// Use `go run` so that the CLI is recompiled and therefore uses the local
// charts directory rather than the directory from whenever it was last compiled.
func (c *CLICluster) runCLI(args []string) ([]byte, error) {
cmd := exec.Command("go", append([]string{"run", "."}, args...)...)
cmd.Dir = config.CLIPath
return cmd.Output()
}
59 changes: 59 additions & 0 deletions acceptance/tests/connect/connect_inject_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"
"time"

"github.com/hashicorp/consul-k8s/acceptance/framework/cli"
"github.com/hashicorp/consul-k8s/acceptance/framework/consul"
"github.com/hashicorp/consul-k8s/acceptance/framework/helpers"
"github.com/hashicorp/consul-k8s/acceptance/framework/k8s"
Expand All @@ -18,6 +19,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const ipv4RegEx = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"

// TestConnectInject tests that Connect works in a default and a secure installation.
func TestConnectInject(t *testing.T) {
cases := map[string]struct {
Expand Down Expand Up @@ -60,6 +63,9 @@ func TestConnectInject(t *testing.T) {

for name, c := range cases {
t.Run(name, func(t *testing.T) {
cli, err := cli.NewCLI()
require.NoError(t, err)

cfg := suite.Config()
ctx := suite.Environment().DefaultContext(t)

Expand All @@ -80,6 +86,40 @@ func TestConnectInject(t *testing.T) {
connHelper.TestConnectionFailureWithoutIntention(t)
connHelper.CreateIntention(t)
}

// Run proxy list and get the two results.
listOut, err := cli.Run("proxy", "list")
require.NoError(t, err)
logger.Log(t, string(listOut))
list := translateListOutput(listOut)
require.Equal(t, 2, len(list))
for _, proxyType := range list {
require.Equal(t, "Sidecar", proxyType)
}

// Run proxy read and check that the connection is present in the output.
retrier := &retry.Timer{Timeout: 160 * time.Second, Wait: 2 * time.Second}
retry.RunWith(retrier, t, func(r *retry.R) {
for podName := range list {
out, err := cli.Run("proxy", "read", podName)
require.NoError(t, err)

output := string(out)
logger.Log(t, output)

// Both proxies must see their own local agent and app as clusters.
require.Regexp(r, "local_agent.*STATIC", output)
require.Regexp(r, "local_app.*STATIC", output)

// Static Client must have Static Server as a cluster and endpoint.
if strings.Contains(podName, "static-client") {
require.Regexp(r, "static-server.*static-server\\.default\\.dc1\\.internal.*EDS", output)
require.Regexp(r, ipv4RegEx+".*static-server.default.dc1.internal", output)
}

}
})

connHelper.TestConnectionSuccess(t)
connHelper.TestConnectionFailureWhenUnhealthy(t)
})
Expand Down Expand Up @@ -428,3 +468,22 @@ func TestConnectInject_MultiportServices(t *testing.T) {
})
}
}

// translateListOutput takes the raw output from the proxy list command and
// translates the table into a map.
func translateListOutput(raw []byte) map[string]string {
formatted := make(map[string]string)
for _, pod := range strings.Split(strings.TrimSpace(string(raw)), "\n")[3:] {
row := strings.Split(strings.TrimSpace(pod), "\t")

var name string
if len(row) == 3 { // Handle the case where namespace is present
name = fmt.Sprintf("%s/%s", strings.TrimSpace(row[0]), strings.TrimSpace(row[1]))
} else if len(row) == 2 {
name = strings.TrimSpace(row[0])
}
formatted[name] = row[len(row)-1]
}

return formatted
}
3 changes: 0 additions & 3 deletions cli/cmd/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,6 @@ func (c *Command) init() {
})

c.help = c.set.Help()

// c.Init() calls the embedded BaseCommand's initialization function.
c.Init()
}

// Run installs Consul into a Kubernetes cluster.
Expand Down
26 changes: 26 additions & 0 deletions cli/cmd/proxy/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package proxy

import (
"fmt"

"github.com/hashicorp/consul-k8s/cli/common"
"github.com/mitchellh/cli"
)

// ProxyCommand provides a synopsis for the proxy subcommands (e.g. read).
type ProxyCommand struct {
*common.BaseCommand
}

// Run prints out information about the subcommands.
func (c *ProxyCommand) Run(args []string) int {
return cli.RunResultHelp
}

func (c *ProxyCommand) Help() string {
return fmt.Sprintf("%s\n\nUsage: consul-k8s proxy <subcommand>", c.Synopsis())
}

func (c *ProxyCommand) Synopsis() string {
return "Inspect Envoy proxies managed by Consul."
}
Loading

0 comments on commit 2d3ca40

Please sign in to comment.