Skip to content

Commit

Permalink
Extract config into it's own package
Browse files Browse the repository at this point in the history
This has an awkward of side-effect of referencing the internal config
struct as config.Config -- welcome to naming ideas!

An alternative would be to store a config of type config.Config in
cmd, and instead of calling config.Init from OnInitialize,
we could have a init function in cmd that sets up the struct.
  • Loading branch information
Zachary Scott committed Jun 12, 2018
1 parent 166ef36 commit 84da9f9
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 118 deletions.
13 changes: 8 additions & 5 deletions cmd/diagnostic.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

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

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

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

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

config.Logger.Infof("Verbose mode: %v\n", config.Config.Verbose)
}
11 changes: 6 additions & 5 deletions cmd/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"

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

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

query, err := ioutil.ReadAll(os.Stdin)
Logger.FatalOnError("Something happened", err)
config.Logger.FatalOnError("Something happened", err)

resp, err := client.Run(string(query))
Logger.FatalOnError("Something happend", err)
config.Logger.FatalOnError("Something happend", err)
b, err := json.MarshalIndent(resp, "", " ")
Logger.FatalOnError("Could not parse graphql response", err)
config.Logger.FatalOnError("Could not parse graphql response", err)

Logger.Infoln(string(b))
config.Logger.Infoln(string(b))
}
115 changes: 7 additions & 108 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package cmd

import (
"fmt"
"os"

"github.com/circleci/circleci-cli/logger"
"github.com/circleci/circleci-cli/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
Expand All @@ -26,120 +25,20 @@ var rootCmd = &cobra.Command{
Long: `Use CircleCI from the command line.`,
}

var (
verbose bool
cfgFile string
cfgName = "cli"
cfgPathDefault = fmt.Sprintf("%s/.circleci/%s.yml", os.Getenv("HOME"), cfgName)
)

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

func init() {
cobra.OnInitialize(initConfig)
cobra.OnInitialize(config.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().BoolVarP(&config.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().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 endpoint flag", viper.BindPFlag("endpoint", rootCmd.PersistentFlags().Lookup("endpoint")))
Logger.FatalOnError("Error binding token flag", viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token")))
}

// TODO: move config stuff to it's own package
func initConfig() {
if err := readConfig(); err == nil {
return
}

Logger.FatalOnError("Error creating a new config file", createConfig())

cfgFile = cfgPathDefault
Logger.FatalOnError(
"Failed to re-read config after creating a new file",
readConfig(), // reload config after creating it
)
}

func readConfig() (err error) {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
}

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

// read in environment variables that match
// set a prefix for config, i.e. CIRCLECI_CLI_ENDPOINT
viper.SetEnvPrefix("circleci_cli")
viper.AutomaticEnv()

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

func createConfig() (err error) {
// Don't support creating config at --config flag, only default
if cfgFile != "" {
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) {
Logger.FatalOnError(
fmt.Sprintf("Error creating directory: '%s'", path),
os.Mkdir(path, 0700),
)
} else {
Logger.FatalOnError(fmt.Sprintf("Error accessing '%s'", path), err)
}

// Create default config file
if _, err = os.Create(cfgPathDefault); err != nil {
return err
}

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

// 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.")
}

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

Logger.Infof("Your configuration has been created in `%v`.\n", cfgPathDefault)
Logger.Infoln("It can edited manually for advanced settings.")
return err
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")))
}
119 changes: 119 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package config

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")),
}

// Init is called on initialize of the root command.
func Init() {
Logger = logger.NewLogger(Config.Verbose)
if err := Read(); err == nil {
return
}

Logger.FatalOnError("Error creating a new config file", Create())

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

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

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

// read in environment variables that match
// set a prefix for config, i.e. CIRCLECI_CLI_ENDPOINT
viper.SetEnvPrefix("circleci_cli")
viper.AutomaticEnv()

// If a config file is found, read it in.
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)
}

path := fmt.Sprintf("%s/.circleci", os.Getenv("HOME"))

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)
}

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

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

// 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.")
}

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

Logger.Infof("Your configuration has been created in `%v`.\n", Config.DefaultPath)
Logger.Infoln("It can edited manually for advanced settings.")
return err
}

0 comments on commit 84da9f9

Please sign in to comment.