deploy.go 11 KB

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