I have the following configuration model:
type Config struct {
Project []Project `mapstructure:"project"`
}
type Project struct {
Name string `mapstructure:"name"`
}
I want to be able to configure this using a configuration file as well as options on the command line. I know how to do the config file by passing it in the correct format and then unmarshalling it.
However what I cannot work out how to do is have the name of the project set on the command line using Cobra and then have Viper bind that value as the first item in the Project array.
The following is a simple program I put together to show the problem I have:
package main
import (
"fmt"
"log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type Config struct {
Project []Project `mapstructure:"project"`
Name string `mapstructure:"name"`
}
type Project struct {
Name string `mapstructure:"name"`
}
var (
config Config
rootCmd = &cobra.Command{
Use: "rjsdummy",
Short: "Dummy app to understand Viper BindPFlags",
Long: "",
PersistentPreRun: preRun,
Run: executeRun,
}
)
func init() {
var name string
var project_name string
cobra.OnInitialize()
// configure the flags on the command line
rootCmd.Flags().StringVarP(&name, "name", "n", "", "Your name")
rootCmd.Flags().StringVarP(&project_name, "project", "p", "", "Project name")
// bind the flags to the configuration
viper.BindPFlag("name", rootCmd.Flags().Lookup(("name")))
viper.BindPFlag("project.0.name", rootCmd.Flags().Lookup(("project")))
}
func preRun(ccmd *cobra.Command, args []string) {
err := viper.Unmarshal(&config)
if err != nil {
log.Fatalf("Unable to read Viper options into configuration: %v", err)
}
}
func executeRun(ccmd *cobra.Command, args []string) {
fmt.Printf("Your name: %s\n", config.Name)
fmt.Printf("Project name: %s\n", config.Project[0].Name)
}
func main() {
rootCmd.Execute()
}
When I run this with the command go run .\binding.go -n Russell -p Turtle
I get the following output:
So I know that the line viper.BindPFlag("project.0.name", rootCmd.Flags().Lookup(("project")))
is not working. If I change this to project[0].name
I get a stack trace. The question is how do I add this (and other attributes) as an the first item in array of complex objects? Can I have a second Viper that would read into another object and then add to the main config or is there another way?
After playing around with this I have the answer.
Even though I have set the configuation so that it has a slice of project Project []Project
, Viper is clever enough to work this out.
So to bind the project name to the first element of the slice, it is as simple as using:
viper.BindPFlag("project.name", runCmd.Flags().Lookup("name"))
No index is required. However I can print the value with:
fmt.Println(Config.Project[0].Name)
I was over thinking this