config.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. package config
  2. import (
  3. "errors"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "github.com/fatih/color"
  10. api "github.com/porter-dev/porter/api/client"
  11. "github.com/porter-dev/porter/cli/cmd/utils"
  12. "github.com/porter-dev/porter/internal/alerter"
  13. "github.com/spf13/viper"
  14. "k8s.io/client-go/util/homedir"
  15. )
  16. var (
  17. SentryDSN = ""
  18. home = homedir.HomeDir()
  19. // config is a shared object used by all commands
  20. config = &CLIConfig{}
  21. )
  22. // CLIConfig is the set of shared configuration options for the CLI commands.
  23. // This config is used by viper: calling Set() function for any parameter will
  24. // update the corresponding field in the viper config file.
  25. type CLIConfig struct {
  26. // Driver can be either "docker" or "local", and represents which driver is
  27. // used to run an instance of the server.
  28. Driver string `yaml:"driver"`
  29. Host string `yaml:"host"`
  30. Project uint `yaml:"project"`
  31. Cluster uint `yaml:"cluster"`
  32. Token string `yaml:"token"`
  33. Registry uint `yaml:"registry"`
  34. HelmRepo uint `yaml:"helm_repo"`
  35. Kubeconfig string `yaml:"kubeconfig"`
  36. alerter alerter.Alerter
  37. }
  38. // InitAndLoadConfig populates the config object with the following precedence rules:
  39. // 1. flag
  40. // 2. env
  41. // 3. config
  42. // 4. default
  43. //
  44. // It populates the shared config object above
  45. func InitAndLoadConfig() {
  46. initAndLoadConfig(config)
  47. }
  48. func InitAndLoadNewConfig() *CLIConfig {
  49. newConfig := &CLIConfig{}
  50. initAndLoadConfig(newConfig)
  51. return newConfig
  52. }
  53. func InitAlerter(config *CLIConfig) alerter.Alerter {
  54. var err error
  55. config.alerter = &alerter.NoOpAlerter{}
  56. if SentryDSN != "" {
  57. config.alerter, err = alerter.NewCLISentryAlerter(SentryDSN, "cli", Version)
  58. if err != nil {
  59. // fallback to noop alerter
  60. config.alerter = &alerter.NoOpAlerter{}
  61. }
  62. }
  63. return config.alerter
  64. }
  65. func initAndLoadConfig(_config *CLIConfig) {
  66. initFlagSet()
  67. // check that the .porter folder exists; create if not
  68. porterDir := filepath.Join(home, ".porter")
  69. if _, err := os.Stat(porterDir); os.IsNotExist(err) {
  70. os.Mkdir(porterDir, 0700)
  71. } else if err != nil {
  72. color.New(color.FgRed).Fprintf(os.Stderr, "%v\n", err)
  73. os.Exit(1)
  74. }
  75. viper.SetConfigName("porter")
  76. viper.SetConfigType("yaml")
  77. viper.AddConfigPath(porterDir)
  78. // Bind the flagset initialized above
  79. viper.BindPFlags(utils.DriverFlagSet)
  80. viper.BindPFlags(utils.DefaultFlagSet)
  81. viper.BindPFlags(utils.RegistryFlagSet)
  82. viper.BindPFlags(utils.HelmRepoFlagSet)
  83. // Bind the environment variables with prefix "PORTER_"
  84. viper.SetEnvPrefix("PORTER")
  85. viper.BindEnv("host")
  86. viper.BindEnv("project")
  87. viper.BindEnv("cluster")
  88. viper.BindEnv("token")
  89. err := viper.ReadInConfig()
  90. if err != nil {
  91. if _, ok := err.(viper.ConfigFileNotFoundError); ok {
  92. // create blank config file
  93. err := ioutil.WriteFile(filepath.Join(home, ".porter", "porter.yaml"), []byte{}, 0644)
  94. if err != nil {
  95. color.New(color.FgRed).Fprintf(os.Stderr, "%v\n", err)
  96. os.Exit(1)
  97. }
  98. } else {
  99. // Config file was found but another error was produced
  100. color.New(color.FgRed).Fprintf(os.Stderr, "%v\n", err)
  101. os.Exit(1)
  102. }
  103. }
  104. // unmarshal the config into the shared config struct
  105. err = viper.Unmarshal(_config)
  106. if err != nil {
  107. color.New(color.FgRed).Fprintf(os.Stderr, "error reading config: %v\n", err)
  108. os.Exit(1)
  109. }
  110. }
  111. // initFlagSet initializes the shared flags used by multiple commands
  112. func initFlagSet() {
  113. utils.DriverFlagSet.StringVar(
  114. &config.Driver,
  115. "driver",
  116. "local",
  117. "driver to use (local or docker)",
  118. )
  119. utils.DefaultFlagSet.StringVar(
  120. &config.Host,
  121. "host",
  122. "https://dashboard.getporter.dev",
  123. "host URL of Porter instance",
  124. )
  125. utils.DefaultFlagSet.UintVar(
  126. &config.Project,
  127. "project",
  128. 0,
  129. "project ID of Porter project",
  130. )
  131. utils.DefaultFlagSet.UintVar(
  132. &config.Cluster,
  133. "cluster",
  134. 0,
  135. "cluster ID of Porter cluster",
  136. )
  137. utils.DefaultFlagSet.StringVar(
  138. &config.Token,
  139. "token",
  140. "",
  141. "token for Porter authentication",
  142. )
  143. utils.RegistryFlagSet.UintVar(
  144. &config.Registry,
  145. "registry",
  146. 0,
  147. "registry ID of connected Porter registry",
  148. )
  149. utils.HelmRepoFlagSet.UintVar(
  150. &config.HelmRepo,
  151. "helmrepo",
  152. 0,
  153. "helm repo ID of connected Porter Helm repository",
  154. )
  155. }
  156. func GetCLIConfig() *CLIConfig {
  157. if config == nil {
  158. panic("GetCLIConfig() called before initialisation")
  159. }
  160. return config
  161. }
  162. func GetAPIClient() *api.Client {
  163. config := GetCLIConfig()
  164. if token := config.Token; token != "" {
  165. return api.NewClientWithToken(config.Host+"/api", token)
  166. }
  167. return api.NewClient(config.Host+"/api", "cookie.json")
  168. }
  169. func (c *CLIConfig) SetDriver(driver string) error {
  170. viper.Set("driver", driver)
  171. color.New(color.FgGreen).Printf("Set the current driver as %s\n", driver)
  172. err := viper.WriteConfig()
  173. if err != nil {
  174. return err
  175. }
  176. config.Driver = driver
  177. return nil
  178. }
  179. func (c *CLIConfig) SetHost(host string) error {
  180. // a trailing / can lead to errors with the api server
  181. host = strings.TrimRight(host, "/")
  182. viper.Set("host", host)
  183. color.New(color.FgGreen).Printf("Set the current host as %s\n", host)
  184. err := viper.WriteConfig()
  185. if err != nil {
  186. return err
  187. }
  188. config.Host = host
  189. return nil
  190. }
  191. func (c *CLIConfig) SetProject(projectID uint) error {
  192. if config.Kubeconfig != "" || viper.IsSet("kubeconfig") {
  193. viper.Set("kubeconfig", "")
  194. color.New(color.FgBlue).Println("Removing local kubeconfig")
  195. config.Kubeconfig = ""
  196. }
  197. viper.Set("project", projectID)
  198. color.New(color.FgGreen).Printf("Set the current project as %d\n", projectID)
  199. err := viper.WriteConfig()
  200. if err != nil {
  201. return err
  202. }
  203. config.Project = projectID
  204. return nil
  205. }
  206. func (c *CLIConfig) SetCluster(clusterID uint) error {
  207. if config.Kubeconfig != "" || viper.IsSet("kubeconfig") {
  208. viper.Set("kubeconfig", "")
  209. color.New(color.FgBlue).Println("Removing local kubeconfig")
  210. config.Kubeconfig = ""
  211. }
  212. viper.Set("cluster", clusterID)
  213. color.New(color.FgGreen).Printf("Set the current cluster as %d\n", clusterID)
  214. err := viper.WriteConfig()
  215. if err != nil {
  216. return err
  217. }
  218. config.Cluster = clusterID
  219. return nil
  220. }
  221. func (c *CLIConfig) SetToken(token string) error {
  222. viper.Set("token", token)
  223. err := viper.WriteConfig()
  224. if err != nil {
  225. return err
  226. }
  227. config.Token = token
  228. return nil
  229. }
  230. func (c *CLIConfig) SetRegistry(registryID uint) error {
  231. viper.Set("registry", registryID)
  232. color.New(color.FgGreen).Printf("Set the current registry as %d\n", registryID)
  233. err := viper.WriteConfig()
  234. if err != nil {
  235. return err
  236. }
  237. config.Registry = registryID
  238. return nil
  239. }
  240. func (c *CLIConfig) SetHelmRepo(helmRepoID uint) error {
  241. viper.Set("helm_repo", helmRepoID)
  242. color.New(color.FgGreen).Printf("Set the current Helm repo as %d\n", helmRepoID)
  243. err := viper.WriteConfig()
  244. if err != nil {
  245. return err
  246. }
  247. config.HelmRepo = helmRepoID
  248. return nil
  249. }
  250. func (c *CLIConfig) SetKubeconfig(kubeconfig string) error {
  251. path, err := filepath.Abs(kubeconfig)
  252. if err != nil {
  253. return err
  254. }
  255. if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
  256. return fmt.Errorf("%s does not exist", path)
  257. }
  258. viper.Set("kubeconfig", path)
  259. color.New(color.FgGreen).Printf("Set the path to kubeconfig as %s\n", path)
  260. err = viper.WriteConfig()
  261. if err != nil {
  262. return err
  263. }
  264. config.Kubeconfig = kubeconfig
  265. return nil
  266. }