GitOps for k8s
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

list_images_cmd.go 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "sort"
  6. "time"
  7. "github.com/spf13/cobra"
  8. "github.com/fluxcd/flux/api/v10"
  9. "github.com/fluxcd/flux/api/v6"
  10. "github.com/fluxcd/flux/registry"
  11. "github.com/fluxcd/flux/resource"
  12. "github.com/fluxcd/flux/update"
  13. )
  14. type imageListOpts struct {
  15. *rootOpts
  16. namespace string
  17. workload string
  18. limit int
  19. // Deprecated
  20. controller string
  21. }
  22. func newImageList(parent *rootOpts) *imageListOpts {
  23. return &imageListOpts{rootOpts: parent}
  24. }
  25. func (opts *imageListOpts) Command() *cobra.Command {
  26. cmd := &cobra.Command{
  27. Use: "list-images",
  28. Short: "Show deployed and available images.",
  29. Example: makeExample("fluxctl list-images --namespace default --workload=deployment/foo"),
  30. RunE: opts.RunE,
  31. }
  32. cmd.Flags().StringVarP(&opts.namespace, "namespace", "n", getKubeConfigContextNamespace(""), "Namespace")
  33. cmd.Flags().StringVarP(&opts.workload, "workload", "w", "", "Show images for this workload")
  34. cmd.Flags().IntVarP(&opts.limit, "limit", "l", 10, "Number of images to show (0 for all)")
  35. // Deprecated
  36. cmd.Flags().StringVarP(&opts.controller, "controller", "c", "", "Show images for this controller")
  37. cmd.Flags().MarkDeprecated("controller", "changed to --workload, use that instead")
  38. return cmd
  39. }
  40. func (opts *imageListOpts) RunE(cmd *cobra.Command, args []string) error {
  41. if len(args) != 0 {
  42. return errorWantedNoArgs
  43. }
  44. imageOpts := v10.ListImagesOptions{
  45. Spec: update.ResourceSpecAll,
  46. Namespace: opts.namespace,
  47. }
  48. // Backwards compatibility with --controller until we remove it
  49. switch {
  50. case opts.workload != "" && opts.controller != "":
  51. return newUsageError("can't specify both the controller and workload")
  52. case opts.controller != "":
  53. opts.workload = opts.controller
  54. }
  55. if len(opts.workload) > 0 {
  56. id, err := resource.ParseIDOptionalNamespace(opts.namespace, opts.workload)
  57. if err != nil {
  58. return err
  59. }
  60. imageOpts.Spec = update.MakeResourceSpec(id)
  61. imageOpts.Namespace = ""
  62. }
  63. ctx := context.Background()
  64. workloads, err := opts.API.ListImagesWithOptions(ctx, imageOpts)
  65. if err != nil {
  66. return err
  67. }
  68. sort.Sort(imageStatusByName(workloads))
  69. out := newTabwriter()
  70. fmt.Fprintln(out, "WORKLOAD\tCONTAINER\tIMAGE\tCREATED")
  71. for _, workload := range workloads {
  72. if len(workload.Containers) == 0 {
  73. fmt.Fprintf(out, "%s\t\t\t\n", workload.ID)
  74. continue
  75. }
  76. workloadName := workload.ID.String()
  77. for _, container := range workload.Containers {
  78. var lineCount int
  79. containerName := container.Name
  80. reg, repo, currentTag := container.Current.ID.Components()
  81. if reg != "" {
  82. reg += "/"
  83. }
  84. if len(container.Available) == 0 {
  85. availableErr := container.AvailableError
  86. if availableErr == "" {
  87. availableErr = registry.ErrNoImageData.Error()
  88. }
  89. fmt.Fprintf(out, "%s\t%s\t%s%s\t%s\n", workloadName, containerName, reg, repo, availableErr)
  90. } else {
  91. fmt.Fprintf(out, "%s\t%s\t%s%s\t\n", workloadName, containerName, reg, repo)
  92. }
  93. foundRunning := false
  94. for _, available := range container.Available {
  95. running := "| "
  96. _, _, tag := available.ID.Components()
  97. if currentTag == tag {
  98. running = "'->"
  99. foundRunning = true
  100. } else if foundRunning {
  101. running = " "
  102. }
  103. lineCount++
  104. var printEllipsis, printLine bool
  105. if opts.limit <= 0 || lineCount <= opts.limit {
  106. printEllipsis, printLine = false, true
  107. } else if container.Current.ID == available.ID {
  108. printEllipsis, printLine = lineCount > (opts.limit+1), true
  109. }
  110. if printEllipsis {
  111. fmt.Fprintf(out, "\t\t%s (%d image(s) omitted)\t\n", ":", lineCount-opts.limit-1)
  112. }
  113. if printLine {
  114. createdAt := ""
  115. if !available.CreatedTS().IsZero() {
  116. createdAt = available.CreatedTS().Format(time.RFC822)
  117. }
  118. fmt.Fprintf(out, "\t\t%s %s\t%s\n", running, tag, createdAt)
  119. }
  120. }
  121. if !foundRunning {
  122. running := "'->"
  123. if currentTag == "" {
  124. currentTag = "(untagged)"
  125. }
  126. fmt.Fprintf(out, "\t\t%s %s\t%s\n", running, currentTag, "?")
  127. }
  128. workloadName = ""
  129. }
  130. }
  131. out.Flush()
  132. return nil
  133. }
  134. type imageStatusByName []v6.ImageStatus
  135. func (s imageStatusByName) Len() int {
  136. return len(s)
  137. }
  138. func (s imageStatusByName) Less(a, b int) bool {
  139. return s[a].ID.String() < s[b].ID.String()
  140. }
  141. func (s imageStatusByName) Swap(a, b int) {
  142. s[a], s[b] = s[b], s[a]
  143. }