deploy.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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/api/types"
  8. "github.com/porter-dev/porter/cli/cmd/deploy"
  9. "github.com/spf13/cobra"
  10. )
  11. // updateCmd represents the "porter update" base command when called
  12. // without any subcommands
  13. var updateCmd = &cobra.Command{
  14. Use: "update",
  15. Short: "Builds and updates a specified application given by the --app flag.",
  16. Long: fmt.Sprintf(`
  17. %s
  18. Builds and updates a specified application given by the --app flag. For example:
  19. %s
  20. This command will automatically build from a local path. The path can be configured via the
  21. --path flag. You can also overwrite the tag using the --tag flag. For example, to build from the
  22. local directory ~/path-to-dir with the tag "testing":
  23. %s
  24. If the application has a remote Git repository source configured, you can specify that the remote
  25. Git repository should be used to build the new image by specifying "--source github". Porter will use
  26. the latest commit from the remote repo and branch to update an application, and will use the latest
  27. commit as the image tag.
  28. %s
  29. To add new configuration or update existing configuration, you can pass a values.yaml file in via the
  30. --values flag. For example;
  31. %s
  32. If your application is set up to use a Dockerfile by default, you can use a buildpack via the flag
  33. "--method pack". Conversely, if your application is set up to use a buildpack by default, you can
  34. use a Dockerfile by passing the flag "--method docker". You can specify the relative path to a Dockerfile
  35. in your remote Git repository. For example, if a Dockerfile is found at ./docker/prod.Dockerfile, you can
  36. specify it as follows:
  37. %s
  38. `,
  39. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update\":"),
  40. color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app"),
  41. color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app --path ~/path-to-dir --tag testing"),
  42. color.New(color.FgGreen, color.Bold).Sprintf("porter update --app remote-git-app --source github"),
  43. color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app --values my-values.yaml"),
  44. color.New(color.FgGreen, color.Bold).Sprintf("porter update --app example-app --method docker --dockerfile ./docker/prod.Dockerfile"),
  45. ),
  46. Run: func(cmd *cobra.Command, args []string) {
  47. err := checkLoginAndRun(args, updateFull)
  48. if err != nil {
  49. os.Exit(1)
  50. }
  51. },
  52. }
  53. var updateGetEnvCmd = &cobra.Command{
  54. Use: "get-env",
  55. Short: "Gets environment variables for a deployment for a specified application given by the --app flag.",
  56. Long: fmt.Sprintf(`
  57. %s
  58. Gets environment variables for a deployment for a specified application given by the --app
  59. flag. By default, env variables are printed via stdout for use in downstream commands:
  60. %s
  61. Output can also be written to a file via the --file flag, which should specify the
  62. destination path for a .env file. For example:
  63. %s
  64. `,
  65. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update get-env\":"),
  66. color.New(color.FgGreen, color.Bold).Sprintf("porter update get-env --app example-app | xargs"),
  67. color.New(color.FgGreen, color.Bold).Sprintf("porter update get-env --app example-app --file .env"),
  68. ),
  69. Run: func(cmd *cobra.Command, args []string) {
  70. err := checkLoginAndRun(args, updateGetEnv)
  71. if err != nil {
  72. os.Exit(1)
  73. }
  74. },
  75. }
  76. var updateBuildCmd = &cobra.Command{
  77. Use: "build",
  78. Short: "Builds a new version of the application specified by the --app flag.",
  79. Long: fmt.Sprintf(`
  80. %s
  81. Builds a new version of the application specified by the --app flag. Depending on the
  82. configured settings, this command may work automatically or will require a specified
  83. --method flag.
  84. If you have configured the Dockerfile path and/or a build context for this application,
  85. this command will by default use those settings, so you just need to specify the --app
  86. flag:
  87. %s
  88. If you have not linked the build-time requirements for this application, the command will
  89. use a local build. By default, the cloud-native buildpacks builder will automatically be run
  90. from the current directory. If you would like to change the build method, you can do so by
  91. using the --method flag, for example:
  92. %s
  93. When using "--method docker", you can specify the path to the Dockerfile using the
  94. --dockerfile flag. This will also override the Dockerfile path that you may have linked
  95. for the application:
  96. %s
  97. `,
  98. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update build\":"),
  99. color.New(color.FgGreen, color.Bold).Sprintf("porter update build --app example-app"),
  100. color.New(color.FgGreen, color.Bold).Sprintf("porter update build --app example-app --method docker"),
  101. color.New(color.FgGreen, color.Bold).Sprintf("porter update build --app example-app --method docker --dockerfile ./prod.Dockerfile"),
  102. ),
  103. Run: func(cmd *cobra.Command, args []string) {
  104. err := checkLoginAndRun(args, updateBuild)
  105. if err != nil {
  106. os.Exit(1)
  107. }
  108. },
  109. }
  110. var updatePushCmd = &cobra.Command{
  111. Use: "push",
  112. Short: "Pushes a new image for an application specified by the --app flag.",
  113. Long: fmt.Sprintf(`
  114. %s
  115. Pushes a new image for an application specified by the --app flag. This command uses
  116. the image repository saved in the application config by default. For example, if an
  117. application "nginx" was created from the image repo "gcr.io/snowflake-123456/nginx",
  118. the following command would push the image "gcr.io/snowflake-123456/nginx:new-tag":
  119. %s
  120. This command will not use your pre-saved authentication set up via "docker login," so if you
  121. are using an image registry that was created outside of Porter, make sure that you have
  122. linked it via "porter connect".
  123. `,
  124. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update push\":"),
  125. color.New(color.FgGreen, color.Bold).Sprintf("porter update push --app nginx --tag new-tag"),
  126. ),
  127. Run: func(cmd *cobra.Command, args []string) {
  128. err := checkLoginAndRun(args, updatePush)
  129. if err != nil {
  130. os.Exit(1)
  131. }
  132. },
  133. }
  134. var updateConfigCmd = &cobra.Command{
  135. Use: "config",
  136. Short: "Updates the configuration for an application specified by the --app flag.",
  137. Long: fmt.Sprintf(`
  138. %s
  139. Updates the configuration for an application specified by the --app flag, using the configuration
  140. given by the --values flag. This will trigger a new deployment for the application with
  141. new configuration set. Note that this will merge your existing configuration with configuration
  142. specified in the --values file. For example:
  143. %s
  144. You can update the configuration with only a new tag with the --tag flag, which will only update
  145. the image that the application uses if no --values file is specified:
  146. %s
  147. `,
  148. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter update config\":"),
  149. color.New(color.FgGreen, color.Bold).Sprintf("porter update config --app example-app --values my-values.yaml"),
  150. color.New(color.FgGreen, color.Bold).Sprintf("porter update config --app example-app --tag custom-tag"),
  151. ),
  152. Run: func(cmd *cobra.Command, args []string) {
  153. err := checkLoginAndRun(args, updateUpgrade)
  154. if err != nil {
  155. os.Exit(1)
  156. }
  157. },
  158. }
  159. var app string
  160. var getEnvFileDest string
  161. var localPath string
  162. var tag string
  163. var dockerfile string
  164. var method string
  165. var stream bool
  166. func init() {
  167. rootCmd.AddCommand(updateCmd)
  168. updateCmd.PersistentFlags().StringVar(
  169. &app,
  170. "app",
  171. "",
  172. "Application in the Porter dashboard",
  173. )
  174. updateCmd.MarkPersistentFlagRequired("app")
  175. updateCmd.PersistentFlags().StringVar(
  176. &namespace,
  177. "namespace",
  178. "default",
  179. "Namespace of the application",
  180. )
  181. updateCmd.PersistentFlags().StringVar(
  182. &source,
  183. "source",
  184. "local",
  185. "the type of source (\"local\" or \"github\")",
  186. )
  187. updateCmd.PersistentFlags().StringVarP(
  188. &localPath,
  189. "path",
  190. "p",
  191. "",
  192. "If local build, the path to the build directory. If remote build, the relative path from the repository root to the build directory.",
  193. )
  194. updateCmd.PersistentFlags().StringVarP(
  195. &tag,
  196. "tag",
  197. "t",
  198. "",
  199. "the specified tag to use, if not \"latest\"",
  200. )
  201. updateCmd.PersistentFlags().StringVarP(
  202. &values,
  203. "values",
  204. "v",
  205. "",
  206. "Filepath to a values.yaml file",
  207. )
  208. updateCmd.PersistentFlags().StringVar(
  209. &dockerfile,
  210. "dockerfile",
  211. "",
  212. "the path to the dockerfile",
  213. )
  214. updateCmd.PersistentFlags().StringVar(
  215. &method,
  216. "method",
  217. "",
  218. "the build method to use (\"docker\" or \"pack\")",
  219. )
  220. updateCmd.PersistentFlags().BoolVar(
  221. &stream,
  222. "stream",
  223. false,
  224. "stream update logs to porter dashboard",
  225. )
  226. updateCmd.AddCommand(updateGetEnvCmd)
  227. updateGetEnvCmd.PersistentFlags().StringVar(
  228. &getEnvFileDest,
  229. "file",
  230. "",
  231. "file destination for .env files",
  232. )
  233. updateCmd.AddCommand(updateBuildCmd)
  234. updateCmd.AddCommand(updatePushCmd)
  235. updateCmd.AddCommand(updateConfigCmd)
  236. }
  237. func updateFull(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
  238. color.New(color.FgGreen).Println("Deploying app:", app)
  239. updateAgent, err := updateGetAgent(client)
  240. if err != nil {
  241. return err
  242. }
  243. err = updateBuildWithAgent(updateAgent)
  244. if err != nil {
  245. return err
  246. }
  247. err = updatePushWithAgent(updateAgent)
  248. if err != nil {
  249. return err
  250. }
  251. err = updateUpgradeWithAgent(updateAgent)
  252. if err != nil {
  253. return err
  254. }
  255. return nil
  256. }
  257. func updateGetEnv(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
  258. updateAgent, err := updateGetAgent(client)
  259. if err != nil {
  260. return err
  261. }
  262. buildEnv, err := updateAgent.GetBuildEnv()
  263. if err != nil {
  264. return err
  265. }
  266. // set the environment variables in the process
  267. err = updateAgent.SetBuildEnv(buildEnv)
  268. if err != nil {
  269. return err
  270. }
  271. // write the environment variables to either a file or stdout (stdout by default)
  272. return updateAgent.WriteBuildEnv(getEnvFileDest)
  273. }
  274. func updateBuild(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
  275. updateAgent, err := updateGetAgent(client)
  276. if err != nil {
  277. return err
  278. }
  279. return updateBuildWithAgent(updateAgent)
  280. }
  281. func updatePush(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
  282. updateAgent, err := updateGetAgent(client)
  283. if err != nil {
  284. return err
  285. }
  286. return updatePushWithAgent(updateAgent)
  287. }
  288. func updateUpgrade(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
  289. updateAgent, err := updateGetAgent(client)
  290. if err != nil {
  291. return err
  292. }
  293. return updateUpgradeWithAgent(updateAgent)
  294. }
  295. // HELPER METHODS
  296. func updateGetAgent(client *api.Client) (*deploy.DeployAgent, error) {
  297. var buildMethod deploy.DeployBuildType
  298. if method != "" {
  299. buildMethod = deploy.DeployBuildType(method)
  300. }
  301. // initialize the update agent
  302. return deploy.NewDeployAgent(client, app, &deploy.DeployOpts{
  303. SharedOpts: &deploy.SharedOpts{
  304. ProjectID: config.Project,
  305. ClusterID: config.Cluster,
  306. Namespace: namespace,
  307. LocalPath: localPath,
  308. LocalDockerfile: dockerfile,
  309. OverrideTag: tag,
  310. Method: buildMethod,
  311. },
  312. Local: source != "github",
  313. })
  314. }
  315. func updateBuildWithAgent(updateAgent *deploy.DeployAgent) error {
  316. // build the deployment
  317. color.New(color.FgGreen).Println("Building docker image for", app)
  318. if stream {
  319. updateAgent.StreamEvent(types.SubEvent{
  320. EventID: "build",
  321. Name: "Build",
  322. Index: 100,
  323. Status: types.EventStatusInProgress,
  324. Info: "",
  325. })
  326. }
  327. buildEnv, err := updateAgent.GetBuildEnv()
  328. if err != nil {
  329. if stream {
  330. // another concern: is it safe to ignore the error here?
  331. updateAgent.StreamEvent(types.SubEvent{
  332. EventID: "build",
  333. Name: "Build",
  334. Index: 110,
  335. Status: types.EventStatusFailed,
  336. Info: err.Error(),
  337. })
  338. }
  339. return err
  340. }
  341. // set the environment variables in the process
  342. err = updateAgent.SetBuildEnv(buildEnv)
  343. if err != nil {
  344. if stream {
  345. updateAgent.StreamEvent(types.SubEvent{
  346. EventID: "build",
  347. Name: "Build",
  348. Index: 120,
  349. Status: types.EventStatusFailed,
  350. Info: err.Error(),
  351. })
  352. }
  353. return err
  354. }
  355. if err := updateAgent.Build(); err != nil {
  356. if stream {
  357. updateAgent.StreamEvent(types.SubEvent{
  358. EventID: "build",
  359. Name: "Build",
  360. Index: 130,
  361. Status: types.EventStatusFailed,
  362. Info: err.Error(),
  363. })
  364. }
  365. return err
  366. }
  367. if stream {
  368. updateAgent.StreamEvent(types.SubEvent{
  369. EventID: "build",
  370. Name: "Build",
  371. Index: 140,
  372. Status: types.EventStatusSuccess,
  373. Info: "",
  374. })
  375. }
  376. return nil
  377. }
  378. func updatePushWithAgent(updateAgent *deploy.DeployAgent) error {
  379. // push the deployment
  380. color.New(color.FgGreen).Println("Pushing new image for", app)
  381. if stream {
  382. updateAgent.StreamEvent(types.SubEvent{
  383. EventID: "push",
  384. Name: "Push",
  385. Index: 200,
  386. Status: types.EventStatusInProgress,
  387. Info: "",
  388. })
  389. }
  390. if err := updateAgent.Push(); err != nil {
  391. if stream {
  392. updateAgent.StreamEvent(types.SubEvent{
  393. EventID: "push",
  394. Name: "Push",
  395. Index: 210,
  396. Status: types.EventStatusFailed,
  397. Info: err.Error(),
  398. })
  399. }
  400. return err
  401. }
  402. if stream {
  403. updateAgent.StreamEvent(types.SubEvent{
  404. EventID: "push",
  405. Name: "Push",
  406. Index: 220,
  407. Status: types.EventStatusSuccess,
  408. Info: "",
  409. })
  410. }
  411. return nil
  412. }
  413. func updateUpgradeWithAgent(updateAgent *deploy.DeployAgent) error {
  414. // push the deployment
  415. color.New(color.FgGreen).Println("Upgrading configuration for", app)
  416. if stream {
  417. updateAgent.StreamEvent(types.SubEvent{
  418. EventID: "upgrade",
  419. Name: "Upgrade",
  420. Index: 300,
  421. Status: types.EventStatusInProgress,
  422. Info: "",
  423. })
  424. }
  425. // read the values if necessary
  426. valuesObj, err := readValuesFile()
  427. if err != nil {
  428. if stream {
  429. updateAgent.StreamEvent(types.SubEvent{
  430. EventID: "upgrade",
  431. Name: "Upgrade",
  432. Index: 310,
  433. Status: types.EventStatusFailed,
  434. Info: err.Error(),
  435. })
  436. }
  437. return err
  438. }
  439. err = updateAgent.UpdateImageAndValues(valuesObj)
  440. if err != nil {
  441. if stream {
  442. updateAgent.StreamEvent(types.SubEvent{
  443. EventID: "upgrade",
  444. Name: "Upgrade",
  445. Index: 320,
  446. Status: types.EventStatusFailed,
  447. Info: err.Error(),
  448. })
  449. }
  450. return err
  451. }
  452. if stream {
  453. updateAgent.StreamEvent(types.SubEvent{
  454. EventID: "upgrade",
  455. Name: "Upgrade",
  456. Index: 330,
  457. Status: types.EventStatusSuccess,
  458. Info: "",
  459. })
  460. }
  461. color.New(color.FgGreen).Println("Successfully updated", app)
  462. return nil
  463. }