diff --git a/cmd/cli/proxy_get.go b/cmd/cli/proxy_get.go index fb9b83d4c9..8f220cd9c1 100644 --- a/cmd/cli/proxy_get.go +++ b/cmd/cli/proxy_get.go @@ -1,10 +1,8 @@ package main import ( - "context" - "fmt" + "bufio" "io" - "net/http" "os" "github.com/pkg/errors" @@ -15,8 +13,8 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "github.com/openservicemesh/osm/pkg/cli" "github.com/openservicemesh/osm/pkg/constants" - "github.com/openservicemesh/osm/pkg/k8s" ) const getCmdDescription = ` @@ -86,58 +84,24 @@ func newProxyGetCmd(config *action.Configuration, out io.Writer) *cobra.Command } func (cmd *proxyGetCmd) run() error { - // 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 annotateErrMsgWithPodNamespaceMsg("Could not find pod %s in namespace %s", cmd.pod, cmd.namespace) - } - if !isMeshedPod(*pod) { - return annotateErrMsgWithPodNamespaceMsg("Pod %s in namespace %s is not a part of a mesh", cmd.pod, cmd.namespace) - } - if pod.Status.Phase != corev1.PodRunning { - return annotateErrMsgWithPodNamespaceMsg("Pod %s in namespace %s is not running", cmd.pod, cmd.namespace) - } - - dialer, err := k8s.DialerToPod(cmd.config, cmd.clientSet, cmd.pod, cmd.namespace) + envoyProxyConfig, err := cli.GetEnvoyProxyConfig(cmd.clientSet, cmd.config, cmd.namespace, cmd.pod, cmd.localPort, cmd.query) if err != nil { return err } - portForwarder, err := k8s.NewPortForwarder(dialer, fmt.Sprintf("%d:%d", cmd.localPort, constants.EnvoyAdminPort)) - if err != nil { - return errors.Errorf("Error setting up port forwarding: %s", err) - } - - err = portForwarder.Start(func(pf *k8s.PortForwarder) error { - defer pf.Stop() - url := fmt.Sprintf("http://localhost:%d/%s", cmd.localPort, cmd.query) - - // #nosec G107: Potential HTTP request made with variable url - resp, err := http.Get(url) + out := cmd.out // By default, output is written to stdout + if cmd.outFile != "" { + fd, err := os.Create(cmd.outFile) if err != nil { - return errors.Errorf("Error fetching url %s: %s", url, err) + return errors.Errorf("Error opening file %s: %s", cmd.outFile, err) } - - out := cmd.out // By default, output is written to stdout - if cmd.outFile != "" { - fd, err := os.Create(cmd.outFile) - if err != nil { - return errors.Errorf("Error opening file %s: %s", cmd.outFile, err) - } - defer fd.Close() //nolint: errcheck, gosec - out = fd // write output to file - } - - if _, err := io.Copy(out, resp.Body); err != nil { - return errors.Errorf("Error rendering HTTP response: %s", err) - } - return nil - }) - if err != nil { - return annotateErrMsgWithPodNamespaceMsg("Error retrieving proxy config for pod %s in namespace %s: %s", cmd.pod, cmd.namespace, err) + defer fd.Close() //nolint: errcheck, gosec + out = fd // write output to file } - return nil + w := bufio.NewWriter(out) + _, err = w.WriteString(string(envoyProxyConfig)) + return err } // isMeshedPod returns a boolean indicating if the pod is part of a mesh @@ -146,8 +110,3 @@ func isMeshedPod(pod corev1.Pod) bool { _, proxyLabelSet := pod.Labels[constants.EnvoyUniqueIDLabelName] return proxyLabelSet } - -// annotateProxyGetErrorMessage returns a formatted err msg with an actionable message regarding pod details -func annotateErrMsgWithPodNamespaceMsg(errMsgFormat string, args ...interface{}) error { - return annotateErrorMessageWithActionableMessage("Note: Use the flag --namespace to modify the intended pod namespace.", errMsgFormat, args...) -} diff --git a/cmd/cli/proxy_get_test.go b/cmd/cli/proxy_get_test.go deleted file mode 100644 index 33f8876d3a..0000000000 --- a/cmd/cli/proxy_get_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "fmt" - "testing" - - tassert "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) { - 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) { - assert := tassert.New(t) - - isMeshed := isMeshedPod(tc.pod) - assert.Equal(isMeshed, tc.isMeshed) - }) - } -} - -func TestAnnotateErrMsgWithPodNamespaceMsg(t *testing.T) { - type test struct { - errorMsg string - podName string - podNamespace string - annotatedMsg string - } - - podNamespaceActionableMsg := "Note: Use the flag --namespace to modify the intended pod namespace." - - testCases := []test{ - { - "Proxy get command error for pod name [%s] in pod namespace [%s]", - "test-pod-name", - "test-namespace", - "Proxy get command error for pod name [test-pod-name] in pod namespace [test-namespace]\n\n" + podNamespaceActionableMsg, - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("Testing annotated error message for pod name [%s] in pod namespace [%s]", tc.podName, tc.podNamespace), func(t *testing.T) { - assert := tassert.New(t) - - assert.Equal( - tc.annotatedMsg, - annotateErrMsgWithPodNamespaceMsg(tc.errorMsg, tc.podName, tc.podNamespace).Error()) - }) - } -} diff --git a/pkg/cli/proxy_get.go b/pkg/cli/proxy_get.go new file mode 100644 index 0000000000..3580a4a456 --- /dev/null +++ b/pkg/cli/proxy_get.go @@ -0,0 +1,78 @@ +package cli + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + + "github.com/google/uuid" + "github.com/pkg/errors" + 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" + "github.com/openservicemesh/osm/pkg/k8s" +) + +// GetEnvoyProxyConfig returns the sidecar envoy proxy config of a pod +func GetEnvoyProxyConfig(clientSet kubernetes.Interface, config *rest.Config, namespace string, podName string, localPort uint16, query string) ([]byte, error) { + // Check if the pod belongs to a mesh + pod, err := clientSet.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{}) + if err != nil { + return nil, errors.Errorf("Could not find pod %s in namespace %s", podName, namespace) + } + if !proxyLabelExists(*pod) { + return nil, errors.Errorf("Pod %s in namespace %s is not a part of a mesh", podName, namespace) + } + if pod.Status.Phase != corev1.PodRunning { + return nil, errors.Errorf("Pod %s in namespace %s is not running", podName, namespace) + } + + dialer, err := k8s.DialerToPod(config, clientSet, podName, namespace) + if err != nil { + return nil, err + } + + portForwarder, err := k8s.NewPortForwarder(dialer, fmt.Sprintf("%d:%d", localPort, constants.EnvoyAdminPort)) + if err != nil { + return nil, errors.Errorf("Error setting up port forwarding: %s", err) + } + + var envoyProxyConfig []byte + err = portForwarder.Start(func(pf *k8s.PortForwarder) error { + defer pf.Stop() + url := fmt.Sprintf("http://localhost:%d/%s", localPort, query) + + // #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) + } + + envoyProxyConfig, err = ioutil.ReadAll(resp.Body) + if err != nil { + return errors.Errorf("Error rendering HTTP response: %s", err) + } + return nil + }) + if err != nil { + return nil, errors.Errorf("Error retrieving proxy config for pod %s in namespace %s: %s", podName, namespace, err) + } + + return envoyProxyConfig, nil +} + +// proxyLabelExists returns a boolean indicating if the pod is part of a mesh +func proxyLabelExists(pod corev1.Pod) bool { + // osm-controller adds a unique label to each pod that belongs to a mesh + proxyUUID, proxyLabelSet := pod.Labels[constants.EnvoyUniqueIDLabelName] + return proxyLabelSet && isValidUUID(proxyUUID) +} + +func isValidUUID(u string) bool { + _, err := uuid.Parse(u) + return err == nil +} diff --git a/pkg/cli/proxy_get_test.go b/pkg/cli/proxy_get_test.go new file mode 100644 index 0000000000..34a2c437b1 --- /dev/null +++ b/pkg/cli/proxy_get_test.go @@ -0,0 +1,48 @@ +package cli + +import ( + "fmt" + "testing" + + tassert "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) { + 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) { + assert := tassert.New(t) + + isMeshed := proxyLabelExists(tc.pod) + assert.Equal(isMeshed, tc.isMeshed) + }) + } +}