-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Wrote README.md * Support xds,channelz,health
- Loading branch information
Showing
15 changed files
with
2,445 additions
and
2 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(×tampFlag, "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) | ||
} | ||
} |
Oops, something went wrong.