job.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. package cmd
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "strconv"
  7. "time"
  8. "github.com/fatih/color"
  9. api "github.com/porter-dev/porter/api/client"
  10. "github.com/porter-dev/porter/api/types"
  11. "github.com/spf13/cobra"
  12. v1 "k8s.io/api/batch/v1"
  13. )
  14. var jobCmd = &cobra.Command{
  15. Use: "job",
  16. }
  17. var batchImageUpdateCmd = &cobra.Command{
  18. Use: "update-images",
  19. Short: "Updates the image tag of all jobs in a namespace which use a specific image.",
  20. Long: fmt.Sprintf(`
  21. %s
  22. Updates the image tag of all jobs in a namespace which use a specific image. Note that for all
  23. jobs with version <= v0.4.0, this will trigger a new run of a manual job. However, for versions
  24. >= v0.5.0, this will not create a new run of the job.
  25. Example commands:
  26. %s
  27. This command is namespace-scoped and uses the default namespace. To specify a different namespace,
  28. use the --namespace flag:
  29. %s
  30. `,
  31. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter job update-images\":"),
  32. color.New(color.FgGreen, color.Bold).Sprintf("porter job update-images --image-repo-uri my-image.registry.io --tag newtag"),
  33. color.New(color.FgGreen, color.Bold).Sprintf("porter job update-images --namespace custom-namespace --image-repo-uri my-image.registry.io --tag newtag"),
  34. ),
  35. Run: func(cmd *cobra.Command, args []string) {
  36. err := checkLoginAndRun(args, batchImageUpdate)
  37. if err != nil {
  38. os.Exit(1)
  39. }
  40. },
  41. }
  42. var waitCmd = &cobra.Command{
  43. Use: "wait",
  44. Short: "Waits for a job to complete.",
  45. Long: fmt.Sprintf(`
  46. %s
  47. Waits for a job with a given name and namespace to complete a run. If the job completes successfully,
  48. this command exits with exit code 0. Otherwise, this command exits with exit code 1.
  49. Example commands:
  50. %s
  51. This command is namespace-scoped and uses the default namespace. To specify a different namespace,
  52. use the --namespace flag:
  53. %s
  54. `,
  55. color.New(color.FgBlue, color.Bold).Sprintf("Help for \"porter job wait\":"),
  56. color.New(color.FgGreen, color.Bold).Sprintf("porter job wait --name job-example"),
  57. color.New(color.FgGreen, color.Bold).Sprintf("porter job wait --name job-example --namespace custom-namespace"),
  58. ),
  59. Run: func(cmd *cobra.Command, args []string) {
  60. err := checkLoginAndRun(args, waitForJob)
  61. if err != nil {
  62. os.Exit(1)
  63. }
  64. },
  65. }
  66. var imageRepoURI string
  67. func init() {
  68. rootCmd.AddCommand(jobCmd)
  69. jobCmd.AddCommand(batchImageUpdateCmd)
  70. jobCmd.AddCommand(waitCmd)
  71. batchImageUpdateCmd.PersistentFlags().StringVar(
  72. &tag,
  73. "tag",
  74. "",
  75. "The new image tag to use.",
  76. )
  77. batchImageUpdateCmd.PersistentFlags().StringVar(
  78. &namespace,
  79. "namespace",
  80. "",
  81. "The namespace of the jobs.",
  82. )
  83. batchImageUpdateCmd.PersistentFlags().StringVarP(
  84. &imageRepoURI,
  85. "image-repo-uri",
  86. "i",
  87. "",
  88. "Image repo uri",
  89. )
  90. batchImageUpdateCmd.MarkPersistentFlagRequired("image-repo-uri")
  91. batchImageUpdateCmd.MarkPersistentFlagRequired("tag")
  92. waitCmd.PersistentFlags().StringVar(
  93. &namespace,
  94. "namespace",
  95. "",
  96. "The namespace of the jobs.",
  97. )
  98. waitCmd.PersistentFlags().StringVar(
  99. &name,
  100. "name",
  101. "",
  102. "The name of the jobs.",
  103. )
  104. waitCmd.MarkPersistentFlagRequired("name")
  105. }
  106. func batchImageUpdate(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
  107. color.New(color.FgGreen).Println("Updating all jobs which use the image:", imageRepoURI)
  108. return client.UpdateBatchImage(
  109. context.TODO(),
  110. config.Project,
  111. config.Cluster,
  112. namespace,
  113. &types.UpdateImageBatchRequest{
  114. ImageRepoURI: imageRepoURI,
  115. Tag: tag,
  116. },
  117. )
  118. }
  119. // waits for a job with a given name/namespace
  120. func waitForJob(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
  121. // get the job release
  122. jobRelease, err := client.GetRelease(context.Background(), config.Project, config.Cluster, namespace, name)
  123. if err != nil {
  124. return err
  125. }
  126. // make sure the job chart has a manual job running
  127. pausedVal, ok := jobRelease.Release.Config["paused"]
  128. pausedErr := fmt.Errorf("this job template is not currently running a manual job")
  129. if !ok {
  130. return pausedErr
  131. }
  132. if pausedValBool, ok := pausedVal.(bool); ok && pausedValBool {
  133. return pausedErr
  134. }
  135. // if no job exists with the given revision, wait up to 5 minutes
  136. timeWait := time.Now().Add(5 * time.Minute)
  137. for timeNow := time.Now(); timeNow.Before(timeWait); {
  138. // get the jobs for that job chart
  139. jobs, err := client.GetJobs(context.Background(), config.Project, config.Cluster, namespace, name)
  140. if err != nil {
  141. return err
  142. }
  143. job := getJobMatchingRevision(uint(jobRelease.Release.Version), jobs)
  144. if job == nil {
  145. time.Sleep(10 * time.Second)
  146. continue
  147. }
  148. // once job is running, wait for status to be completed, or failed
  149. // if failed, exit with non-zero exit code
  150. if job.Status.Failed > 0 {
  151. return fmt.Errorf("job failed")
  152. }
  153. if job.Status.Succeeded > 0 {
  154. return nil
  155. }
  156. // otherwise, return no error
  157. time.Sleep(10 * time.Second)
  158. continue
  159. }
  160. return fmt.Errorf("timed out waiting for job")
  161. }
  162. func getJobMatchingRevision(revision uint, jobs []v1.Job) *v1.Job {
  163. for _, job := range jobs {
  164. revisionLabel, revisionLabelExists := job.Labels["helm.sh/revision"]
  165. if !revisionLabelExists {
  166. continue
  167. }
  168. jobRevision, err := strconv.ParseUint(revisionLabel, 10, 64)
  169. if err != nil {
  170. continue
  171. }
  172. if uint(jobRevision) == revision {
  173. return &job
  174. }
  175. }
  176. return nil
  177. }