Skip to content

Commit

Permalink
opt: hgctl dashboard/completion optimize (alibaba#677)
Browse files Browse the repository at this point in the history
Signed-off-by: sjcsjc123 <[email protected]>
  • Loading branch information
sjcsjc123 authored Dec 13, 2023
1 parent 518d8df commit c55a5b9
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 19 deletions.
232 changes: 232 additions & 0 deletions pkg/cmd/hgctl/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hgctl

import (
"fmt"
"io"
"os"
"path/filepath"

"github.com/spf13/cobra"
)

const completionDesc = `
Generate autocompletion scripts for hgctl for the specified shell.
`

const bashCompDesc = `
Generate the autocompletion script for the bash shell.
This script depends on the 'bash-completion' package.
If it is not installed already, you can install it via your OS's package manager.
To load completions in your current shell session:
source <(hgctl completion bash)
To load completions for every new session, execute once:
#### Linux:
hgctl completion bash > /etc/bash_completion.d/hgctl
#### macOS:
hgctl completion bash > $(brew --prefix)/etc/bash_completion.d/hgctl
You will need to start a new shell for this setup to take effect.
`

const zshCompDesc = `
Generate the autocompletion script for the zsh shell.
If shell completion is not already enabled in your environment you will need
to enable it. You can execute the following once:
echo "autoload -U compinit; compinit" >> ~/.zshrc
To load completions in your current shell session:
source <(hgctl completion zsh); compdef _hgctl hgctl
To load completions for every new session, execute once:
#### Linux:
hgctl completion zsh > "${fpath[1]}/_hgctl"
#### macOS:
hgctl completion zsh > $(brew --prefix)/share/zsh/site-functions/_hgctl
You will need to start a new shell for this setup to take effect.
`

const fishCompDesc = `
Generate the autocompletion script for the fish shell.
To load completions in your current shell session:
hgctl completion fish | source
To load completions for every new session, execute once:
hgctl completion fish > ~/.config/fish/completions/hgctl.fish
You will need to start a new shell for this setup to take effect.
`

const powershellCompDesc = `
Generate the autocompletion script for powershell.
To load completions in your current shell session:
hgctl completion powershell | Out-String | Invoke-Expression
To load completions for every new session, add the output of the above command
to your powershell profile.
`

const (
noDescFlagName = "no-descriptions"
noDescFlagText = "disable completion descriptions"
)

var disableCompDescriptions bool

// newCompletionCmd creates a new completion command for hgctl
func newCompletionCmd(out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "completion",
Short: "generate autocompletion scripts for the specified shell",
Long: completionDesc,
Args: cobra.NoArgs,
}

bash := &cobra.Command{
Use: "bash",
Short: "generate autocompletion script for bash",
Long: bashCompDesc,
Args: cobra.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionBash(out, cmd)
},
}
bash.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)

zsh := &cobra.Command{
Use: "zsh",
Short: "generate autocompletion script for zsh",
Long: zshCompDesc,
Args: cobra.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionZsh(out, cmd)
},
}
zsh.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)

fish := &cobra.Command{
Use: "fish",
Short: "generate autocompletion script for fish",
Long: fishCompDesc,
Args: cobra.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionFish(out, cmd)
},
}
fish.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)

powershell := &cobra.Command{
Use: "powershell",
Short: "generate autocompletion script for powershell",
Long: powershellCompDesc,
Args: cobra.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
return runCompletionPowershell(out, cmd)
},
}
powershell.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)

cmd.AddCommand(bash, zsh, fish, powershell)

return cmd
}

func runCompletionBash(out io.Writer, cmd *cobra.Command) error {
err := cmd.Root().GenBashCompletionV2(out, !disableCompDescriptions)

// In case the user renamed the hgctl binary, we hook the new binary name to the completion function
if binary := filepath.Base(os.Args[0]); binary != "hgctl" {
renamedBinaryHook := `
# Hook the command used to generate the completion script
# to the hgctl completion function to handle the case where
# the user renamed the hgctl binary
if [[ $(type -t compopt) = "builtin" ]]; then
complete -o default -F __start_hgctl %[1]s
else
complete -o default -o nospace -F __start_hgctl %[1]s
fi
`
fmt.Fprintf(out, renamedBinaryHook, binary)
}

return err
}

func runCompletionZsh(out io.Writer, cmd *cobra.Command) error {
var err error
if disableCompDescriptions {
err = cmd.Root().GenZshCompletionNoDesc(out)
} else {
err = cmd.Root().GenZshCompletion(out)
}

// In case the user renamed the hgctl binary, we hook the new binary name to the completion function
if binary := filepath.Base(os.Args[0]); binary != "hgctl" {
renamedBinaryHook := `
# Hook the command used to generate the completion script
# to the hgctl completion function to handle the case where
# the user renamed the hgctl binary
compdef _hgctl %[1]s
`
fmt.Fprintf(out, renamedBinaryHook, binary)
}

// Cobra doesn't source zsh completion file, explicitly doing it here
fmt.Fprintf(out, "compdef _hgctl hgctl")

return err
}

func runCompletionFish(out io.Writer, cmd *cobra.Command) error {
return cmd.Root().GenFishCompletion(out, !disableCompDescriptions)
}

func runCompletionPowershell(out io.Writer, cmd *cobra.Command) error {
if disableCompDescriptions {
return cmd.Root().GenPowerShellCompletion(out)
}
return cmd.Root().GenPowerShellCompletionWithDesc(out)
}

// Function to disable file completion
func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
2 changes: 1 addition & 1 deletion pkg/cmd/hgctl/config_retriever.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func portForwarder(nn types.NamespacedName) (kubernetes.PortForwarder, error) {
return nil, fmt.Errorf("pod %s is not running", nn)
}

fw, err := kubernetes.NewLocalPortForwarder(c, nn, 0, defaultProxyAdminPort)
fw, err := kubernetes.NewLocalPortForwarder(c, nn, 0, defaultProxyAdminPort, bindAddress)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/hgctl/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type fakePortForwarder struct {
}

func newFakePortForwarder(b []byte) (kubernetes.PortForwarder, error) {
p, err := kubernetes.LocalAvailablePort()
p, err := kubernetes.LocalAvailablePort("localhost")
if err != nil {
return nil, err
}
Expand Down
51 changes: 46 additions & 5 deletions pkg/cmd/hgctl/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@
package hgctl

import (
"context"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"runtime"
"strings"

"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"
types2 "github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -49,6 +54,8 @@ var (
envoyDashNs = ""

proxyAdminPort int

docker = false
)

const (
Expand Down Expand Up @@ -81,6 +88,7 @@ func newDashboardCmd() *cobra.Command {
"Default is true which means hgctl dashboard will always open a browser to view the dashboard.")
dashboardCmd.PersistentFlags().StringVarP(&addonNamespace, "namespace", "n", "higress-system",
"Namespace where the addon is running, if not specified, higress-system would be used")
dashboardCmd.PersistentFlags().StringVarP(&bindAddress, "listen", "l", "localhost", "The address to bind to")

prom := promDashCmd()
prom.PersistentFlags().IntVar(&promPort, "ui-port", defaultPrometheusPort, "The component dashboard UI port.")
Expand All @@ -99,6 +107,7 @@ func newDashboardCmd() *cobra.Command {

consoleCmd := consoleDashCmd()
consoleCmd.PersistentFlags().IntVar(&consolePort, "ui-port", defaultConsolePort, "The component dashboard UI port.")
consoleCmd.PersistentFlags().BoolVar(&docker, "docker", false, "Search higress console from docker")
dashboardCmd.AddCommand(consoleCmd)

controllerDebugCmd := controllerDebugCmd()
Expand Down Expand Up @@ -156,18 +165,23 @@ func consoleDashCmd() *cobra.Command {
hgctl dash console
hgctl d console`,
RunE: func(cmd *cobra.Command, args []string) error {
if docker {
return accessDocker(cmd)
}
client, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return fmt.Errorf("build CLI client fail: %w", err)
fmt.Printf("build kubernetes CLI client fail: %v\ntry to access docker container\n", err)
return accessDocker(cmd)
}

pl, err := client.PodsForSelector(addonNamespace, "app.kubernetes.io/name=higress-console")
if err != nil {
return fmt.Errorf("not able to locate console pod: %v", err)
fmt.Printf("build kubernetes CLI client fail: %v\ntry to access docker container\n", err)
return accessDocker(cmd)
}

if len(pl.Items) < 1 {
return errors.New("no higress console pods found")
fmt.Printf("no higress console pods found\ntry to access docker container\n")
return accessDocker(cmd)
}

// only use the first pod in the list
Expand All @@ -179,6 +193,32 @@ func consoleDashCmd() *cobra.Command {
return cmd
}

// accessDocker access docker container
func accessDocker(cmd *cobra.Command) error {
dockerCli, err := command.NewDockerCli(command.WithCombinedStreams(os.Stdout))
if err != nil {
return fmt.Errorf("build docker CLI client fail: %w", err)
}
err = dockerCli.Initialize(flags.NewClientOptions())
if err != nil {
return fmt.Errorf("docker client initialize fail: %w", err)
}
apiClient := dockerCli.Client()
list, err := apiClient.ContainerList(context.Background(), types2.ContainerListOptions{})
for _, container := range list {
for i, name := range container.Names {
if strings.Contains(name, "higress-console") {
port := container.Ports[i].PublicPort
// not support define ip address
url := fmt.Sprintf("http://localhost:%d", port)
openBrowser(url, cmd.OutOrStdout(), browser)
return nil
}
}
}
return errors.New("no higress console container found")
}

// port-forward to Higress System Grafana; open browser
func grafanaDashCmd() *cobra.Command {
cmd := &cobra.Command{
Expand Down Expand Up @@ -324,7 +364,7 @@ func portForward(podName, namespace, flavor, urlFormat, localAddress string, rem
var err error
for _, localPort := range portPrefs {
var fw kubernetes.PortForwarder
fw, err = kubernetes.NewLocalPortForwarder(client, types.NamespacedName{Namespace: namespace, Name: podName}, localPort, remotePort)
fw, err = kubernetes.NewLocalPortForwarder(client, types.NamespacedName{Namespace: namespace, Name: podName}, localPort, remotePort, bindAddress)
if err != nil {
return fmt.Errorf("could not build port forwarder for %s: %v", flavor, err)
}
Expand Down Expand Up @@ -378,6 +418,7 @@ func openBrowser(url string, writer io.Writer, browser bool) {
default:
fmt.Fprintf(writer, "Unsupported platform %q; open %s in your browser.\n", runtime.GOOS, url)
}

}

func openCommand(writer io.Writer, command string, args ...string) {
Expand Down
Loading

0 comments on commit c55a5b9

Please sign in to comment.