package cmd
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"reflect"
"regexp"
"strings"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
yaml "gopkg.in/yaml.v2"
"github.com/vmware/dispatch/pkg/dispatchcli/i18n"
"github.com/vmware/dispatch/pkg/utils"
)
var cmdConfig struct {
Current string `json:"current"`
Contexts map[string]*hostConfig `json:"contexts"`
}
type hostConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Scheme string `json:"scheme"`
Organization string `json:"organization"`
Project string `json:"project"`
Cookie string `json:"cookie"`
Insecure bool `json:"insecure"`
Namespace string `json:"namespace,omitempty"`
Output string `json:"-"`
Token string `json:"-"`
ServiceAccount string `json:"serviceaccount,omitempty"`
JWTPrivateKey string `json:"jwtprivatekey,omitempty"`
}
var dispatchConfig = hostConfig{}
var validResources = i18n.T(`Valid resource types include:
* endpoints
* base-images
* eventdrivers
* eventdrivertypes
* functions
* images
* secrets
* subscriptions
`)
var (
dispatchConfigPath = ""
cmds *cobra.Command
execRunner Runner
)
func initConfig() {
if dispatchConfigPath != "" {
viper.SetConfigFile(dispatchConfigPath)
readConfigFile(false)
} else if config := os.Getenv("DISPATCH_CONFIG"); config != "" {
viper.SetConfigFile(config)
readConfigFile(false)
} else {
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
viper.AddConfigPath(path.Join(home, ".dispatch"))
viper.SetConfigName("config")
readConfigFile(true)
}
cmdConfig.Contexts = make(map[string]*hostConfig)
viper.Unmarshal(&cmdConfig)
if viperCtx := viper.Sub(fmt.Sprintf("contexts.%s", cmdConfig.Current)); viperCtx != nil {
bindFlagsAndEnv(viperCtx)
viperCtx.Unmarshal(&dispatchConfig)
} else {
v := viper.New()
bindFlagsAndEnv(v)
v.Unmarshal(&dispatchConfig)
}
if dispatchConfig.Output != "" {
matched, _ := regexp.MatchString("yaml|json", dispatchConfig.Output)
if !matched {
fmt.Println("invalid format option [yaml|json]")
os.Exit(1)
}
}
}
func readConfigFile(ignoreNotFound bool) {
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ignoreNotFound && ok {
return
}
fatal(fmt.Sprintf("Error reading config file %s: %s", viper.ConfigFileUsed(), err), 1)
}
}
func writeConfigFile() {
writeConfigFile := viper.ConfigFileUsed()
if writeConfigFile == "" {
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
writeConfigFile = path.Join(home, ".dispatch", "config.json")
}
if cmdConfig.Current == "" {
cmdConfig.Current = formatContextName(dispatchConfig.Host)
}
cmdConfig.Contexts[cmdConfig.Current] = &dispatchConfig
vsConfigJSON, err := json.MarshalIndent(cmdConfig, "", " ")
if err != nil {
fatal(fmt.Sprintf("error writing configuration, %s", "error marshalling json"), 1)
}
err = ioutil.WriteFile(writeConfigFile, vsConfigJSON, 0644)
if err != nil {
fatal(fmt.Sprintf("error writing configuration to file: %s, %s", viper.ConfigFileUsed(), err), 1)
}
}
func bindFlagsAndEnv(v *viper.Viper) {
cmds.PersistentFlags().VisitAll(func(f *pflag.Flag) {
key := utils.SeparatedToCamelCase(f.Name, "-")
v.BindPFlag(key, f)
v.BindEnv(key, "DISPATCH_"+strings.ToUpper(strings.Replace(f.Name, "-", "_", -1)))
})
}
func NewCLI(in io.Reader, out, errOut io.Writer) *cobra.Command {
cobra.OnInitialize(initConfig)
cmds = &cobra.Command{
Use: "dispatch",
Short: i18n.T("dispatch allows to interact with VMware Dispatch framework."),
Long: i18n.T("dispatch allows to interact with VMware Dispatch framework."),
Run: runHelp,
}
execRunner = &execCmdRunner{}
cmds.PersistentFlags().StringVar(&dispatchConfigPath, "config", "", "config file (default is $HOME/.dispatch)")
cmds.PersistentFlags().String("host", "localhost", "Dispatch host to connect to")
cmds.PersistentFlags().Int("port", 443, "Port which Dispatch is listening on")
cmds.PersistentFlags().String("scheme", "https", "The protocol scheme to use, either http or https")
cmds.PersistentFlags().String("organization", "", "Organization name")
cmds.PersistentFlags().StringVar(&dispatchConfig.Project, "project", "", "Project name")
cmds.PersistentFlags().Bool("insecure", false, "If true, will ignore verifying the server's certificate and your https connection is insecure.")
cmds.PersistentFlags().StringVarP(&dispatchConfig.Output, "output", "o", "", "Output format [json|yaml]")
cmds.PersistentFlags().String("token", "", "JWT Bearer Token")
cmds.PersistentFlags().String("service-account", "", "Name of the service account, if specified, a jwt-private-key is also required")
cmds.PersistentFlags().String("jwt-private-key", "", "JWT private key file path")
cmds.PersistentFlags().MarkHidden("json")
cmds.AddCommand(NewCmdGet(out, errOut))
cmds.AddCommand(NewCmdCreate(out, errOut))
cmds.AddCommand(NewCmdUpdate(out, errOut))
cmds.AddCommand(NewCmdExec(in, out, errOut))
cmds.AddCommand(NewCmdDelete(out, errOut))
cmds.AddCommand(NewCmdLogin(in, out, errOut))
cmds.AddCommand(NewCmdLogout(in, out, errOut))
cmds.AddCommand(NewCmdEmit(out, errOut))
cmds.AddCommand(NewCmdVersion(out))
cmds.AddCommand(NewCmdIam(out, errOut))
cmds.AddCommand(NewCmdManage(out, errOut))
cmds.AddCommand(NewCmdLog(out, errOut))
return cmds
}
func runHelp(cmd *cobra.Command, args []string) {
cmd.Help()
}
func resourceName(name string) string {
if name != "" {
return name
}
return utils.RandomResourceName()
}
func getOrgFromConfig() string {
return dispatchConfig.Organization
}
func getProjectFromConfig() string {
return dispatchConfig.Project
}
func formatOutput(out io.Writer, list bool, models interface{}) (bool, error) {
v := reflect.ValueOf(models)
if !list && (v.Kind() == reflect.Slice || v.Kind() == reflect.Array) {
item := v.Index(0)
models = item.Interface()
}
if dispatchConfig.Output == "json" {
encoder := json.NewEncoder(out)
encoder.SetIndent("", " ")
return true, encoder.Encode(models)
}
if dispatchConfig.Output == "yaml" {
encoder := yaml.NewEncoder(out)
return true, encoder.Encode(models)
}
return false, nil
}