Skip to content

Commit

Permalink
Merge pull request #11 from circleci/config-pkg
Browse files Browse the repository at this point in the history
Reduce cross-package exported variables
  • Loading branch information
Zachary Scott authored Jun 12, 2018
2 parents ba038bc + 04eb7a9 commit 76570ac
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 80 deletions.
9 changes: 6 additions & 3 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package client
import (
"context"

"github.com/circleci/circleci-cli/config"
"github.com/circleci/circleci-cli/logger"
"github.com/machinebox/graphql"
)

Expand All @@ -12,15 +12,18 @@ type Client struct {
endpoint string
token string
client *graphql.Client
logger *logger.Logger
}

// NewClient returns a reference to a Client.
// We also call graphql.NewClient to initialize a new GraphQL Client.
func NewClient(endpoint string, token string) *Client {
// Then we pass the Logger originally constructed as cmd.Logger.
func NewClient(endpoint string, token string, logger *logger.Logger) *Client {
return &Client{
endpoint,
token,
graphql.NewClient(endpoint),
logger,
}
}

Expand All @@ -34,7 +37,7 @@ func (c *Client) Run(query string) (map[string]interface{}, error) {
ctx := context.Background()
var resp map[string]interface{}

config.Logger.Debug("Querying %s with:\n\n%s\n\n", c.endpoint, query)
c.logger.Debug("Querying %s with:\n\n%s\n\n", c.endpoint, query)
err := c.client.Run(ctx, req, &resp)
return resp, err
}
16 changes: 8 additions & 8 deletions cmd/diagnostic.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package cmd

import (
"github.com/circleci/circleci-cli/config"
"errors"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)
Expand All @@ -16,17 +17,16 @@ func diagnostic(cmd *cobra.Command, args []string) {
endpoint := viper.GetString("endpoint")
token := viper.GetString("token")

config.Logger.Infoln("\n---\nCircleCI CLI Diagnostics\n---\n")
config.Logger.Infof("Config found: %v\n", viper.ConfigFileUsed())
Logger.Infoln("\n---\nCircleCI CLI Diagnostics\n---\n")
Logger.Infof("Config found: %v\n", viper.ConfigFileUsed())

config.Logger.Infof("GraphQL API endpoint: %s\n", endpoint)
Logger.Infof("GraphQL API endpoint: %s\n", endpoint)

if token == "token" || token == "" {
var err error
config.Logger.FatalOnError("Please set a token!", err)
Logger.FatalOnError("Please set a token!", errors.New(""))
} else {
config.Logger.Infoln("OK, got a token.")
Logger.Infoln("OK, got a token.")
}

config.Logger.Infof("Verbose mode: %v\n", config.Config.Verbose)
Logger.Infof("Verbose mode: %v\n", Config.Verbose)
}
16 changes: 8 additions & 8 deletions cmd/query.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package cmd

import (
"encoding/json"
"io/ioutil"
"os"

"github.com/circleci/circleci-cli/client"
"github.com/circleci/circleci-cli/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
Expand All @@ -18,15 +16,17 @@ var queryCmd = &cobra.Command{
}

func query(cmd *cobra.Command, args []string) {
client := client.NewClient(viper.GetString("endpoint"), viper.GetString("token"))
client := client.NewClient(viper.GetString("endpoint"), viper.GetString("token"), Logger)

query, err := ioutil.ReadAll(os.Stdin)
config.Logger.FatalOnError("Something happened", err)
if err != nil {
Logger.FatalOnError("Unable to read query", err)
}

resp, err := client.Run(string(query))
config.Logger.FatalOnError("Something happend", err)
b, err := json.MarshalIndent(resp, "", " ")
config.Logger.FatalOnError("Could not parse graphql response", err)
if err != nil {
Logger.FatalOnError("Error occurred when running query", err)
}

config.Logger.Infoln(string(b))
Logger.Prettyify(resp)
}
29 changes: 24 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"

"github.com/circleci/circleci-cli/config"
"github.com/circleci/circleci-cli/logger"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
Expand All @@ -19,6 +20,16 @@ func Execute() {
}
}

// 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

// Config is the current configuration available to all commands in `cmd` package.
var Config = &config.Config{
Verbose: false,
Name: "cli",
}

var rootCmd = &cobra.Command{
Use: "cli",
Short: "Use CircleCI from the command line.",
Expand All @@ -31,14 +42,22 @@ func addCommands() {
}

func init() {
cobra.OnInitialize(config.Init)
cobra.OnInitialize(setup)

rootCmd.PersistentFlags().BoolVarP(&config.Config.Verbose, "verbose", "v", false, "Enable verbose logging.")
rootCmd.PersistentFlags().BoolVarP(&Config.Verbose, "verbose", "v", false, "Enable verbose logging.")

rootCmd.PersistentFlags().StringVarP(&config.Config.File, "config", "c", "", "config file (default is $HOME/.circleci/cli.yml)")
rootCmd.PersistentFlags().StringVarP(&Config.File, "config", "c", "", "config file (default is $HOME/.circleci/cli.yml)")
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")

config.Logger.FatalOnError("Error binding endpoint flag", viper.BindPFlag("endpoint", rootCmd.PersistentFlags().Lookup("endpoint")))
config.Logger.FatalOnError("Error binding token flag", viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token")))
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")))
}

func setup() {
Logger = logger.NewLogger(Config.Verbose)
Logger.FatalOnError(
"Failed to setup configuration: ",
Config.Init(),
)
}
118 changes: 62 additions & 56 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,44 @@ import (
"fmt"
"os"

"github.com/circleci/circleci-cli/logger"
"github.com/spf13/viper"
)

type config struct {
Verbose bool
File string
Name string
DefaultPath string
}

// Logger is exposed here so we can access it from subcommands.
// Use this to print to the log at anytime.
var Logger *logger.Logger

// Config is a struct of the current configuration available at runtime.
var Config = &config{
Verbose: false,
Name: "cli",
DefaultPath: fmt.Sprintf("%s/.circleci/cli.yml", os.Getenv("HOME")),
type Config struct {
Verbose bool
File string
Name string
}

// Init is called on initialize of the root command.
func Init() {
Logger = logger.NewLogger(Config.Verbose)
if err := Read(); err == nil {
return
func (c *Config) Init() error {
// try to read the config for the user
if err := c.read(); err == nil {
return err
}

// create a new config, prompting the user
if err := c.create(); err != nil {
return err
}

Logger.FatalOnError("Error creating a new config file", Create())
c.File = c.defaultFile()

Config.File = Config.DefaultPath
Logger.FatalOnError(
"Failed to re-read config after creating a new file",
Read(), // reload config after creating it
)
// reload after creating config
err := c.read()
return err
}

// Read tries to load the config either from Config.DefaultPath or Config.File.
func Read() (err error) {
if Config.File != "" {
viper.SetConfigFile(Config.File)
// read tries to load the config either from Config.defaultPath or Config.file.
func (c *Config) read() error {
if c.File != "" {
viper.SetConfigFile(c.File)
}

if viper.ConfigFileUsed() == "" {
viper.AddConfigPath("$HOME/.circleci")
viper.SetConfigName(Config.Name)
viper.SetConfigName(c.Name)
}

// read in environment variables that match
Expand All @@ -59,61 +50,76 @@ func Read() (err error) {
viper.AutomaticEnv()

// If a config file is found, read it in.
err = viper.ReadInConfig()
err := viper.ReadInConfig()
return err
}

// Create will generate a new config, after asking the user for their token.
func Create() (err error) {
// Don't support creating config at --config flag, only default
if Config.File != "" {
Logger.Debug("Setting up default config at: %v\n", Config.DefaultPath)
}
// defaultPath is the default path to the parent directory that contains the config file
func (c *Config) defaultPath() string {
return fmt.Sprintf("%s/.circleci", os.Getenv("HOME"))
}

path := fmt.Sprintf("%s/.circleci", os.Getenv("HOME"))
// defaultFile is the path to the default config file
func (c *Config) defaultFile() string {
return fmt.Sprintf("%s/%s.yml", c.defaultPath(), c.Name)
}

if _, err = os.Stat(path); os.IsNotExist(err) {
Logger.FatalOnError(
fmt.Sprintf("Error creating directory: '%s'", path),
os.Mkdir(path, 0700),
)
} else {
Logger.FatalOnError(fmt.Sprintf("Error accessing '%s'", path), err)
func (c *Config) setupDefaults() error {
if _, err := os.Stat(c.defaultPath()); os.IsNotExist(err) {
if err = os.Mkdir(c.defaultPath(), 0700); err != nil {
return fmt.Errorf("Error creating directory: '%s'", c.defaultPath())
}
}

// Create default config file
if _, err = os.Create(Config.DefaultPath); err != nil {
_, err := os.Create(c.defaultFile())
return err
}

// create will generate a new config, after asking the user for their token.
func (c *Config) create() error {
// Don't support creating config at --config flag, only default
if c.File != "" {
fmt.Printf("Setting up default config at: %v\n", c.defaultFile())
}

if err := c.setupDefaults(); err != nil {
return err
}

// open file with read & write
file, err := os.OpenFile(Config.DefaultPath, os.O_RDWR, 0600)
file, err := os.OpenFile(c.defaultFile(), os.O_RDWR, 0600)
if err != nil {
Logger.FatalOnError("", err)
return err
}
defer func() {
Logger.FatalOnError("Error closing config file", file.Close())
cerr := file.Close()
if err == nil {
err = cerr
}
}()

// read flag values
endpoint := viper.GetString("endpoint")
token := viper.GetString("token")

if token == "token" || token == "" {
Logger.Info("Please enter your CircleCI API token: ")
fmt.Scanln(&token)
Logger.Infoln("OK.")
fmt.Print("Please enter your CircleCI API token: ")
if n, es := fmt.Scanln(&token); n < 0 {
return es
}
fmt.Println("OK.")
}

// format input
configValues := fmt.Sprintf("endpoint: %v\ntoken: %v\n", endpoint, token)

// write new config values to file
if _, err = file.WriteString(configValues); err != nil {
Logger.FatalOnError("", err)
return err
}

Logger.Infof("Your configuration has been created in `%v`.\n", Config.DefaultPath)
Logger.Infoln("It can edited manually for advanced settings.")
fmt.Printf("Your configuration has been created in `%v`.\n", c.defaultFile())
fmt.Println("It can edited manually for advanced settings.")
return err
}
11 changes: 11 additions & 0 deletions logger/logger.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package logger

import (
"encoding/json"
"log"
"os"
)
Expand Down Expand Up @@ -71,3 +72,13 @@ func (l *Logger) FatalOnError(msg string, err error) {
l.error.Fatalln(msg, err.Error())
}
}

// Prettyify accepts a map fo data and pretty prints it.
// It's using json.MarshalIndent and printing with log.Logger.Infoln
func (l *Logger) Prettyify(data map[string]interface{}) {
bytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
l.error.Fatalln(err)
}
l.Infoln(string(bytes))
}

0 comments on commit 76570ac

Please sign in to comment.