env.go 9.6 KB

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