deploy.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. package cmd
  2. import (
  3. "fmt"
  4. "os"
  5. "github.com/fatih/color"
  6. "github.com/porter-dev/porter/cli/cmd/api"
  7. "github.com/porter-dev/porter/cli/cmd/deploy"
  8. "github.com/spf13/cobra"
  9. )
  10. // deployCmd represents the "porter deploy" base command when called
  11. // without any subcommands
  12. var deployCmd = &cobra.Command{
  13. Use: "deploy",
  14. Short: "Builds and deploys a specified application given by the --app flag.",
  15. Long: fmt.Sprintf(`
  16. %s
  17. Builds and deploys a specified application given by the --app flag. For example:
  18. %s
  19. If the application has a remote Git repository source configured, this command uses the latest commit
  20. from the remote repo and branch to deploy an application. It will use the latest commit as the image
  21. tag.
  22. To build from a local directory, you must specify the --local flag. The path can be configured via the
  23. --path flag. You can also overwrite the tag using the --tag flag. For example, to build from the
  24. local directory ~/path-to-dir with the tag "testing":
  25. %s
  26. To add new configuration or update existing configuration, you can pass a values.yaml file in via the
  27. --values flag. For example;
  28. %s
  29. If your application is set up to use a Dockerfile by default, you can use a buildpack via the flag
  30. "--method pack". Conversely, if your application is set up to use a buildpack by default, you can
  31. use a Dockerfile by passing the flag "--method docker". You can specify the relative path to a Dockerfile
  32. in your remote Git repository. For example, if a Dockerfile is found at ./docker/prod.Dockerfile, you can
  33. specify it as follows:
  34. %s
  35. If an application does not have a remote Git repository source, this command will attempt to use a
  36. cloud-native buildpack builder and build from the current directory. If this is the desired behavior,
  37. you do not need to configure additional flags:
  38. %s
  39. If you would like to build from a Dockerfile instead, use the flag --dockerfile and "--method docker"
  40. as documented above. For example:
  41. %s
  42. `,
  43. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter deploy\":"),
  44. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy --app example-app"),
  45. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy --app remote-git-app --local --path ~/path-to-dir --tag testing"),
  46. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy --app remote-git-app --values my-values.yaml"),
  47. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy --app remote-git-app --method docker --dockerfile ./docker/prod.Dockerfile"),
  48. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy --app local-app"),
  49. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy --app local-app --method docker --dockerfile ~/porter-test/prod.Dockerfile"),
  50. ),
  51. Run: func(cmd *cobra.Command, args []string) {
  52. err := checkLoginAndRun(args, deployFull)
  53. if err != nil {
  54. os.Exit(1)
  55. }
  56. },
  57. }
  58. var deployGetEnvCmd = &cobra.Command{
  59. Use: "get-env",
  60. Short: "Gets environment variables for a deployment for a specified application given by the --app flag.",
  61. Long: fmt.Sprintf(`
  62. %s
  63. Gets environment variables for a deployment for a specified application given by the --app
  64. flag. By default, env variables are printed via stdout for use in downstream commands:
  65. %s
  66. Output can also be written to a file via the --file flag, which should specify the
  67. destination path for a .env file. For example:
  68. %s
  69. `,
  70. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter deploy get-env\":"),
  71. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy get-env --app example-app | xargs"),
  72. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy get-env --app example-app --file .env"),
  73. ),
  74. Run: func(cmd *cobra.Command, args []string) {
  75. err := checkLoginAndRun(args, deployGetEnv)
  76. if err != nil {
  77. os.Exit(1)
  78. }
  79. },
  80. }
  81. var deployBuildCmd = &cobra.Command{
  82. Use: "build",
  83. Short: "Builds a new version of the application specified by the --app flag.",
  84. Long: fmt.Sprintf(`
  85. %s
  86. Builds a new version of the application specified by the --app flag. Depending on the
  87. configured settings, this command may work automatically or will require a specified
  88. --method flag.
  89. If you have configured the Dockerfile path and/or a build context for this application,
  90. this command will by default use those settings, so you just need to specify the --app
  91. flag:
  92. %s
  93. If you have not linked the build-time requirements for this application, the command will
  94. use a local build. By default, the cloud-native buildpacks builder will automatically be run
  95. from the current directory. If you would like to change the build method, you can do so by
  96. using the --method flag, for example:
  97. %s
  98. When using "--method docker", you can specify the path to the Dockerfile using the
  99. --dockerfile flag. This will also override the Dockerfile path that you may have linked
  100. for the application:
  101. %s
  102. `,
  103. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter deploy build\":"),
  104. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy build --app example-app"),
  105. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy build --app example-app --method docker"),
  106. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy build --app example-app --method docker --dockerfile ./prod.Dockerfile"),
  107. ),
  108. Run: func(cmd *cobra.Command, args []string) {
  109. err := checkLoginAndRun(args, deployBuild)
  110. if err != nil {
  111. os.Exit(1)
  112. }
  113. },
  114. }
  115. var deployPushCmd = &cobra.Command{
  116. Use: "push",
  117. Short: "Pushes a new image for an application specified by the --app flag.",
  118. Long: fmt.Sprintf(`
  119. %s
  120. Pushes a new image for an application specified by the --app flag. This command uses
  121. the image repository saved in the application config by default. For example, if an
  122. application "nginx" was created from the image repo "gcr.io/snowflake-123456/nginx",
  123. the following command would push the image "gcr.io/snowflake-123456/nginx:new-tag":
  124. %s
  125. This command will not use your pre-saved authentication set up via "docker login," so if you
  126. are using an image registry that was created outside of Porter, make sure that you have
  127. linked it via "porter connect".
  128. `,
  129. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter deploy push\":"),
  130. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy push --app nginx --tag new-tag"),
  131. ),
  132. Run: func(cmd *cobra.Command, args []string) {
  133. err := checkLoginAndRun(args, deployPush)
  134. if err != nil {
  135. os.Exit(1)
  136. }
  137. },
  138. }
  139. var deployCallWebhookCmd = &cobra.Command{
  140. Use: "update-config",
  141. Short: "Updates the configuration for an application specified by the --app flag.",
  142. Long: fmt.Sprintf(`
  143. %s
  144. Updates the configuration for an application specified by the --app flag, using the configuration
  145. given by the --values flag. This will trigger a new deployment for the application with
  146. new configuration set. Note that this will merge your existing configuration with configuration
  147. specified in the --values file. For example:
  148. %s
  149. You can update the configuration with only a new tag with the --tag flag, which will only update
  150. the image that the application uses if no --values file is specified:
  151. %s
  152. `,
  153. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter deploy update-config\":"),
  154. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy call-webhook --app example-app --values my-values.yaml"),
  155. color.New(color.FgGreen, color.Bold).Sprintf("porter deploy call-webhook --app example-app --tag custom-tag"),
  156. ),
  157. Run: func(cmd *cobra.Command, args []string) {
  158. err := checkLoginAndRun(args, deployUpgrade)
  159. if err != nil {
  160. os.Exit(1)
  161. }
  162. },
  163. }
  164. var app string
  165. var getEnvFileDest string
  166. var local bool
  167. var localPath string
  168. var tag string
  169. var dockerfile string
  170. var method string
  171. func init() {
  172. rootCmd.AddCommand(deployCmd)
  173. deployCmd.PersistentFlags().StringVar(
  174. &app,
  175. "app",
  176. "",
  177. "Application in the Porter dashboard",
  178. )
  179. deployCmd.MarkPersistentFlagRequired("app")
  180. deployCmd.PersistentFlags().StringVar(
  181. &namespace,
  182. "namespace",
  183. "default",
  184. "Namespace of the application",
  185. )
  186. deployCmd.PersistentFlags().BoolVar(
  187. &local,
  188. "local",
  189. true,
  190. "Whether local context should be used for build",
  191. )
  192. deployCmd.PersistentFlags().StringVarP(
  193. &localPath,
  194. "path",
  195. "p",
  196. ".",
  197. "If local build, the path to the build directory",
  198. )
  199. deployCmd.PersistentFlags().StringVarP(
  200. &tag,
  201. "tag",
  202. "t",
  203. "",
  204. "the specified tag to use, if not \"latest\"",
  205. )
  206. deployCmd.PersistentFlags().StringVarP(
  207. &values,
  208. "values",
  209. "v",
  210. "",
  211. "Filepath to a values.yaml file",
  212. )
  213. deployCmd.PersistentFlags().StringVar(
  214. &dockerfile,
  215. "dockerfile",
  216. "",
  217. "the path to the dockerfile",
  218. )
  219. deployCmd.PersistentFlags().StringVar(
  220. &method,
  221. "method",
  222. "",
  223. "the build method to use (\"docker\" or \"pack\")",
  224. )
  225. deployCmd.AddCommand(deployGetEnvCmd)
  226. deployGetEnvCmd.PersistentFlags().StringVar(
  227. &getEnvFileDest,
  228. "file",
  229. "",
  230. "file destination for .env files",
  231. )
  232. deployCmd.AddCommand(deployBuildCmd)
  233. deployCmd.AddCommand(deployPushCmd)
  234. deployCmd.AddCommand(deployCallWebhookCmd)
  235. }
  236. func deployFull(resp *api.AuthCheckResponse, client *api.Client, args []string) error {
  237. color.New(color.FgGreen).Println("Deploying app:", app)
  238. deployAgent, err := deployGetAgent(client)
  239. if err != nil {
  240. return err
  241. }
  242. err = deployBuildWithAgent(deployAgent)
  243. if err != nil {
  244. return err
  245. }
  246. err = deployPushWithAgent(deployAgent)
  247. if err != nil {
  248. return err
  249. }
  250. err = deployUpgradeWithAgent(deployAgent)
  251. if err != nil {
  252. return err
  253. }
  254. return nil
  255. }
  256. func deployGetEnv(resp *api.AuthCheckResponse, client *api.Client, args []string) error {
  257. deployAgent, err := deployGetAgent(client)
  258. if err != nil {
  259. return err
  260. }
  261. buildEnv, err := deployAgent.GetBuildEnv()
  262. if err != nil {
  263. return err
  264. }
  265. // set the environment variables in the process
  266. err = deployAgent.SetBuildEnv(buildEnv)
  267. if err != nil {
  268. return err
  269. }
  270. // write the environment variables to either a file or stdout (stdout by default)
  271. return deployAgent.WriteBuildEnv(getEnvFileDest)
  272. }
  273. func deployBuild(resp *api.AuthCheckResponse, client *api.Client, args []string) error {
  274. deployAgent, err := deployGetAgent(client)
  275. if err != nil {
  276. return err
  277. }
  278. return deployBuildWithAgent(deployAgent)
  279. }
  280. func deployPush(resp *api.AuthCheckResponse, client *api.Client, args []string) error {
  281. deployAgent, err := deployGetAgent(client)
  282. if err != nil {
  283. return err
  284. }
  285. return deployPushWithAgent(deployAgent)
  286. }
  287. func deployUpgrade(resp *api.AuthCheckResponse, client *api.Client, args []string) error {
  288. deployAgent, err := deployGetAgent(client)
  289. if err != nil {
  290. return err
  291. }
  292. return deployUpgradeWithAgent(deployAgent)
  293. }
  294. // HELPER METHODS
  295. func deployGetAgent(client *api.Client) (*deploy.DeployAgent, error) {
  296. var buildMethod deploy.DeployBuildType
  297. if method != "" {
  298. buildMethod = deploy.DeployBuildType(method)
  299. }
  300. // initialize the deploy agent
  301. return deploy.NewDeployAgent(client, app, &deploy.DeployOpts{
  302. SharedOpts: &deploy.SharedOpts{
  303. ProjectID: config.Project,
  304. ClusterID: config.Cluster,
  305. Namespace: namespace,
  306. LocalPath: localPath,
  307. LocalDockerfile: dockerfile,
  308. OverrideTag: tag,
  309. Method: buildMethod,
  310. },
  311. Local: local,
  312. })
  313. }
  314. func deployBuildWithAgent(deployAgent *deploy.DeployAgent) error {
  315. // build the deployment
  316. color.New(color.FgGreen).Println("Building docker image for", app)
  317. buildEnv, err := deployAgent.GetBuildEnv()
  318. if err != nil {
  319. return err
  320. }
  321. // set the environment variables in the process
  322. err = deployAgent.SetBuildEnv(buildEnv)
  323. if err != nil {
  324. return err
  325. }
  326. return deployAgent.Build()
  327. }
  328. func deployPushWithAgent(deployAgent *deploy.DeployAgent) error {
  329. // push the deployment
  330. color.New(color.FgGreen).Println("Pushing new image for", app)
  331. return deployAgent.Push()
  332. }
  333. func deployUpgradeWithAgent(deployAgent *deploy.DeployAgent) error {
  334. // push the deployment
  335. color.New(color.FgGreen).Println("Calling webhook for", app)
  336. // read the values if necessary
  337. valuesObj, err := readValuesFile()
  338. if err != nil {
  339. return err
  340. }
  341. err = deployAgent.UpdateImageAndValues(valuesObj)
  342. if err != nil {
  343. return err
  344. }
  345. color.New(color.FgGreen).Println("Successfully re-deployed", app)
  346. return nil
  347. }