From 3a4d5d1d613616e7a99fd5b9c40c37d977cd054f Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Tue, 5 Jun 2018 21:33:38 +0900 Subject: [PATCH 1/7] wip logger --- cmd/root.go | 49 ++++++++++++++++++++++-------------------------- logger/logger.go | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 27 deletions(-) create mode 100644 logger/logger.go diff --git a/cmd/root.go b/cmd/root.go index b13c543b4..fc6053e15 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,14 +2,14 @@ package cmd import ( "fmt" - "log" "os" + "github.com/circleci/circleci-cli/logger" "github.com/spf13/cobra" "github.com/spf13/viper" ) -// Execute adds all child commands to RootCmd and +// Execute adds all child commands to rootCmd and // sets flags appropriately. This function is called // by main.main(). It only needs to happen once to // the RootCmd. @@ -27,9 +27,11 @@ var rootCmd = &cobra.Command{ } var ( + verbose bool cfgFile string cfgName = "cli" cfgPathDefault = fmt.Sprintf("%s/.circleci/%s.yml", os.Getenv("HOME"), cfgName) + Logger *logger.Logger ) func addCommands() { @@ -37,22 +39,17 @@ func addCommands() { rootCmd.AddCommand(queryCmd) } -func fatalOnError(msg string, err error) { - if err == nil { - return - } - log.Fatalln(msg, err.Error()) -} - func init() { cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging.") + Logger = logger.NewLogger(verbose) rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.circleci/cli.yml)") rootCmd.PersistentFlags().StringP("host", "H", "https://circleci.com", "the host of your CircleCI install") rootCmd.PersistentFlags().StringP("token", "t", "", "your token for using CircleCI") - fatalOnError("Error binding host flag", viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("host"))) - fatalOnError("Error binding token flag", viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))) + Logger.Error("Error binding host flag", viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("host"))) + Logger.Error("Error binding token flag", viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))) } // TODO: move config stuff to it's own package @@ -61,10 +58,10 @@ func initConfig() { return } - fatalOnError("Error creating a new config file", createConfig()) + Logger.Error("Error creating a new config file", createConfig()) cfgFile = cfgPathDefault - fatalOnError( + Logger.Error( "Failed to re-read config after creating a new file", readConfig(), // reload config after creating it ) @@ -93,18 +90,18 @@ func readConfig() (err error) { func createConfig() (err error) { // Don't support creating config at --config flag, only default if cfgFile != "" { - fmt.Printf("Setting up default config at: %v\n", cfgPathDefault) + Logger.Debug("Setting up default config at: %v\n", cfgPathDefault) } path := fmt.Sprintf("%s/.circleci", os.Getenv("HOME")) if _, err = os.Stat(path); os.IsNotExist(err) { - fatalOnError( + Logger.Error( fmt.Sprintf("Error creating directory: '%s'", path), os.Mkdir(path, 0644), ) } else { - fatalOnError(fmt.Sprintf("Error accessing '%s'", path), err) + Logger.Error(fmt.Sprintf("Error accessing '%s'", path), err) } // Create default config file @@ -115,25 +112,24 @@ func createConfig() (err error) { // open file with read & write file, err := os.OpenFile(cfgPathDefault, os.O_RDWR, 0600) if err != nil { - fmt.Println(err.Error()) - os.Exit(-1) + Logger.Error("", err) } - defer fatalOnError("Error closing config file", file.Close()) + defer Logger.Error("Error closing config file", file.Close()) // read flag values host := viper.GetString("host") token := viper.GetString("token") if host == "host" || host == "" { - fmt.Print("Please enter the HTTP(S) host of your CircleCI installation:") + Logger.Info("Please enter the HTTP(S) host of your CircleCI installation:") fmt.Scanln(&host) - fmt.Println("OK.") + Logger.Info("OK.\n") } if token == "token" || token == "" { - fmt.Print("Please enter your CircleCI API token: ") + Logger.Info("Please enter your CircleCI API token: ") fmt.Scanln(&token) - fmt.Println("OK.") + Logger.Info("OK.\n") } // format input @@ -141,11 +137,10 @@ func createConfig() (err error) { // write new config values to file if _, err = file.WriteString(configValues); err != nil { - fmt.Println(err.Error()) - os.Exit(-1) + Logger.Error("", err) } - fmt.Printf("Your configuration has been created in `%v`.\n", cfgPathDefault) - fmt.Println("It can edited manually for advanced settings.") + Logger.Info("Your configuration has been created in `%v`.\n", cfgPathDefault) + Logger.Info("It can edited manually for advanced settings.\n") return err } diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 000000000..9fe11fdf6 --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,40 @@ +package logger + +import ( + "log" + "os" +) + +type Logger struct { + debug *log.Logger + info *log.Logger + error *log.Logger + verbose bool +} + +func NewLogger(verbose bool) *Logger { + return &Logger{ + log.New(os.Stdout, "", 0), + log.New(os.Stdout, "", 0), + log.New(os.Stderr, "", 0), + verbose, + } +} + +func (l *Logger) Debug(msg string, args ...interface{}) { + if l.verbose { + l.debug.Printf(msg, args...) + } +} + +func (l *Logger) Info(msg string, args ...interface{}) { + l.info.Printf(msg, args...) +} + +func (l *Logger) Error(msg string, err error) { + if err == nil { + l.error.Println(msg) + } else { + l.error.Fatalln(msg, err.Error()) + } +} From 4a7489ce75f08386a2c5707f2b7b49474a53d747 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Wed, 6 Jun 2018 00:44:01 +0900 Subject: [PATCH 2/7] Add FatalOnError for explicitly exit on error Add Infof and Debug for wrapping Printf Info now simply wraps Print Hooked up diagnostic and root commands to new logger --- cmd/diagnostic.go | 16 +++++----------- cmd/root.go | 18 +++++++++--------- logger/logger.go | 20 +++++++++++++------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/cmd/diagnostic.go b/cmd/diagnostic.go index 97def2c99..67219bbc9 100644 --- a/cmd/diagnostic.go +++ b/cmd/diagnostic.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -17,18 +15,14 @@ func diagnostic(cmd *cobra.Command, args []string) { host := viper.GetString("host") token := viper.GetString("token") - fmt.Printf("\n---\nCircleCI CLI Diagnostics\n---\n\n") - fmt.Printf("Config found: `%v`\n", viper.ConfigFileUsed()) + Logger.Info("\n---\nCircleCI CLI Diagnostics\n---\n\n") + Logger.Infof("Config found: `%v`\n", viper.ConfigFileUsed()) - if host == "host" || host == "" { - fmt.Println("Please set a host!") - } else { - fmt.Printf("Host is: %s\n", host) - } + Logger.Infof("Host is: %s\n", host) if token == "token" || token == "" { - fmt.Println("Please set a token!") + Logger.Info("Please set a token!") } else { - fmt.Println("OK, got a token.") + Logger.Info("OK, got a token.") } } diff --git a/cmd/root.go b/cmd/root.go index fc6053e15..d04a4dbee 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -48,8 +48,8 @@ func init() { rootCmd.PersistentFlags().StringP("host", "H", "https://circleci.com", "the host of your CircleCI install") rootCmd.PersistentFlags().StringP("token", "t", "", "your token for using CircleCI") - Logger.Error("Error binding host flag", viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("host"))) - Logger.Error("Error binding token flag", viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))) + Logger.FatalOnError("Error binding host flag", viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("host"))) + Logger.FatalOnError("Error binding token flag", viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))) } // TODO: move config stuff to it's own package @@ -58,10 +58,10 @@ func initConfig() { return } - Logger.Error("Error creating a new config file", createConfig()) + Logger.FatalOnError("Error creating a new config file", createConfig()) cfgFile = cfgPathDefault - Logger.Error( + Logger.FatalOnError( "Failed to re-read config after creating a new file", readConfig(), // reload config after creating it ) @@ -96,12 +96,12 @@ func createConfig() (err error) { path := fmt.Sprintf("%s/.circleci", os.Getenv("HOME")) if _, err = os.Stat(path); os.IsNotExist(err) { - Logger.Error( + Logger.FatalOnError( fmt.Sprintf("Error creating directory: '%s'", path), os.Mkdir(path, 0644), ) } else { - Logger.Error(fmt.Sprintf("Error accessing '%s'", path), err) + Logger.FatalOnError(fmt.Sprintf("Error accessing '%s'", path), err) } // Create default config file @@ -112,9 +112,9 @@ func createConfig() (err error) { // open file with read & write file, err := os.OpenFile(cfgPathDefault, os.O_RDWR, 0600) if err != nil { - Logger.Error("", err) + Logger.FatalOnError("", err) } - defer Logger.Error("Error closing config file", file.Close()) + defer Logger.FatalOnError("Error closing config file", file.Close()) // read flag values host := viper.GetString("host") @@ -137,7 +137,7 @@ func createConfig() (err error) { // write new config values to file if _, err = file.WriteString(configValues); err != nil { - Logger.Error("", err) + Logger.FatalOnError("", err) } Logger.Info("Your configuration has been created in `%v`.\n", cfgPathDefault) diff --git a/logger/logger.go b/logger/logger.go index 9fe11fdf6..039736263 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -21,20 +21,26 @@ func NewLogger(verbose bool) *Logger { } } -func (l *Logger) Debug(msg string, args ...interface{}) { +func (l *Logger) Debug(format string, args ...interface{}) { if l.verbose { - l.debug.Printf(msg, args...) + l.debug.Printf(format, args...) } } -func (l *Logger) Info(msg string, args ...interface{}) { - l.info.Printf(msg, args...) +func (l *Logger) Info(args ...interface{}) { + l.info.Print(args...) +} + +func (l *Logger) Infof(format string, args ...interface{}) { + l.info.Printf(format, args...) } func (l *Logger) Error(msg string, err error) { - if err == nil { - l.error.Println(msg) - } else { + l.error.Print(msg, err.Error()) +} + +func (l *Logger) FatalOnError(msg string, err error) { + if err != nil { l.error.Fatalln(msg, err.Error()) } } From f29be3248f6334540541764bf91f7181221102c8 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Wed, 6 Jun 2018 00:46:32 +0900 Subject: [PATCH 3/7] Add graphql client wrapper package Hook up client and query command to logger --- client/client.go | 37 +++++++++++++++++++++++++++++++++++++ cmd/query.go | 31 ++++++++----------------------- 2 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 client/client.go diff --git a/client/client.go b/client/client.go new file mode 100644 index 000000000..b66298672 --- /dev/null +++ b/client/client.go @@ -0,0 +1,37 @@ +package client + +import ( + "context" + + "github.com/circleci/circleci-cli/logger" + "github.com/machinebox/graphql" +) + +type Client struct { + host string + token string + client *graphql.Client + logger *logger.Logger +} + +func NewClient(host string, token string, logger *logger.Logger) *Client { + return &Client{ + host, + token, + graphql.NewClient(host + "/graphql"), + logger, + } +} + +func (c *Client) Run(query string) (map[string]interface{}, error) { + req := graphql.NewRequest(query) + req.Header.Set("Authorization", c.token) + + ctx := context.Background() + var resp map[string]interface{} + + // TODO: fix wrapping logs with + c.logger.Info("Querying ", c.host, " with:\n\n", query, "\n\n") + err := c.client.Run(ctx, req, &resp) + return resp, err +} diff --git a/cmd/query.go b/cmd/query.go index cb28b0eda..0c9f3b80e 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -1,12 +1,9 @@ package cmd import ( - "context" "encoding/json" - "fmt" - "log" - "github.com/machinebox/graphql" + "github.com/circleci/circleci-cli/client" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -18,9 +15,7 @@ var queryCmd = &cobra.Command{ } func query(cmd *cobra.Command, args []string) { - host := viper.GetString("host") - token := viper.GetString("token") - client := graphql.NewClient(host + "/graphql") + client := client.NewClient(viper.GetString("host"), viper.GetString("token"), Logger) query := ` query IntrospectionQuery { @@ -47,21 +42,11 @@ func query(cmd *cobra.Command, args []string) { } }` - req := graphql.NewRequest(query) - req.Header.Set("Authorization", token) - - ctx := context.Background() - var resp map[string]interface{} - - fmt.Print("Querying ", host, " with:\n\n", query, "\n\n") - if err := client.Run(ctx, req, &resp); err != nil { - log.Fatal(err) - } - + resp, err := client.Run(query) + Logger.FatalOnError("Something happend", err) b, err := json.MarshalIndent(resp, "", " ") - if err != nil { - log.Fatalln("Could not parse graphql response", err.Error()) - } - fmt.Print("Result: \n\n") - fmt.Println(string(b)) + Logger.FatalOnError("Could not parse graphql response", err) + + Logger.Info("Result: \n\n") + Logger.Info(string(b) + "\n") } From 31b93b49e9bb287fe5c13466ac0ae2fb0f1e110c Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Wed, 6 Jun 2018 00:52:41 +0900 Subject: [PATCH 4/7] CIRCLE-11151: CLI can accept a GQL query from stdin and call api-service --- cmd/query.go | 30 +++++------------------------- testquery.gql | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 testquery.gql diff --git a/cmd/query.go b/cmd/query.go index 0c9f3b80e..42f49dded 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -2,6 +2,8 @@ package cmd import ( "encoding/json" + "io/ioutil" + "os" "github.com/circleci/circleci-cli/client" "github.com/spf13/cobra" @@ -17,32 +19,10 @@ var queryCmd = &cobra.Command{ func query(cmd *cobra.Command, args []string) { client := client.NewClient(viper.GetString("host"), viper.GetString("token"), Logger) - query := ` - query IntrospectionQuery { - __schema { - queryType { name } - mutationType { name } - subscriptionType { name } - types { - ...FullType - } - directives { - name - description - } - } - } + query, err := ioutil.ReadAll(os.Stdin) + Logger.FatalOnError("Something happened", err) - fragment FullType on __Type { - kind - name - description - fields(includeDeprecated: true) { - name - } - }` - - resp, err := client.Run(query) + resp, err := client.Run(string(query)) Logger.FatalOnError("Something happend", err) b, err := json.MarshalIndent(resp, "", " ") Logger.FatalOnError("Could not parse graphql response", err) diff --git a/testquery.gql b/testquery.gql new file mode 100644 index 000000000..004df04bf --- /dev/null +++ b/testquery.gql @@ -0,0 +1,23 @@ + query IntrospectionQuery { + __schema { + queryType { name } + mutationType { name } + subscriptionType { name } + types { + ...FullType + } + directives { + name + description + } + } + } + + fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + } + } \ No newline at end of file From 4486a42d33dbd4f960056c8cdbee43710a1985ce Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Wed, 6 Jun 2018 01:16:16 +0900 Subject: [PATCH 5/7] Rename host to endpoint and include /graphql as default --- client/client.go | 17 ++++++++--------- cmd/diagnostic.go | 4 ++-- cmd/query.go | 2 +- cmd/root.go | 16 ++++++++-------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/client/client.go b/client/client.go index b66298672..448500f93 100644 --- a/client/client.go +++ b/client/client.go @@ -8,17 +8,17 @@ import ( ) type Client struct { - host string - token string - client *graphql.Client - logger *logger.Logger + endpoint string + token string + client *graphql.Client + logger *logger.Logger } -func NewClient(host string, token string, logger *logger.Logger) *Client { +func NewClient(endpoint string, token string, logger *logger.Logger) *Client { return &Client{ - host, + endpoint, token, - graphql.NewClient(host + "/graphql"), + graphql.NewClient(endpoint), logger, } } @@ -30,8 +30,7 @@ func (c *Client) Run(query string) (map[string]interface{}, error) { ctx := context.Background() var resp map[string]interface{} - // TODO: fix wrapping logs with - c.logger.Info("Querying ", c.host, " with:\n\n", query, "\n\n") + c.logger.Info("Querying ", c.endpoint, " with:\n\n", query, "\n\n") err := c.client.Run(ctx, req, &resp) return resp, err } diff --git a/cmd/diagnostic.go b/cmd/diagnostic.go index 67219bbc9..dc974371e 100644 --- a/cmd/diagnostic.go +++ b/cmd/diagnostic.go @@ -12,13 +12,13 @@ var diagnosticCmd = &cobra.Command{ } func diagnostic(cmd *cobra.Command, args []string) { - host := viper.GetString("host") + endpoint := viper.GetString("endpoint") token := viper.GetString("token") Logger.Info("\n---\nCircleCI CLI Diagnostics\n---\n\n") Logger.Infof("Config found: `%v`\n", viper.ConfigFileUsed()) - Logger.Infof("Host is: %s\n", host) + Logger.Infof("GraphQL API endpoint: %s\n", endpoint) if token == "token" || token == "" { Logger.Info("Please set a token!") diff --git a/cmd/query.go b/cmd/query.go index 42f49dded..d66ba0c28 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -17,7 +17,7 @@ var queryCmd = &cobra.Command{ } func query(cmd *cobra.Command, args []string) { - client := client.NewClient(viper.GetString("host"), viper.GetString("token"), Logger) + client := client.NewClient(viper.GetString("endpoint"), viper.GetString("token"), Logger) query, err := ioutil.ReadAll(os.Stdin) Logger.FatalOnError("Something happened", err) diff --git a/cmd/root.go b/cmd/root.go index d04a4dbee..253791281 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -45,10 +45,10 @@ func init() { rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging.") Logger = logger.NewLogger(verbose) rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.circleci/cli.yml)") - rootCmd.PersistentFlags().StringP("host", "H", "https://circleci.com", "the host of your CircleCI install") + rootCmd.PersistentFlags().StringP("endpoint", "e", "https://circleci.com/graphql", "the endpoint of your CircleCI GraphQL API") rootCmd.PersistentFlags().StringP("token", "t", "", "your token for using CircleCI") - Logger.FatalOnError("Error binding host flag", viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("host"))) + Logger.FatalOnError("Error binding endpoint flag", viper.BindPFlag("endpoint", rootCmd.PersistentFlags().Lookup("endpoint"))) Logger.FatalOnError("Error binding token flag", viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))) } @@ -78,7 +78,7 @@ func readConfig() (err error) { } // read in environment variables that match - // set a prefix for config, i.e. CIRCLECI_CLI_HOST + // set a prefix for config, i.e. CIRCLECI_CLI_ENDPOINT viper.SetEnvPrefix("circleci_cli") viper.AutomaticEnv() @@ -117,12 +117,12 @@ func createConfig() (err error) { defer Logger.FatalOnError("Error closing config file", file.Close()) // read flag values - host := viper.GetString("host") + endpoint := viper.GetString("endpoint") token := viper.GetString("token") - if host == "host" || host == "" { - Logger.Info("Please enter the HTTP(S) host of your CircleCI installation:") - fmt.Scanln(&host) + if endpoint == "endpoint" || endpoint == "" { + Logger.Info("Please enter the HTTP(S) endpoint to your CircleCI GraphQL API:") + fmt.Scanln(&endpoint) Logger.Info("OK.\n") } @@ -133,7 +133,7 @@ func createConfig() (err error) { } // format input - configValues := fmt.Sprintf("host: %v\ntoken: %v\n", host, token) + configValues := fmt.Sprintf("endpoint: %v\ntoken: %v\n", endpoint, token) // write new config values to file if _, err = file.WriteString(configValues); err != nil { From c38eeeb073c60d5821698ba6c0a4e9d7a3dcddfc Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Wed, 6 Jun 2018 20:48:32 +0900 Subject: [PATCH 6/7] Add docs to fix linting errors Also adds the following make tasks: * doc: easily run godoc to view package docs * dev: install godoc and gometalinter development dependencies As a side-effect, debug now prints to stderr --- Makefile | 10 ++++++++++ README.md | 19 +++++++++++++++++-- client/client.go | 7 +++++++ cmd/root.go | 5 ++++- logger/logger.go | 25 +++++++++++++++++++++++-- 5 files changed, 61 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index f3b289b0e..792e6a805 100644 --- a/Makefile +++ b/Makefile @@ -32,3 +32,13 @@ cover: .PHONY: lint lint: gometalinter --deadline 60s --vendor ./... + +.PHONY: doc +doc: + godoc -http=:6060 + +.PHONY: dev +dev: + go get golang.org/x/tools/cmd/godoc + go get -u github.com/alecthomas/gometalinter + gometalinter --install diff --git a/README.md b/README.md index 1dcb82fe0..43475461a 100644 --- a/README.md +++ b/README.md @@ -75,15 +75,30 @@ To make sure dependencies are installed: $ dep ensure ``` +## Linting + +We use [`gometalinter`](github.com/alecthomas/gometalinter) for linting. + +You can install it via `$ make dev` or manually with: + +``` +$ go get -u github.com/alecthomas/gometalinter +$ gometalinter --install +``` + +Then you can run it with `$ make lint`. + ## Known Issues * ... ## Doc -You can view `godoc` of cli in your browser. +You can view `godoc` of the cli in your browser. + +After installing it either via `go get golang.org/x/tools/cmd/godoc` or running `make dev`. -1. Run `godoc -http=:6060` +1. Run `make doc` or `godoc -http=:6060` 2. Access http://localhost:6060/pkg/github.com/circleci/circleci-cli/ ## Editor support diff --git a/client/client.go b/client/client.go index 448500f93..79adb65b3 100644 --- a/client/client.go +++ b/client/client.go @@ -7,6 +7,7 @@ import ( "github.com/machinebox/graphql" ) +// Client wraps a graphql.Client and other fields for making API calls. type Client struct { endpoint string token string @@ -14,6 +15,9 @@ type Client struct { logger *logger.Logger } +// NewClient returns a reference to a Client. +// We also call graphql.NewClient to initialize a new GraphQL Client. +// Then we pass the Logger originally constructed as cmd.Logger. func NewClient(endpoint string, token string, logger *logger.Logger) *Client { return &Client{ endpoint, @@ -23,6 +27,9 @@ func NewClient(endpoint string, token string, logger *logger.Logger) *Client { } } +// Run will construct a request using graphql.NewRequest. +// Then it will execute the given query using graphql.Client.Run. +// This function will return the unmarshalled response as JSON. func (c *Client) Run(query string) (map[string]interface{}, error) { req := graphql.NewRequest(query) req.Header.Set("Authorization", c.token) diff --git a/cmd/root.go b/cmd/root.go index 253791281..de9a624cc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -31,9 +31,12 @@ var ( cfgFile string cfgName = "cli" cfgPathDefault = fmt.Sprintf("%s/.circleci/%s.yml", os.Getenv("HOME"), cfgName) - Logger *logger.Logger ) +// Logger is exposed here so we can access it from subcommands. +// This allows us to print to the log at anytime from within the `cmd` package. +var Logger *logger.Logger + func addCommands() { rootCmd.AddCommand(diagnosticCmd) rootCmd.AddCommand(queryCmd) diff --git a/logger/logger.go b/logger/logger.go index 039736263..6304960ef 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -5,6 +5,8 @@ import ( "os" ) +// Logger wraps a few log.Logger instances in private fields. +// They are accessible via their respective methods. type Logger struct { debug *log.Logger info *log.Logger @@ -12,33 +14,52 @@ type Logger struct { verbose bool } +// NewLogger returns a reference to a Logger. +// We usually call this when initializing cmd.Logger. +// Later we pass this to client.NewClient so it can also log. +// By default debug and error go to os.Stderr, and info goes to os.Stdout func NewLogger(verbose bool) *Logger { return &Logger{ - log.New(os.Stdout, "", 0), + log.New(os.Stderr, "", 0), log.New(os.Stdout, "", 0), log.New(os.Stderr, "", 0), verbose, } } +// Debug prints a formatted message to stderr only if verbose is set. +// Consider these messages useful for developers of the CLI. +// This method wraps log.Logger.Printf func (l *Logger) Debug(format string, args ...interface{}) { if l.verbose { l.debug.Printf(format, args...) } } +// Info prints all args to os.Stdout +// It's commonly used for messages we want to show the user. +// This method wraps log.Logger.Print func (l *Logger) Info(args ...interface{}) { l.info.Print(args...) } +// Infof prints a formatted message to stdout +// This method wraps log.Logger.Printf func (l *Logger) Infof(format string, args ...interface{}) { l.info.Printf(format, args...) } +// Error prints a message and the given error's message to os.Stderr +// This method wraps log.Logger.Print func (l *Logger) Error(msg string, err error) { - l.error.Print(msg, err.Error()) + if err != nil { + l.error.Print(msg, err.Error()) + } } +// FatalOnError prints a message and error's message to os.Stderr then QUITS! +// Please be aware this method will exit the program via os.Exit(1). +// This method wraps log.Logger.Fatalln func (l *Logger) FatalOnError(msg string, err error) { if err != nil { l.error.Fatalln(msg, err.Error()) From 3fdf2b3b418108eb22ba74f1a7493284c4044524 Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Wed, 6 Jun 2018 21:03:07 +0900 Subject: [PATCH 7/7] client.Client.Run should print it's message to debug Also added logger.Logger.Infoln that wraps log.Logger.Println --- client/client.go | 2 +- cmd/diagnostic.go | 7 ++++--- cmd/query.go | 3 +-- cmd/root.go | 10 ++-------- logger/logger.go | 6 ++++++ 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/client/client.go b/client/client.go index 79adb65b3..299140a82 100644 --- a/client/client.go +++ b/client/client.go @@ -37,7 +37,7 @@ func (c *Client) Run(query string) (map[string]interface{}, error) { ctx := context.Background() var resp map[string]interface{} - c.logger.Info("Querying ", c.endpoint, " with:\n\n", query, "\n\n") + c.logger.Debug("Querying %s with:\n\n%s\n\n", c.endpoint, query) err := c.client.Run(ctx, req, &resp) return resp, err } diff --git a/cmd/diagnostic.go b/cmd/diagnostic.go index dc974371e..bfb0c1437 100644 --- a/cmd/diagnostic.go +++ b/cmd/diagnostic.go @@ -15,14 +15,15 @@ func diagnostic(cmd *cobra.Command, args []string) { endpoint := viper.GetString("endpoint") token := viper.GetString("token") - Logger.Info("\n---\nCircleCI CLI Diagnostics\n---\n\n") + Logger.Infoln("\n---\nCircleCI CLI Diagnostics\n---\n") Logger.Infof("Config found: `%v`\n", viper.ConfigFileUsed()) Logger.Infof("GraphQL API endpoint: %s\n", endpoint) if token == "token" || token == "" { - Logger.Info("Please set a token!") + var err error + Logger.FatalOnError("Please set a token!", err) } else { - Logger.Info("OK, got a token.") + Logger.Infoln("OK, got a token.") } } diff --git a/cmd/query.go b/cmd/query.go index d66ba0c28..345c2e411 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -27,6 +27,5 @@ func query(cmd *cobra.Command, args []string) { b, err := json.MarshalIndent(resp, "", " ") Logger.FatalOnError("Could not parse graphql response", err) - Logger.Info("Result: \n\n") - Logger.Info(string(b) + "\n") + Logger.Infoln(string(b)) } diff --git a/cmd/root.go b/cmd/root.go index de9a624cc..543d8b4f9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -123,16 +123,10 @@ func createConfig() (err error) { endpoint := viper.GetString("endpoint") token := viper.GetString("token") - if endpoint == "endpoint" || endpoint == "" { - Logger.Info("Please enter the HTTP(S) endpoint to your CircleCI GraphQL API:") - fmt.Scanln(&endpoint) - Logger.Info("OK.\n") - } - if token == "token" || token == "" { Logger.Info("Please enter your CircleCI API token: ") fmt.Scanln(&token) - Logger.Info("OK.\n") + Logger.Infoln("OK.") } // format input @@ -144,6 +138,6 @@ func createConfig() (err error) { } Logger.Info("Your configuration has been created in `%v`.\n", cfgPathDefault) - Logger.Info("It can edited manually for advanced settings.\n") + Logger.Infoln("It can edited manually for advanced settings.") return err } diff --git a/logger/logger.go b/logger/logger.go index 6304960ef..f46910999 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -43,6 +43,12 @@ func (l *Logger) Info(args ...interface{}) { l.info.Print(args...) } +// Infoln prints all args to os.Stdout followed by a newline. +// This method wraps log.Logger.Println +func (l *Logger) Infoln(args ...interface{}) { + l.info.Println(args...) +} + // Infof prints a formatted message to stdout // This method wraps log.Logger.Printf func (l *Logger) Infof(format string, args ...interface{}) {