Skip to content

Commit

Permalink
add dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
hanxiaop committed Feb 22, 2024
1 parent 4c79ef9 commit 5e9098d
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 0 deletions.
2 changes: 2 additions & 0 deletions internal/cmd/egctl/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ func (fw *fakePortForwarder) Address() string {
return fmt.Sprintf("localhost:%d", fw.localPort)
}

func (fw *fakePortForwarder) WaitForStop() {}

func TestExtractAllConfigDump(t *testing.T) {
input, err := readInputConfig("in.all.json")
require.NoError(t, err)
Expand Down
23 changes: 23 additions & 0 deletions internal/cmd/egctl/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package egctl

import (
"github.com/spf13/cobra"
)

func newDashboardCommand() *cobra.Command {
c := &cobra.Command{
Use: "dashboard",
Aliases: []string{"d"},
Long: "Retrieve the dashboard.",
Short: "Retrieve the dashboard.",
}

c.AddCommand(newEnvoyDashboardCmd())

return c
}
128 changes: 128 additions & 0 deletions internal/cmd/egctl/envoy_dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package egctl

import (
"errors"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"runtime"

kube "github.com/envoyproxy/gateway/internal/kubernetes"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
)

func newEnvoyDashboardCmd() *cobra.Command {
var podName, podNamespace string
var listenPort int

dashboardCmd := &cobra.Command{
Use: "envoy-proxy <name> -n <namespace>",
Short: "Retrieves Envoy admin dashboard for the specified pod",
Long: `Retrieve Envoy admin dashboard for the specified pod.`,
Example: ` # Retrieve Envoy admin dashboard for the specified pod.
egctl experimental dashboard envoy-proxy <pod-name> -n <namespace>
# short syntax
egctl experimental d envoy-proxy <pod-name> -n <namespace>
`,
Aliases: []string{"d"},
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 && len(labelSelectors) == 0 {
cmd.Println(cmd.UsageString())
return fmt.Errorf("dashboard requires pod name or label selector")
}
if len(args) > 0 && labelSelectors != nil {
cmd.Println(cmd.UsageString())
return fmt.Errorf("name cannot be provided when a selector is specified")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
kubeClient, err := getCLIClient()
if err != nil {
return err
}
if len(args) != 0 {
podName = args[0]
}
if len(labelSelectors) > 0 {
pl, err := kubeClient.PodsForSelector(podNamespace, labelSelectors...)
if err != nil {
return fmt.Errorf("not able to locate pod with selector %s: %v", labelSelectors, err)
}
if len(pl.Items) < 1 {
return errors.New("no pods found")
}
podName = pl.Items[0].Name
podNamespace = pl.Items[0].Namespace
}

return portForward(podName, podNamespace, "http://%s", adminPort, kubeClient, c.OutOrStdout())
},
}
dashboardCmd.PersistentFlags().StringArrayVarP(&labelSelectors, "labels", "l", nil, "Labels to select the envoy proxy pod.")
dashboardCmd.PersistentFlags().StringVarP(&podNamespace, "namespace", "n", "envoy-gateway-system", "Namespace where envoy proxy pod are installed.")
dashboardCmd.PersistentFlags().IntVarP(&listenPort, "port", "p", 0, "Local port to listen to.")

return dashboardCmd
}

// portForward first tries to forward localhost:remotePort to podName:remotePort, falls back to dynamic local port
func portForward(podName, namespace, urlFormat string, listenPort int, client kube.CLIClient, writer io.Writer) error {
var fw kube.PortForwarder
meta := types.NamespacedName{
Namespace: namespace,
Name: podName,
}
fw, err := kube.NewLocalPortForwarder(client, meta, listenPort, adminPort)
if err != nil {
return fmt.Errorf("could not build port forwarder for envoy proxy: %v", err)
}

if err = fw.Start(); err != nil {
fw.Stop()
return fmt.Errorf("could not start port forwarder for envoy proxy: %v", err)
}

ClosePortForwarderOnInterrupt(fw)

openBrowser(fmt.Sprintf(urlFormat, fw.Address()), writer)

fw.WaitForStop()

return nil
}

func ClosePortForwarderOnInterrupt(fw kube.PortForwarder) {
go func() {
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
defer signal.Stop(signals)
<-signals
fw.Stop()
}()
}

func openBrowser(url string, writer io.Writer) {
var err error

fmt.Fprintf(writer, "%s\n", url)

switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
fmt.Fprintf(writer, "Unsupported platform %q; open %s in your browser.\n", runtime.GOOS, url)
}

if err != nil {
fmt.Fprintf(writer, "Failed to open browser; open %s in your browser.\n", url)
}
}
1 change: 1 addition & 0 deletions internal/cmd/egctl/experimental.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func newExperimentalCommand() *cobra.Command {
experimentalCommand.AddCommand(newTranslateCommand())
experimentalCommand.AddCommand(newStatsCommand())
experimentalCommand.AddCommand(newStatusCommand())
experimentalCommand.AddCommand(newDashboardCommand())

return experimentalCommand
}
6 changes: 6 additions & 0 deletions internal/kubernetes/port-forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type PortForwarder interface {

Stop()

WaitForStop()

// Address returns the address of the local forwarded address.
Address() string
}
Expand Down Expand Up @@ -128,6 +130,10 @@ func (f *localForwarder) Stop() {
close(f.stopCh)
}

func (f *localForwarder) WaitForStop() {
<-f.stopCh
}

func (f *localForwarder) Address() string {
return fmt.Sprintf("%s:%d", netutil.DefaultLocalAddress, f.localPort)
}

0 comments on commit 5e9098d

Please sign in to comment.