2
0

env.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. package commands
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "time"
  7. "github.com/briandowns/spinner"
  8. "github.com/fatih/color"
  9. api "github.com/porter-dev/porter/api/client"
  10. "github.com/porter-dev/porter/api/server/handlers/environment_groups"
  11. "github.com/porter-dev/porter/api/server/handlers/porter_app"
  12. "github.com/porter-dev/porter/api/types"
  13. "github.com/porter-dev/porter/cli/cmd/config"
  14. "github.com/spf13/cobra"
  15. )
  16. var (
  17. appName string
  18. envGroupName string
  19. envFilePath string
  20. )
  21. type envVariables struct {
  22. Variables map[string]string `json:"variables"`
  23. Secrets map[string]string `json:"secrets"`
  24. }
  25. type envVariableDeletions struct {
  26. Variables []string `json:"variables"`
  27. Secrets []string `json:"secrets"`
  28. }
  29. func registerCommand_Env(cliConf config.CLIConfig) *cobra.Command {
  30. envCmd := &cobra.Command{
  31. Use: "env",
  32. Args: cobra.MinimumNArgs(0),
  33. Short: "Manage environment variables for a project",
  34. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  35. if len(cmd.Commands()) == 1 {
  36. return nil
  37. }
  38. if appName == "" && envGroupName == "" {
  39. return fmt.Errorf("must specify either --app or --group")
  40. }
  41. if appName != "" && envGroupName != "" {
  42. return fmt.Errorf("only one of --app or --group can be specified")
  43. }
  44. return nil
  45. },
  46. RunE: func(cmd *cobra.Command, args []string) error {
  47. return cmd.Help()
  48. },
  49. }
  50. envCmd.PersistentFlags().StringVarP(&appName, "app", "a", "", "app name")
  51. envCmd.PersistentFlags().StringVarP(&envGroupName, "group", "g", "", "environment group name")
  52. envCmd.PersistentFlags().StringVarP(&deploymentTargetName, "target", "x", "", "the name of the deployment target for the app")
  53. pullCommand := &cobra.Command{
  54. Use: "pull",
  55. Short: "Pull environment variables for an app or environment group",
  56. Long: `Pull environment variables for an app or environment group.
  57. Optionally, specify a file to write the environment variables to. Otherwise the environment variables will be written to stdout.`,
  58. Args: cobra.NoArgs,
  59. RunE: func(cmd *cobra.Command, args []string) error {
  60. return checkLoginAndRunWithConfig(cmd, cliConf, args, pullEnv)
  61. },
  62. }
  63. pullCommand.Flags().StringVarP(&envFilePath, "file", "f", "", "file to write environment variables to")
  64. setCommand := &cobra.Command{
  65. Use: "set",
  66. Short: "Set environment variables for an app or environment group",
  67. Long: `Set environment variables for an app or environment group.
  68. Both variables and secrets can be specified as key-value pairs.
  69. When updating an environment group, all apps linked to the environment group will be re-deployed, unless the --skip-redeploys flag is used.`,
  70. Args: cobra.NoArgs,
  71. RunE: func(cmd *cobra.Command, args []string) error {
  72. return checkLoginAndRunWithConfig(cmd, cliConf, args, setEnv)
  73. },
  74. }
  75. setCommand.Flags().StringToStringP("variables", "v", nil, "variables to set")
  76. setCommand.Flags().StringToStringP("secrets", "s", nil, "secrets to set")
  77. setCommand.Flags().Bool("skip-redeploys", false, "skip re-deploying apps linked to the environment group")
  78. unsetCommand := &cobra.Command{
  79. Use: "unset",
  80. Short: "Unset environment variables for an app or environment group",
  81. Long: `Unset environment variables for an app or environment group.
  82. Both variables and secrets can be specified as keys.
  83. When updating an environment group, all apps linked to the environment group will be re-deployed, unless the --skip-redeploys flag is used.`,
  84. Args: cobra.NoArgs,
  85. RunE: func(cmd *cobra.Command, args []string) error {
  86. return checkLoginAndRunWithConfig(cmd, cliConf, args, unsetEnv)
  87. },
  88. }
  89. unsetCommand.Flags().StringSliceP("variables", "v", nil, "variables to unset")
  90. unsetCommand.Flags().StringSliceP("secrets", "s", nil, "secrets to unset")
  91. unsetCommand.Flags().Bool("skip-redeploys", false, "skip re-deploying apps linked to the environment group")
  92. envCmd.AddCommand(pullCommand)
  93. envCmd.AddCommand(setCommand)
  94. envCmd.AddCommand(unsetCommand)
  95. return envCmd
  96. }
  97. func pullEnv(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
  98. var envVars envVariables
  99. if appName != "" {
  100. color.New(color.FgGreen).Printf("Pulling environment variables for app %s...\n", appName) // nolint:errcheck,gosec
  101. envVarsResp, err := client.GetAppEnvVariables(ctx, cliConf.Project, cliConf.Cluster, appName, deploymentTargetName)
  102. if err != nil {
  103. return fmt.Errorf("could not get app env variables: %w", err)
  104. }
  105. if envVarsResp == nil {
  106. return fmt.Errorf("could not get app env variables: response was nil")
  107. }
  108. envVars = envVariables{
  109. Variables: envVarsResp.EnvVariables.Variables,
  110. Secrets: envVarsResp.EnvVariables.Secrets,
  111. }
  112. }
  113. if envGroupName != "" {
  114. color.New(color.FgGreen).Printf("Pulling environment variables for environment group %s...\n", envGroupName) // nolint:errcheck,gosec
  115. envVarsResp, err := client.GetLatestEnvGroupVariables(ctx, cliConf.Project, cliConf.Cluster, envGroupName)
  116. if err != nil {
  117. return fmt.Errorf("could not get env group env variables: %w", err)
  118. }
  119. if envVarsResp == nil {
  120. return fmt.Errorf("could not get env group variables: response was nil")
  121. }
  122. envVars = envVariables{
  123. Variables: envVarsResp.Variables,
  124. Secrets: envVarsResp.Secrets,
  125. }
  126. }
  127. if envFilePath != "" {
  128. err := writeEnvFile(envFilePath, envVars)
  129. if err != nil {
  130. return fmt.Errorf("could not write env file: %w", err)
  131. }
  132. color.New(color.FgGreen).Printf("Wrote environment variables to %s\n", envFilePath) // nolint:errcheck,gosec
  133. }
  134. if envFilePath == "" {
  135. err := exportEnvVars(envVars)
  136. if err != nil {
  137. return fmt.Errorf("could not export env vars: %w", err)
  138. }
  139. }
  140. return nil
  141. }
  142. func setEnv(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
  143. var envVars envVariables
  144. variables, err := cmd.Flags().GetStringToString("variables")
  145. if err != nil {
  146. return fmt.Errorf("could not get variables: %w", err)
  147. }
  148. secrets, err := cmd.Flags().GetStringToString("secrets")
  149. if err != nil {
  150. return fmt.Errorf("could not get secrets: %w", err)
  151. }
  152. skipRedeploys, err := cmd.Flags().GetBool("skip-redeploys")
  153. if err != nil {
  154. return fmt.Errorf("could not get skip-redeploys: %w", err)
  155. }
  156. envVars = envVariables{
  157. Variables: variables,
  158. Secrets: secrets,
  159. }
  160. s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
  161. s.Color("cyan") // nolint:errcheck,gosec
  162. if appName != "" {
  163. s.Suffix = fmt.Sprintf(" Setting environment variables for app %s...", appName)
  164. s.Start()
  165. _, err := client.UpdateApp(ctx, api.UpdateAppInput{
  166. ProjectID: cliConf.Project,
  167. ClusterID: cliConf.Cluster,
  168. Name: appName,
  169. DeploymentTargetName: deploymentTargetName,
  170. Variables: envVars.Variables,
  171. Secrets: envVars.Secrets,
  172. })
  173. if err != nil {
  174. return fmt.Errorf("could not set app env variables: %w", err)
  175. }
  176. s.Stop()
  177. color.New(color.FgGreen).Printf("Updated environment variable keys in app %s:\n", appName) // nolint:errcheck,gosec
  178. }
  179. if envGroupName != "" {
  180. s.Suffix = fmt.Sprintf(" Setting environment variables for environment group %s...", envGroupName)
  181. s.Start()
  182. err := client.UpdateEnvGroup(ctx, api.UpdateEnvGroupInput{
  183. ProjectID: cliConf.Project,
  184. ClusterID: cliConf.Cluster,
  185. EnvGroupName: envGroupName,
  186. Variables: envVars.Variables,
  187. Secrets: envVars.Secrets,
  188. SkipRedeploys: skipRedeploys,
  189. })
  190. if err != nil {
  191. return fmt.Errorf("could not set env group env variables: %w", err)
  192. }
  193. s.Stop()
  194. color.New(color.FgGreen).Printf("Updated keys in environment group %s:\n", envGroupName) // nolint:errcheck,gosec
  195. }
  196. for k, v := range envVars.Variables {
  197. color.New(color.FgBlue).Printf("%s=%s\n", k, v) // nolint:errcheck,gosec
  198. }
  199. for k := range envVars.Secrets {
  200. color.New(color.FgBlue).Printf("%s=********\n", k) // nolint:errcheck,gosec
  201. }
  202. return nil
  203. }
  204. func unsetEnv(ctx context.Context, user *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, featureFlags config.FeatureFlags, cmd *cobra.Command, args []string) error {
  205. var envVarDeletions envVariableDeletions
  206. variables, err := cmd.Flags().GetStringSlice("variables")
  207. if err != nil {
  208. return fmt.Errorf("could not get variables: %w", err)
  209. }
  210. secrets, err := cmd.Flags().GetStringSlice("secrets")
  211. if err != nil {
  212. return fmt.Errorf("could not get secrets: %w", err)
  213. }
  214. skipRedeploys, err := cmd.Flags().GetBool("skip-redeploys")
  215. if err != nil {
  216. return fmt.Errorf("could not get skip-redeploys: %w", err)
  217. }
  218. envVarDeletions = envVariableDeletions{
  219. Variables: variables,
  220. Secrets: secrets,
  221. }
  222. s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
  223. s.Color("cyan") // nolint:errcheck,gosec
  224. if appName != "" {
  225. s.Suffix = fmt.Sprintf(" Unsetting environment variables for app %s...", appName)
  226. s.Start()
  227. _, err := client.UpdateApp(ctx, api.UpdateAppInput{
  228. ProjectID: cliConf.Project,
  229. ClusterID: cliConf.Cluster,
  230. Name: appName,
  231. DeploymentTargetName: deploymentTargetName,
  232. Deletions: porter_app.Deletions{
  233. EnvVariableDeletions: porter_app.EnvVariableDeletions{
  234. Variables: envVarDeletions.Variables,
  235. Secrets: envVarDeletions.Secrets,
  236. },
  237. },
  238. })
  239. if err != nil {
  240. return fmt.Errorf("could not unset app env variables: %w", err)
  241. }
  242. s.Stop()
  243. color.New(color.FgGreen).Printf("Unset environment variable keys in app %s:\n", appName) // nolint:errcheck,gosec
  244. }
  245. if envGroupName != "" {
  246. s.Suffix = fmt.Sprintf(" Unsetting environment variables for environment group %s...", envGroupName)
  247. err := client.UpdateEnvGroup(ctx, api.UpdateEnvGroupInput{
  248. ProjectID: cliConf.Project,
  249. ClusterID: cliConf.Cluster,
  250. EnvGroupName: envGroupName,
  251. Deletions: environment_groups.EnvVariableDeletions{
  252. Variables: envVarDeletions.Variables,
  253. Secrets: envVarDeletions.Secrets,
  254. },
  255. SkipRedeploys: skipRedeploys,
  256. })
  257. if err != nil {
  258. return fmt.Errorf("could not unset env group env variables: %w", err)
  259. }
  260. color.New(color.FgGreen).Printf("Unset the keys in environment group %s:\n", envGroupName) // nolint:errcheck,gosec
  261. }
  262. for _, v := range envVarDeletions.Variables {
  263. color.New(color.FgBlue).Printf("%s\n", v) // nolint:errcheck,gosec
  264. }
  265. for _, v := range envVarDeletions.Secrets {
  266. color.New(color.FgBlue).Printf("%s\n", v) // nolint:errcheck,gosec
  267. }
  268. return nil
  269. }
  270. func writeEnvFile(envFilePath string, envVars envVariables) error {
  271. // open existing file or create new file: https://pkg.go.dev/os#example-OpenFile-Append
  272. envFile, err := os.OpenFile(envFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) // nolint:gosec
  273. if err != nil {
  274. return err
  275. }
  276. defer envFile.Close() // nolint:errcheck
  277. _, err = envFile.WriteString("# Generated by Porter CLI\n")
  278. if err != nil {
  279. return err
  280. }
  281. for k, v := range envVars.Variables {
  282. _, err := envFile.WriteString(fmt.Sprintf("%s=%s\n", k, v))
  283. if err != nil {
  284. return err
  285. }
  286. }
  287. for k, v := range envVars.Secrets {
  288. _, err := envFile.WriteString(fmt.Sprintf("%s=%s\n", k, v))
  289. if err != nil {
  290. return err
  291. }
  292. }
  293. return nil
  294. }
  295. func exportEnvVars(envVars envVariables) error {
  296. for k, v := range envVars.Variables {
  297. _, err := os.Stdout.WriteString(fmt.Sprintf("%s=%s\n", k, v))
  298. if err != nil {
  299. return err
  300. }
  301. }
  302. for k, v := range envVars.Secrets {
  303. _, err := os.Stdout.WriteString(fmt.Sprintf("%s=%s\n", k, v))
  304. if err != nil {
  305. return err
  306. }
  307. }
  308. return nil
  309. }