is there a way of dynamically registering cobra commands based on a viper configuration file? Basically I want to only register commands that are available within my config file as "modules".
My root.go's init function looks like this
func init() {
cobra.OnInitialize(initConfig)
//don't I have access to viper.GetString("foo") here? It returns empty string instead of "bar"
registerCommandsPalette()
//disable help command
rootCmd.SetHelpCommand(&cobra.Command{
Use: "no-help",
Hidden: true,
})
//disable completion command
rootCmd.CompletionOptions.DisableDefaultCmd = true
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.toolbox.yaml)")
}
func registerCommandsPalette() {
//here I want to access the config e.g. viper.GetString("foo")
//on various conditions I want to add a command to rootCmd.
rootCmd.AddCommand(server.ServerCmd)
rootCmd.AddCommand(net.NetCmd)
}
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)
// Search config in home directory with name ".toolbox" (without extension).
viper.AddConfigPath(home)
viper.SetConfigType("json")
viper.SetConfigName(".toolbox")
}
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err != nil {
fmt.Println("Could not load configuration file")
os.Exit(1)
}
}
My config file looks like this:
{
"available_modules": [
"module-1",
"module-2"
],
"foo": "bar"
//....
}
Whenever I use viper.GetString("foo") in a command file, I do get the string "bar" back.
Do you have any ideas?
Best,
Jakob
Related
I'm having trouble using Cobra and Viper together. This is what I'm doing:
var options util.Config = util.Config{}
var rootCmd = &cobra.Command{
Use: "test [command] [subcommands]",
Run: func(cmd *cobra.Command, args []string) {
if err := server.Run(); err != nil {
l.Fatal(err)
}
},
}
// initConfig helps initialise configuration with a stated path
func initConfig() {
if options.Path != "" {
viper.SetConfigFile(options.Path)
}
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
fmt.Println("Could not use config file: ", viper.ConfigFileUsed())
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVarP(&options.Path, "config", "n", "", "Path of a configuration file")
rootCmd.PersistentFlags().StringVarP(&options.Password, "password", "d", "", "Password to access the server")
viper.BindPFlag("password", rootCmd.PersistentFlags().Lookup("password"))
rootCmd.AddCommand(log.Cmd(&options))
}
func main() {
rootCmd.Execute()
}
I'm trying to retrieve the value options.Password within my subcommand (an added command within log.Cmd(&options)) however the field isn't being populated. I'm pretty sure I'm following the Cobra docs properly: https://github.com/spf13/cobra#create-rootcmd
Binding cobra flags to viper options only binds cobra flags to viper options, not vice versa. So you can access the password via
pass := viper.GetString("password")
if the password is set either via viper or cobra, but not via the variables defined in your flag definitions.
Basically, you have two options here: Either you use cobra without pointing your flags to variables, and then set your globals via various calls to viper.Get* (you can even sanitize them while being at it), or you use viper as sort of a „parameter registry“ and call viper.Get* where needed. I tend to go with the former solution.
Here is an snippet from my-tool/cmd/root.go
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.my-tool.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
fmt.Println("Config file set")
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
fmt.Println("Config file NOT set")
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
The code has been generated from the scaffolding process of cobra cli, i.e. via ~/go/cobra/init my-tool --pkg-name github.com/something/my-tool
I am trying to tentatively pass the config flag to check if the program is handling it:
▶ go run main.go --config "test"
However, though I 'd expect the init() function to make the call to cobra.OnInitialize(initConfig) and parse the flag as indicated by line:
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.my-tool.yaml)")
and finally to see one of those two messages in the if statement:
func initConfig() {
if cfgFile != "" {
fmt.Println("Config file set")
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
fmt.Println("Config file NOT set")
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
instead, all I get is the root command's help message; why is that?
edit: from what I see by adding some print statements, the initConfig() is never called (for some reason), i.e. as if cobra.OnInitialize(initConfig) does not do anything.
You need to Specify your command first
▶ go run main.go "yourcommand" --config "test"
See:
» go run main.go --config "blah"
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
...
» go run main.go preview --config "blah"
Error: open : no such file or directory
exit status 1
I'm creating an api with golang and zeit-now. I want the API to return a password generated from a list of words (30k). Everything works if I use a local server (http.ListenAndServe), but when I try to access the concerned endpoint with the now link, an error occurs.
I tried to use the config.includeFiles option, but it doesn't work. I also tried to use local path, absolute path but nothing is working.
Here is the now.json and randDict.go files:
now.json
"builds": [
{
"src": "/lambdas/randDict/randDict.go",
"use": "#now/go",
"config": {
"includeFiles": [
"template/wordsGist"
]
}
}
]
randDict.go
// Create a slice with the words from the words' file
func createWordsSlice() ([]string, int, error) {
// Read all the words inside the file "wordsGist"
file, err := ioutil.ReadFile("template/wordsGist")
if err != nil {
fmt.Println(err)
return nil, 0, err
}
// Split the words into a slice
words := strings.Split(string(file), "\n")
return words, len(words), nil
}
Here I expect a slice with all the words, which I'll be using later for password generation, but I get an error and a empty slice (nil).
Here is the github if you want to read all the code.
I have used the following code in filLib.go:
func LoadConfiguration(filename string) (Configuration, error) {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return Configuration{}, err
}
var c Configuration
err = json.Unmarshal(bytes, &c)
if err != nil {
return Configuration{}, err
}
return c, nil
}
But ioutil.ReadFile(filename) return *os.PathError.
Both the files config.json and filLib.go are in same folder.
The path of *.go file is not directly relevant to the working directory of the executing compiled code. Verify where your code thinks it actually is (compare to where you think it should be :).
import(
"os"
"fmt"
"log"
)
func main() {
dir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
fmt.Println(dir)
}
The issue might be with the filename you're providing. Below is the code sample that working fine for me.
func loadConfig() {
var AppConfig Conf
raw, err := ioutil.ReadFile("conf/conf.json")
if err != nil {
log.Println("Error occured while reading config")
return
}
json.Unmarshal(raw, &AppConfig)
}
I found this library enter link description here
It is a very simple and easy to use configuration library, allowing Json based config files for your Go application. Configuration provider reads configuration data from config.json file. You can get the string value of a configuration, or bind an interface to a valid JSON section by related section name convention parameter.
Consider the following config.json file:
{
"ConnectionStrings": {
"DbConnection": "Server=.;User Id=app;Password=123;Database=Db",
"LogDbConnection": "Server=.;User Id=app;Password=123;Database=Log"
},
"Caching": {
"ApplicationKey": "key",
"Host": "127.0.01"
},
"Website": {
"ActivityLogEnable": "true",
"ErrorMessages": {
"InvalidTelephoneNumber": "Invalid Telephone Number",
"RequestNotFound": "Request Not Found",
"InvalidConfirmationCode": "Invalid Confirmation Code"
}
},
"Services": {
"List": [
{
"Id": 1,
"Name": "Service1"
},
{
"Id": 2,
"Name": "Service2"
},
{
"Id": 3,
"Name": "Service3"
}
]
}
}
The following code displays how to access some of the preceding configuration settings. You can get config value via GetSection function with specifying Json sections as string parameter split by ":"
c, err := jsonconfig.GetSection("ConnectionStrings:DbConnection")
Any valid Json is a valid configuration type. You can also bind a struct via jsonconfig. For example, Caching configuration can be bind to valid struct:
type Caching struct {
ApplicationKey string
Host string
}
var c Caching
err = jsonconfig.Bind(&c, "Caching")
The documentation in Cobra and Viper are confusing me. I did cobra init fooproject and then inside the project dir I did cobra add bar. I have a PersistentFlag that is named foo and here is the init function from the root command.
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
}
fmt.Println(cfgFile)
fmt.Println("fooString is: ", fooString)
}
func init() {
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags, which, if defined here,
// will be global for your application.
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.fooproject.yaml)")
RootCmd.PersistentFlags().StringVar(&fooString, "foo", "", "loaded from config")
viper.BindPFlag("foo", RootCmd.PersistentFlags().Lookup("foo"))
// Cobra also supports local flags, which will only run
// when this action is called directly.
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
My configuration file looks like this...
---
foo: aFooString
And when I call go run main.go I see this...
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
Usage:
fooproject [command]
Available Commands:
bar A brief description of your command
help Help about any command
Flags:
--config string config file (default is $HOME/.fooproject.yaml)
--foo string loaded from config
-h, --help help for fooproject
-t, --toggle Help message for toggle
Use "fooproject [command] --help" for more information about a command.
fooString is:
When I call go run main.go bar I see this...
Using config file: my/gopath/github.com/user/fooproject/.fooproject.yaml
bar called
fooString is:
So it is using the configuration file, but neither one of them seems to be reading it. Maybe I am misunderstanding the way that Cobra and Viper work. Any ideas?
To combine spf13/cobra and spf13/viper, first define the flag with Cobra:
RootCmd.PersistentFlags().StringP("foo", "", "loaded from config")
Bind it with Viper:
viper.BindPFlag("foo", RootCmd.PersistentFlags().Lookup("foo"))
And get the variable via the Viper's method:
fmt.Println("fooString is: ", viper.GetString("foo"))
Since Viper values are somewhat inferior to pflags (e.g. no support for custom data types), I was not satisfied with answer "use Viper to retrieve values", and ended up writing small helper type to put values back in pflags.
type viperPFlagBinding struct {
configName string
flagValue pflag.Value
}
type viperPFlagHelper struct {
bindings []viperPFlagBinding
}
func (vch *viperPFlagHelper) BindPFlag(configName string, flag *pflag.Flag) (err error) {
err = viper.BindPFlag(configName, flag)
if err == nil {
vch.bindings = append(vch.bindings, viperPFlagBinding{configName, flag.Value})
}
return
}
func (vch *viperPFlagHelper) setPFlagsFromViper() {
for _, v := range vch.bindings {
v.flagValue.Set(viper.GetString(v.configName))
}
}
func main() {
var rootCmd = &cobra.Command{}
var viperPFlagHelper viperPFlagHelper
rootCmd.PersistentFlags().StringVar(&config.Password, "password", "", "API server password (remote HTTPS mode only)")
viperPFlagHelper.BindPFlag("password", rootCmd.Flag("password"))
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return err
}
}
viperPFlagHelper.setPFlagsFromViper()
return nil
}
}
For those facing the same issue, the problem is on this call:
cobra.OnInitialize(initConfig)
The function initConfig is not executed directly but append to an array of initializers https://github.com/spf13/cobra/blob/master/cobra.go#L80
So, the values stored in your config file will be loaded when the Run field is executed:
rootCmd = &cobra.Command{
Use: "example",
Short: "example cmd",
Run: func(cmd *cobra.Command, args []string) { // OnInitialize is called first
fmt.Println(viper.AllKeys())
},
}
#WGH 's answer is correct, you can do something with the persistent flag in rootCmd.PersistenPreRun, which will be available in all other sub commands.
var rootCmd = &cobra.Command{
PersistentPreRun: func(_ *cobra.Command, _ []string) {
if persistentflag {
// do something with persistentflag
}
},