/* Copyright 2021 The Dapr Authors 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 cmd import ( "fmt" "net" "os" "os/signal" "github.com/pkg/browser" "github.com/spf13/cobra" "github.com/dapr/cli/pkg/kubernetes" "github.com/dapr/cli/pkg/print" "github.com/dapr/cli/pkg/standalone" "github.com/dapr/cli/utils" ) const ( // dashboardSvc is the name of the dashboard service running in cluster. dashboardSvc = "dapr-dashboard" // defaultHost is the default host used for port forwarding for `dapr dashboard`. defaultHost = "localhost" // defaultLocalPort is the default local port used for port forwarding for `dapr dashboard`. defaultLocalPort = 8080 // daprSystemNamespace is the namespace "dapr-system" (recommended Dapr install namespace). daprSystemNamespace = "dapr-system" // defaultNamespace is the default namespace (dapr init -k installation). defaultNamespace = "default" // remotePort is the port dapr dashboard pod is listening on. remotePort = 8080 ) var ( dashboardNamespace string dashboardHost string dashboardLocalPort int dashboardVersionCmd bool ) var DashboardCmd = &cobra.Command{ Use: "dashboard", Short: "Start Dapr dashboard. Supported platforms: Kubernetes and self-hosted", Example: ` # Start dashboard locally dapr dashboard # Start dashboard locally in a specified port dapr dashboard -p 9999 # Start dashboard locally on a random port which is free. dapr dashboard -p 0 # Port forward to dashboard in Kubernetes dapr dashboard -k # Port forward to dashboard in Kubernetes on all addresses in a specified port dapr dashboard -k -p 9999 -a 0.0.0.0 # Port forward to dashboard in Kubernetes using a port dapr dashboard -k -p 9999 # Port forward to dashboard in Kubernetes using a random port which is free. dapr dashboard -k -p 0 `, Run: func(cmd *cobra.Command, args []string) { if dashboardVersionCmd { dashboardVer, err := standalone.GetDashboardVersion(daprRuntimePath) if err != nil { print.FailureStatusEvent(os.Stderr, "Failed to get Dapr install directory: %v", err) os.Exit(1) } fmt.Println(dashboardVer) os.Exit(0) } if !utils.IsAddressLegal(dashboardHost) { print.FailureStatusEvent(os.Stdout, "Invalid address: %s", dashboardHost) os.Exit(1) } if dashboardLocalPort < 0 { print.FailureStatusEvent(os.Stderr, "Invalid port: %v", dashboardLocalPort) os.Exit(1) } if err := utils.CheckIfPortAvailable(dashboardLocalPort); err != nil { print.FailureStatusEvent(os.Stderr, "Please select a different port with %q flag: %s", "-p", err) print.InfoStatusEvent(os.Stdout, "You can also use port 0 to select a random free port.") os.Exit(1) } if kubernetesMode { config, client, err := kubernetes.GetKubeConfigClient() if err != nil { print.FailureStatusEvent(os.Stderr, "Failed to initialize kubernetes client: %s", err.Error()) os.Exit(1) } // search for dashboard service namespace in order: // user-supplied namespace, dapr-system, default. namespaces := []string{dashboardNamespace} if dashboardNamespace != daprSystemNamespace { namespaces = append(namespaces, daprSystemNamespace) } if dashboardNamespace != defaultNamespace { namespaces = append(namespaces, defaultNamespace) } foundNamespace := "" for _, namespace := range namespaces { ok, _ := kubernetes.CheckPodExists(client, namespace, nil, dashboardSvc) if ok { foundNamespace = namespace break } } // if the service is not found, try to search all pods. if foundNamespace == "" { ok, nspace := kubernetes.CheckPodExists(client, "", nil, dashboardSvc) // if the service is found, tell the user to try with the found namespace. // if the service is still not found, throw an error. if ok { print.InfoStatusEvent(os.Stdout, "Dapr dashboard found in namespace: %s. Run dapr dashboard -k -n %s to use this namespace.", nspace, nspace) } else { print.FailureStatusEvent(os.Stderr, "Failed to find Dapr dashboard in cluster. Check status of dapr dashboard in the cluster.") } os.Exit(1) } // manage termination of port forwarding connection on interrupt. signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt) defer signal.Stop(signals) portForward, err := kubernetes.NewPortForward( config, foundNamespace, dashboardSvc, dashboardHost, dashboardLocalPort, remotePort, false, ) if err != nil { print.FailureStatusEvent(os.Stderr, "%s\n", err) os.Exit(1) } // initialize port forwarding. if err = portForward.Init(); err != nil { print.FailureStatusEvent(os.Stderr, "Error in port forwarding: %s\nCheck for `dapr dashboard` running in other terminal sessions, or use the `--port` flag to use a different port.\n", err) os.Exit(1) } // block until interrupt signal is received. go func() { <-signals portForward.Stop() }() // url for dashboard after port forwarding. webURL := fmt.Sprintf("http://%s", net.JoinHostPort(dashboardHost, fmt.Sprint(portForward.LocalPort))) //nolint: perfsprint print.InfoStatusEvent(os.Stdout, "Dapr dashboard found in namespace:\t"+foundNamespace) print.InfoStatusEvent(os.Stdout, fmt.Sprintf("Dapr dashboard available at:\t%s\n", webURL)) err = browser.OpenURL(webURL) if err != nil { print.FailureStatusEvent(os.Stderr, "Failed to start Dapr dashboard in browser automatically") print.FailureStatusEvent(os.Stderr, fmt.Sprintf("Visit %s in your browser to view the dashboard", webURL)) } <-portForward.GetStop() } else { // Standalone mode. dashboardCmd, err := standalone.NewDashboardCmd(daprRuntimePath, dashboardLocalPort) if err != nil { print.FailureStatusEvent(os.Stderr, "Failed to get Dapr install directory: %v", err) } else { err = dashboardCmd.Run() if err != nil { print.FailureStatusEvent(os.Stderr, "Dapr dashboard failed to run: %v", err) } } } }, PostRun: func(cmd *cobra.Command, args []string) { if kubernetesMode { kubernetes.CheckForCertExpiry() } }, } func init() { DashboardCmd.Flags().BoolVarP(&kubernetesMode, "kubernetes", "k", false, "Opens Dapr dashboard in local browser via local proxy to Kubernetes cluster") DashboardCmd.Flags().BoolVarP(&dashboardVersionCmd, "version", "v", false, "Print the version for Dapr dashboard") DashboardCmd.Flags().StringVarP(&dashboardHost, "address", "a", defaultHost, "Address to listen on. Only accepts IP address or localhost as a value") DashboardCmd.Flags().IntVarP(&dashboardLocalPort, "port", "p", defaultLocalPort, "The local port on which to serve Dapr dashboard") DashboardCmd.Flags().StringVarP(&dashboardNamespace, "namespace", "n", daprSystemNamespace, "The namespace where Dapr dashboard is running") DashboardCmd.Flags().BoolP("help", "h", false, "Print this help message") RootCmd.AddCommand(DashboardCmd) }