From 47cab3dae477418340e5cc41cd1b03c46246a6c4 Mon Sep 17 00:00:00 2001 From: Shashank Ram Date: Wed, 21 Oct 2020 09:09:25 -0700 Subject: [PATCH 1/3] cli: add proxy command to dump config Adds a cli command to dump proxy configuration for a given pod. Resolves #1762 --- cmd/cli/osm.go | 1 + cmd/cli/proxy.go | 25 +++++++ cmd/cli/proxy_configdump.go | 122 +++++++++++++++++++++++++++++++ cmd/cli/proxy_configdump_test.go | 48 ++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 cmd/cli/proxy.go create mode 100644 cmd/cli/proxy_configdump.go create mode 100644 cmd/cli/proxy_configdump_test.go diff --git a/cmd/cli/osm.go b/cmd/cli/osm.go index e633e9f1ae..beaae94ce0 100644 --- a/cmd/cli/osm.go +++ b/cmd/cli/osm.go @@ -43,6 +43,7 @@ func newRootCmd(config *action.Configuration, in io.Reader, out io.Writer, args newNamespaceCmd(out), newMetricsCmd(out), newVersionCmd(out), + newProxyCmd(config, out), ) flags.Parse(args) diff --git a/cmd/cli/proxy.go b/cmd/cli/proxy.go new file mode 100644 index 0000000000..abb11b1c6e --- /dev/null +++ b/cmd/cli/proxy.go @@ -0,0 +1,25 @@ +package main + +import ( + "io" + + "github.com/spf13/cobra" + "helm.sh/helm/v3/pkg/action" +) + +const proxyCmdDescription = ` +This command consists of multiple subcommands related to managing the +sidecar proxy on pods. +` + +func newProxyCmd(config *action.Configuration, out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "proxy", + Short: "manage sidecar proxy", + Long: proxyCmdDescription, + Args: cobra.NoArgs, + } + cmd.AddCommand(newProxyDumpConfig(config, out)) + + return cmd +} diff --git a/cmd/cli/proxy_configdump.go b/cmd/cli/proxy_configdump.go new file mode 100644 index 0000000000..3539874825 --- /dev/null +++ b/cmd/cli/proxy_configdump.go @@ -0,0 +1,122 @@ +package main + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "helm.sh/helm/v3/pkg/action" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + + "github.com/openservicemesh/osm/pkg/constants" +) + +const dumpConfigDescription = ` +This command will dump the sidecar proxy configuration for the given pod. +` + +const dumpConfigExample = ` +# Dump the proxy configuration for pod 'bookbuyer-5ccf77f46d-rc5mg' in the 'bookbuyer' namespace +osm proxy dump-config bookbuyer-5ccf77f46d-rc5mg -n bookbuyer +` + +type proxyDumpConfigCmd struct { + out io.Writer + config *rest.Config + clientSet kubernetes.Interface + namespace string + pod string + localPort uint16 + sigintChan chan os.Signal +} + +func newProxyDumpConfig(config *action.Configuration, out io.Writer) *cobra.Command { + dumpConfigCmd := &proxyDumpConfigCmd{ + out: out, + sigintChan: make(chan os.Signal, 1), + } + + cmd := &cobra.Command{ + Use: "dump-config POD ...", + Short: "dump proxy config", + Long: dumpConfigDescription, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + dumpConfigCmd.pod = args[0] + conf, err := config.RESTClientGetter.ToRESTConfig() + if err != nil { + return errors.Errorf("Error fetching kubeconfig") + } + dumpConfigCmd.config = conf + + clientset, err := kubernetes.NewForConfig(conf) + if err != nil { + return errors.Errorf("Could not access Kubernetes cluster. Check kubeconfig") + } + dumpConfigCmd.clientSet = clientset + return dumpConfigCmd.run() + }, + Example: dumpConfigExample, + } + + //add mesh name flag + f := cmd.Flags() + f.StringVarP(&dumpConfigCmd.namespace, "namespace", "n", metav1.NamespaceDefault, "Namespace of pod") + f.Uint16VarP(&dumpConfigCmd.localPort, "local-port", "p", constants.EnvoyAdminPort, "Local port to use for port forwarding") + + return cmd +} + +func (cmd *proxyDumpConfigCmd) run() error { + // Check if the pod belongs to the mesh + pod, err := cmd.clientSet.CoreV1().Pods(cmd.namespace).Get(context.TODO(), cmd.pod, metav1.GetOptions{}) + if err != nil { + return errors.Errorf("Could not find pod %s in namespace %s", cmd.pod, cmd.namespace) + } + if !isMeshedPod(*pod) { + return errors.Errorf("Pod %s in namespace %s is not a part of a mesh", cmd.pod, cmd.namespace) + } + + portForwarder, err := NewPortForwarder(cmd.config, cmd.clientSet, cmd.pod, cmd.namespace, cmd.localPort, constants.EnvoyAdminPort) + if err != nil { + return errors.Errorf("Error setting up port forwarding: %s", err) + } + + err = portForwarder.Start(func(pf *PortForwarder) error { + url := fmt.Sprintf("http://localhost:%d/config_dump", cmd.localPort) + + // #nosec G107: Potential HTTP request made with variable url + resp, err := http.Get(url) + if err != nil { + return errors.Errorf("Error fetching url %s: %s", url, err) + } + config, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return errors.Errorf("Error retrieving proxy config: %s", err) + } + fmt.Fprintf(cmd.out, "%s", config) + pf.Stop() + return nil + }) + if err != nil { + return errors.Errorf("Error retrieving proxy config for pod %s in namespace %s: %s", cmd.pod, cmd.namespace, err) + } + + return nil +} + +// isMeshedPod returns a boolean indicating if the pod is part of a mesh +func isMeshedPod(pod corev1.Pod) bool { + // osm-controller adds a unique label to each pod that belongs to a mesh + _, proxyLabelSet := pod.Labels[constants.EnvoyUniqueIDLabelName] + return proxyLabelSet +} diff --git a/cmd/cli/proxy_configdump_test.go b/cmd/cli/proxy_configdump_test.go new file mode 100644 index 0000000000..06986b27cf --- /dev/null +++ b/cmd/cli/proxy_configdump_test.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openservicemesh/osm/pkg/constants" +) + +func TestIsMeshedPod(t *testing.T) { + assert := assert.New(t) + + type test struct { + pod corev1.Pod + isMeshed bool + } + + testCases := []test{ + { + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Labels: map[string]string{constants.EnvoyUniqueIDLabelName: "test"}, + }, + }, + isMeshed: true, + }, + { + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-2", + }, + }, + isMeshed: false, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Testing if pod %s is meshed", tc.pod.Name), func(t *testing.T) { + isMeshed := isMeshedPod(tc.pod) + assert.Equal(isMeshed, tc.isMeshed) + }) + } +} From dc56388d18e11c7d3cffee4243ca7461ce616538 Mon Sep 17 00:00:00 2001 From: Shashank Ram Date: Wed, 21 Oct 2020 12:09:04 -0700 Subject: [PATCH 2/3] address comments v1 --- cmd/cli/proxy_configdump.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/cli/proxy_configdump.go b/cmd/cli/proxy_configdump.go index 3539874825..259feee298 100644 --- a/cmd/cli/proxy_configdump.go +++ b/cmd/cli/proxy_configdump.go @@ -45,7 +45,7 @@ func newProxyDumpConfig(config *action.Configuration, out io.Writer) *cobra.Comm } cmd := &cobra.Command{ - Use: "dump-config POD ...", + Use: "dump-config POD", Short: "dump proxy config", Long: dumpConfigDescription, Args: cobra.ExactArgs(1), @@ -76,7 +76,7 @@ func newProxyDumpConfig(config *action.Configuration, out io.Writer) *cobra.Comm } func (cmd *proxyDumpConfigCmd) run() error { - // Check if the pod belongs to the mesh + // Check if the pod belongs to a mesh pod, err := cmd.clientSet.CoreV1().Pods(cmd.namespace).Get(context.TODO(), cmd.pod, metav1.GetOptions{}) if err != nil { return errors.Errorf("Could not find pod %s in namespace %s", cmd.pod, cmd.namespace) From b256ffb6c0df7c89a9211138b1c332adbf7768f0 Mon Sep 17 00:00:00 2001 From: Shashank Ram Date: Wed, 21 Oct 2020 12:17:34 -0700 Subject: [PATCH 3/3] use io.Copy --- cmd/cli/proxy_configdump.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cmd/cli/proxy_configdump.go b/cmd/cli/proxy_configdump.go index 259feee298..a92b5a80dd 100644 --- a/cmd/cli/proxy_configdump.go +++ b/cmd/cli/proxy_configdump.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "net/http" "os" @@ -98,12 +97,9 @@ func (cmd *proxyDumpConfigCmd) run() error { if err != nil { return errors.Errorf("Error fetching url %s: %s", url, err) } - config, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - return errors.Errorf("Error retrieving proxy config: %s", err) + if _, err := io.Copy(cmd.out, resp.Body); err != nil { + return errors.Errorf("Error rendering HTTP response: %s", err) } - fmt.Fprintf(cmd.out, "%s", config) pf.Stop() return nil })