Skip to content

Commit

Permalink
Implement the grpcdebug
Browse files Browse the repository at this point in the history
* Wrote README.md
* Support xds,channelz,health
  • Loading branch information
lidizheng committed Apr 7, 2021
1 parent 08fda64 commit bc0b049
Show file tree
Hide file tree
Showing 15 changed files with 2,445 additions and 2 deletions.
417 changes: 415 additions & 2 deletions README.md

Large diffs are not rendered by default.

434 changes: 434 additions & 0 deletions cmd/channelz.go

Large diffs are not rendered by default.

183 changes: 183 additions & 0 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package config

import (
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"regexp"
"runtime"
"strings"

"github.com/grpc-ecosystem/grpcdebug/cmd/verbose"
)

// SecurityType is the enum type of available security modes
type SecurityType int

const (
// TypeInsecure is the insecure security mode and it is the default value
TypeInsecure SecurityType = iota
// TypeTls is the TLS security mode, which requires caller to provide
// credentials to connect to peer
TypeTls
)

// The environment variable name of getting the server configs
const grpcdebugServerConfigEnvName = "GRPCDEBUG_CONFIG"

func (e SecurityType) String() string {
switch e {
case TypeInsecure:
return "Insecure"
case TypeTls:
return "TLS"
default:
return fmt.Sprintf("%d", int(e))
}
}

// ServerConfig is the configuration for how to connect to a target
type ServerConfig struct {
Pattern string
RealAddress string
Security SecurityType
CredentialFile string
ServerNameOverride string
}

func parseServerPattern(x string) (string, error) {
var matcher = regexp.MustCompile(`^Server\s+?([A-Za-z0-9-_\.\*\?:]*)$`)
tokens := matcher.FindStringSubmatch(x)
if len(tokens) != 2 {
return "", fmt.Errorf("Invalid server pattern: %v", x)
}
return strings.TrimSpace(tokens[1]), nil
}

func parseServerOption(x string) (string, string, error) {
var matcher = regexp.MustCompile(`^(\w+?)\s+?(\S*)$`)
tokens := matcher.FindStringSubmatch(x)
if len(tokens) != 3 {
return "", "", fmt.Errorf("Invalid server option: %v", x)
}
return strings.TrimSpace(tokens[1]), strings.TrimSpace(tokens[2]), nil
}

func loadServerConfigsFromFile(path string) []ServerConfig {
file, err := os.Open(path)
if err != nil {
panic(err)
}
bytes, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
lines := strings.Split(string(bytes), "\n")
var configs []ServerConfig
var current *ServerConfig
for i, line := range lines {
if strings.HasPrefix(line, "Server") {
pattern, err := parseServerPattern(line)
if err != nil {
log.Fatalf("Failed to parse config [%v:%d]: %v", path, i, err)
}
configs = append(configs, ServerConfig{Pattern: pattern})
current = &configs[len(configs)-1]
} else {
stem := strings.TrimSpace(line)
if stem == "" {
// Allow black lines, skip them
continue
}
key, value, err := parseServerOption(stem)
if err != nil {
log.Fatalf("Failed to parse config [%v:%d]: %v", path, i, err)
}
switch key {
case "RealAddress":
current.RealAddress = value
case "Security":
switch strings.ToLower(value) {
case "insecure":
current.Security = TypeInsecure
case "tls":
current.Security = TypeTls
default:
log.Fatalf("Unsupported security model: %v", value)
}
case "CredentialFile":
current.CredentialFile = value
case "ServerNameOverride":
current.ServerNameOverride = value
}
}
}
verbose.Debugf("Loaded server configs from %v: %v", path, configs)
return configs
}

func UserConfigDir() (string, error) {
var dir string
switch runtime.GOOS {
case "windows":
dir = os.Getenv("AppData")
if dir == "" {
return "", errors.New("%AppData% is not defined")
}

case "darwin", "ios":
dir = os.Getenv("HOME")
if dir == "" {
return "", errors.New("$HOME is not defined")
}
dir += "/Library/Application Support"

case "plan9":
dir = os.Getenv("home")
if dir == "" {
return "", errors.New("$home is not defined")
}
dir += "/lib"

default: // Unix
dir = os.Getenv("XDG_CONFIG_HOME")
if dir == "" {
dir = os.Getenv("HOME")
if dir == "" {
return "", errors.New("neither $XDG_CONFIG_HOME nor $HOME are defined")
}
dir += "/.config"
}
}
return dir, nil
}

func loadServerConfigs() []ServerConfig {
if value := os.Getenv(grpcdebugServerConfigEnvName); value != "" {
return loadServerConfigsFromFile(value)
}
// Try to load from work directory, if exists
if _, err := os.Stat("./grpcdebug_config"); err == nil {
return loadServerConfigsFromFile("./grpcdebug_config")
}
// Try to load from user config directory, if exists
dir, _ := UserConfigDir()
defaultUserConfig := path.Join(dir, "grpcdebug_config")
if _, err := os.Stat(defaultUserConfig); err == nil {
return loadServerConfigsFromFile(defaultUserConfig)
}
return nil
}

// GetServerConfig returns a connect configuration for the given target
func GetServerConfig(target string) ServerConfig {
for _, config := range loadServerConfigs() {
if config.Pattern == target {
return config
}
}
return ServerConfig{RealAddress: target}
}
32 changes: 32 additions & 0 deletions cmd/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cmd

import (
"fmt"

"github.com/grpc-ecosystem/grpcdebug/cmd/transport"
"github.com/spf13/cobra"
)

var healthCmd = &cobra.Command{
Use: "health [service names]",
Short: "Check health status of the target service (default \"\").",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
fmt.Println(transport.GetHealthStatus(""))
return nil
}
for _, service := range args {
fmt.Fprintf(
w, "%v:\t%v\t\n",
service,
transport.GetHealthStatus(service),
)
}
w.Flush()
return nil
},
}

func init() {
rootCmd.AddCommand(healthCmd)
}
111 changes: 111 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Defines the root command and global flags

package cmd

import (
"fmt"
"log"
"os"
"text/tabwriter"

"github.com/grpc-ecosystem/grpcdebug/cmd/config"
"github.com/grpc-ecosystem/grpcdebug/cmd/transport"
"github.com/grpc-ecosystem/grpcdebug/cmd/verbose"

"github.com/spf13/cobra"
)

var verboseFlag, timestampFlag bool
var address, security, credFile, serverNameOverride string

// The table formater
var w = tabwriter.NewWriter(os.Stdout, 10, 0, 3, ' ', 0)

var rootUsageTemplate = `Usage:{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
grpcdebug <target address> [flags] {{ .CommandPath | ChildCommandPath }} <command>{{end}}{{if gt (len .Aliases) 0}}
Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
Use "grpcdebug <target address> {{ .CommandPath | ChildCommandPath }} [command] --help" for more information about a command.{{end}}
`

var rootCmd = &cobra.Command{
Use: "grpcdebug",
Short: "grpcdebug is an gRPC service admin CLI",
}

func initConfig() {
if verboseFlag {
verbose.EnableDebugOutput()
}
c := config.GetServerConfig(address)
if credFile != "" {
c.CredentialFile = credFile
}
if serverNameOverride != "" {
c.ServerNameOverride = serverNameOverride
}
if security == "tls" {
c.Security = config.TypeTls
if c.CredentialFile == "" {
rootCmd.Usage()
log.Fatalf("Please specify credential file under [tls] mode.")
}
} else if security != "insecure" {
rootCmd.Usage()
log.Fatalf("Unrecognized security mode: %v", security)
}
transport.Connect(c)
}

// ChildCommandPath used in template
func ChildCommandPath(path string) string {
if len(path) <= 10 {
return ""
}
return path[10:]
}

func init() {
cobra.AddTemplateFunc("ChildCommandPath", ChildCommandPath)
cobra.OnInitialize(initConfig)
rootCmd.SetUsageTemplate(rootUsageTemplate)

rootCmd.PersistentFlags().BoolVarP(&verboseFlag, "verbose", "v", false, "Print verbose information for debugging")
rootCmd.PersistentFlags().BoolVarP(&timestampFlag, "timestamp", "t", false, "Print timestamp as RFC3339 instead of human readable strings")
rootCmd.PersistentFlags().StringVar(&security, "security", "insecure", "Defines the type of credentials to use [tls, google-default, insecure]")
rootCmd.PersistentFlags().StringVar(&credFile, "credential_file", "", "Sets the path of the credential file; used in [tls] mode")
rootCmd.PersistentFlags().StringVar(&serverNameOverride, "server_name_override", "", "Overrides the peer server name if non empty; used in [tls] mode")
}

// Execute executes the root command.
func Execute() {
if len(os.Args) > 1 {
address = os.Args[1]
os.Args = os.Args[1:]
} else {
rootCmd.Usage()
os.Exit(1)
}
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
Loading

0 comments on commit bc0b049

Please sign in to comment.