config.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. package config
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  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/spf13/viper"
  13. "k8s.io/client-go/util/homedir"
  14. )
  15. var home = homedir.HomeDir()
  16. // config is a shared object used by all commands
  17. // var config = &CLIConfig{}
  18. // CLIConfig is the set of shared configuration options for the CLI commands.
  19. // This config is used by viper: calling Set() function for any parameter will
  20. // update the corresponding field in the viper config file.
  21. type CLIConfig struct {
  22. // Driver can be either "docker" or "local", and represents which driver is
  23. // used to run an instance of the server.
  24. Driver string `yaml:"driver"`
  25. Host string `yaml:"host"`
  26. Project uint `yaml:"project"`
  27. Cluster uint `yaml:"cluster"`
  28. Token string `yaml:"token"`
  29. Registry uint `yaml:"registry"`
  30. HelmRepo uint `yaml:"helm_repo"`
  31. Kubeconfig string `yaml:"kubeconfig"`
  32. }
  33. // InitAndLoadConfig populates the config object with the following precedence rules:
  34. // 1. flag
  35. // 2. env
  36. // 3. config
  37. // 4. default
  38. func InitAndLoadConfig() (CLIConfig, error) {
  39. return initAndLoadConfig()
  40. }
  41. func initAndLoadConfig() (CLIConfig, error) {
  42. var config CLIConfig
  43. porterDir, err := getOrCreatePorterDirectoryAndConfig()
  44. if err != nil {
  45. return config, fmt.Errorf("unable to get or create porter directory: %w", err)
  46. }
  47. viper.SetConfigName("porter")
  48. viper.SetConfigType("yaml")
  49. viper.AddConfigPath(porterDir)
  50. utils.DriverFlagSet.StringVar(
  51. &config.Driver,
  52. "driver",
  53. "local",
  54. "driver to use (local or docker)",
  55. )
  56. err = viper.BindPFlags(utils.DriverFlagSet)
  57. if err != nil {
  58. return config, err
  59. }
  60. utils.DefaultFlagSet.StringVar(
  61. &config.Host,
  62. "host",
  63. "https://dashboard.getporter.dev",
  64. "host URL of Porter instance",
  65. )
  66. utils.DefaultFlagSet.UintVar(
  67. &config.Project,
  68. "project",
  69. 0,
  70. "project ID of Porter project",
  71. )
  72. utils.DefaultFlagSet.UintVar(
  73. &config.Cluster,
  74. "cluster",
  75. 0,
  76. "cluster ID of Porter cluster",
  77. )
  78. utils.DefaultFlagSet.StringVar(
  79. &config.Token,
  80. "token",
  81. "",
  82. "token for Porter authentication",
  83. )
  84. utils.RegistryFlagSet.UintVar(
  85. &config.Registry,
  86. "registry",
  87. 0,
  88. "registry ID of connected Porter registry",
  89. )
  90. err = viper.BindPFlags(utils.RegistryFlagSet)
  91. if err != nil {
  92. return config, err
  93. }
  94. utils.HelmRepoFlagSet.UintVar(
  95. &config.HelmRepo,
  96. "helmrepo",
  97. 0,
  98. "helm repo ID of connected Porter Helm repository",
  99. )
  100. err = viper.BindPFlags(utils.HelmRepoFlagSet)
  101. if err != nil {
  102. return config, err
  103. }
  104. err = viper.BindPFlags(utils.DefaultFlagSet)
  105. if err != nil {
  106. return config, err
  107. }
  108. viper.SetEnvPrefix("PORTER")
  109. err = viper.BindEnv("host")
  110. if err != nil {
  111. return config, err
  112. }
  113. err = viper.BindEnv("project")
  114. if err != nil {
  115. return config, err
  116. }
  117. err = viper.BindEnv("cluster")
  118. if err != nil {
  119. return config, err
  120. }
  121. err = viper.BindEnv("token")
  122. if err != nil {
  123. return config, err
  124. }
  125. err = createAndLoadPorterYaml(porterDir)
  126. if err != nil {
  127. return config, fmt.Errorf("unable to load porter config: %w", err)
  128. }
  129. err = viper.Unmarshal(&config)
  130. if err != nil {
  131. return config, fmt.Errorf("unable to unmarshal porter config: %w", err)
  132. }
  133. return config, nil
  134. }
  135. // getOrCreatePorterDirectoryAndConfig checks that the .porter folder exists; create if not
  136. func getOrCreatePorterDirectoryAndConfig() (string, error) {
  137. porterDir := filepath.Join(home, ".porter")
  138. _, err := os.Stat(porterDir)
  139. if err != nil {
  140. if !os.IsNotExist(err) {
  141. return "", fmt.Errorf("error reading porter directory: %w", err)
  142. }
  143. err = os.Mkdir(porterDir, 0o700)
  144. if err != nil {
  145. return "", fmt.Errorf("error creating porter directory: %w", err)
  146. }
  147. }
  148. return porterDir, nil
  149. }
  150. // createAndLoadPorterYaml loads a porter.yaml config into Viper if it exists, or creates the file if it does not
  151. func createAndLoadPorterYaml(porterDir string) error {
  152. err := viper.ReadInConfig()
  153. if err != nil {
  154. _, ok := err.(viper.ConfigFileNotFoundError)
  155. if !ok {
  156. return fmt.Errorf("unknown error reading ~/.porter/porter.yaml config: %w", err)
  157. }
  158. err := os.WriteFile(filepath.Join(porterDir, "porter.yaml"), []byte{}, 0o644) //nolint:gosec // do not want to change program logic. Should be addressed later
  159. if err != nil {
  160. return fmt.Errorf("unable to create ~/.porter/porter.yaml config: %w", err)
  161. }
  162. }
  163. return nil
  164. }
  165. // func GetCLIConfig() *CLIConfig {
  166. // if config == nil {
  167. // panic("GetCLIConfig() called before initialisation")
  168. // }
  169. // return config
  170. // }
  171. // func GetAPIClient() api.Client {
  172. // ctx := ctx
  173. // config := GetCLIConfig()
  174. // client := api.NewClientWithConfig(ctx, api.NewClientInput{
  175. // BaseURL: fmt.Sprintf("%s/api", config.Host),
  176. // BearerToken: config.Token,
  177. // CookieFileName: "cookie.json",
  178. // })
  179. // return client
  180. // }
  181. func (c *CLIConfig) SetDriver(driver string) error {
  182. viper.Set("driver", driver)
  183. color.New(color.FgGreen).Printf("Set the current driver as %s\n", driver)
  184. err := viper.WriteConfig()
  185. if err != nil {
  186. return err
  187. }
  188. c.Driver = driver
  189. return nil
  190. }
  191. func (c *CLIConfig) SetHost(host string) error {
  192. // a trailing / can lead to errors with the api server
  193. host = strings.TrimRight(host, "/")
  194. viper.Set("host", host)
  195. // let us clear the project ID, cluster ID, and token when we reset a host
  196. viper.Set("project", 0)
  197. viper.Set("cluster", 0)
  198. viper.Set("token", "")
  199. err := viper.WriteConfig()
  200. if err != nil {
  201. return err
  202. }
  203. color.New(color.FgGreen).Printf("Set the current host as %s\n", host)
  204. c.Host = host
  205. c.Project = 0
  206. c.Cluster = 0
  207. c.Token = ""
  208. return nil
  209. }
  210. // SetProject sets a project for all API commands
  211. func (c *CLIConfig) SetProject(ctx context.Context, apiClient api.Client, projectID uint) error {
  212. viper.Set("project", projectID)
  213. color.New(color.FgGreen).Printf("Set the current project as %d\n", projectID)
  214. if c.Kubeconfig != "" || viper.IsSet("kubeconfig") {
  215. color.New(color.FgYellow).Println("Please change local kubeconfig if needed")
  216. }
  217. err := viper.WriteConfig()
  218. if err != nil {
  219. return err
  220. }
  221. c.Project = projectID
  222. resp, err := apiClient.ListProjectClusters(ctx, projectID)
  223. if err == nil {
  224. clusters := *resp
  225. if len(clusters) == 1 {
  226. _ = c.SetCluster(clusters[0].ID)
  227. }
  228. }
  229. return nil
  230. }
  231. func (c *CLIConfig) SetCluster(clusterID uint) error {
  232. viper.Set("cluster", clusterID)
  233. color.New(color.FgGreen).Printf("Set the current cluster as %d\n", clusterID)
  234. if c.Kubeconfig != "" || viper.IsSet("kubeconfig") {
  235. color.New(color.FgYellow).Println("Please change local kubeconfig if needed")
  236. }
  237. err := viper.WriteConfig()
  238. if err != nil {
  239. return err
  240. }
  241. c.Cluster = clusterID
  242. return nil
  243. }
  244. func (c *CLIConfig) SetToken(token string) error {
  245. viper.Set("token", token)
  246. err := viper.WriteConfig()
  247. if err != nil {
  248. return err
  249. }
  250. c.Token = token
  251. return nil
  252. }
  253. func (c *CLIConfig) SetRegistry(registryID uint) error {
  254. viper.Set("registry", registryID)
  255. color.New(color.FgGreen).Printf("Set the current registry as %d\n", registryID)
  256. err := viper.WriteConfig()
  257. if err != nil {
  258. return err
  259. }
  260. c.Registry = registryID
  261. return nil
  262. }
  263. func (c *CLIConfig) SetHelmRepo(helmRepoID uint) error {
  264. viper.Set("helm_repo", helmRepoID)
  265. color.New(color.FgGreen).Printf("Set the current Helm repo as %d\n", helmRepoID)
  266. err := viper.WriteConfig()
  267. if err != nil {
  268. return err
  269. }
  270. c.HelmRepo = helmRepoID
  271. return nil
  272. }
  273. func (c *CLIConfig) SetKubeconfig(kubeconfig string) error {
  274. path, err := filepath.Abs(kubeconfig)
  275. if err != nil {
  276. return err
  277. }
  278. if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
  279. return fmt.Errorf("%s does not exist", path)
  280. }
  281. viper.Set("kubeconfig", path)
  282. color.New(color.FgGreen).Printf("Set the path to kubeconfig as %s\n", path)
  283. err = viper.WriteConfig()
  284. if err != nil {
  285. return err
  286. }
  287. c.Kubeconfig = kubeconfig
  288. return nil
  289. }
  290. // ValidateCLIEnvironment checks that all required variables are present for running the CLI
  291. func (c *CLIConfig) ValidateCLIEnvironment() error {
  292. if c.Token == "" {
  293. return fmt.Errorf("no auth token present, please run 'porter auth login' to authenticate")
  294. }
  295. if c.Project == 0 {
  296. return fmt.Errorf("no project selected, please run 'porter config set-project' to select a project")
  297. }
  298. if c.Cluster == 0 {
  299. return fmt.Errorf("no cluster selected, please run 'porter config set-cluster' to select a cluster")
  300. }
  301. return nil
  302. }