github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/compose_images.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "text/tabwriter" 24 25 "github.com/containerd/containerd" 26 "github.com/containerd/containerd/pkg/progress" 27 "github.com/containerd/containerd/snapshots" 28 "github.com/containerd/nerdctl/pkg/clientutil" 29 "github.com/containerd/nerdctl/pkg/cmd/compose" 30 "github.com/containerd/nerdctl/pkg/formatter" 31 "github.com/containerd/nerdctl/pkg/imgutil" 32 "github.com/containerd/nerdctl/pkg/labels" 33 "github.com/containerd/nerdctl/pkg/strutil" 34 "github.com/spf13/cobra" 35 "golang.org/x/sync/errgroup" 36 ) 37 38 func newComposeImagesCommand() *cobra.Command { 39 var composeImagesCommand = &cobra.Command{ 40 Use: "images [flags] [SERVICE...]", 41 Short: "List images used by created containers in services", 42 RunE: composeImagesAction, 43 SilenceUsage: true, 44 SilenceErrors: true, 45 } 46 composeImagesCommand.Flags().String("format", "", "Format the output. Supported values: [json]") 47 composeImagesCommand.Flags().BoolP("quiet", "q", false, "Only show numeric image IDs") 48 return composeImagesCommand 49 } 50 51 func composeImagesAction(cmd *cobra.Command, args []string) error { 52 globalOptions, err := processRootCmdFlags(cmd) 53 if err != nil { 54 return err 55 } 56 57 quiet, err := cmd.Flags().GetBool("quiet") 58 if err != nil { 59 return err 60 } 61 format, err := cmd.Flags().GetString("format") 62 if err != nil { 63 return err 64 } 65 if format != "json" && format != "" { 66 return fmt.Errorf("unsupported format %s, supported formats are: [json]", format) 67 } 68 69 client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address) 70 if err != nil { 71 return err 72 } 73 defer cancel() 74 75 options, err := getComposeOptions(cmd, globalOptions.DebugFull, globalOptions.Experimental) 76 if err != nil { 77 return err 78 } 79 c, err := compose.New(client, globalOptions, options, cmd.OutOrStdout(), cmd.ErrOrStderr()) 80 if err != nil { 81 return err 82 } 83 84 serviceNames, err := c.ServiceNames(args...) 85 if err != nil { 86 return err 87 } 88 89 containers, err := c.Containers(ctx, serviceNames...) 90 if err != nil { 91 return err 92 } 93 94 if quiet { 95 return printComposeImageIDs(ctx, containers) 96 } 97 98 sn := client.SnapshotService(globalOptions.Snapshotter) 99 100 return printComposeImages(ctx, cmd, containers, sn, format) 101 } 102 103 func printComposeImageIDs(ctx context.Context, containers []containerd.Container) error { 104 ids := []string{} 105 for _, c := range containers { 106 image, err := c.Image(ctx) 107 if err != nil { 108 return err 109 } 110 metaImage := image.Metadata() 111 id := metaImage.Target.Digest.String() 112 if !strutil.InStringSlice(ids, id) { 113 ids = append(ids, id) 114 } 115 } 116 117 for _, id := range ids { 118 // always truncate image ids. 119 fmt.Println(strings.Split(id, ":")[1][:12]) 120 } 121 return nil 122 } 123 124 func printComposeImages(ctx context.Context, cmd *cobra.Command, containers []containerd.Container, sn snapshots.Snapshotter, format string) error { 125 type composeImagePrintable struct { 126 ContainerName string 127 Repository string 128 Tag string 129 ImageID string 130 Size string 131 } 132 133 imagePrintables := make([]composeImagePrintable, len(containers)) 134 eg, ctx := errgroup.WithContext(ctx) 135 for i, c := range containers { 136 i, c := i, c 137 eg.Go(func() error { 138 info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata) 139 if err != nil { 140 return err 141 } 142 containerName := info.Labels[labels.Name] 143 144 image, err := c.Image(ctx) 145 if err != nil { 146 return err 147 } 148 149 size, err := imgutil.UnpackedImageSize(ctx, sn, image) 150 if err != nil { 151 return err 152 } 153 154 metaImage := image.Metadata() 155 repository, tag := imgutil.ParseRepoTag(metaImage.Name) 156 imageID := metaImage.Target.Digest.String() 157 if repository == "" { 158 repository = "<none>" 159 } 160 if tag == "" { 161 tag = "<none>" 162 } 163 if format != "json" { 164 imageID = strings.Split(imageID, ":")[1][:12] 165 } 166 167 // no race condition since each goroutine accesses different `i` 168 imagePrintables[i] = composeImagePrintable{ 169 ContainerName: containerName, 170 Repository: repository, 171 Tag: tag, 172 ImageID: imageID, 173 Size: progress.Bytes(size).String(), 174 } 175 176 return nil 177 }) 178 } 179 180 if err := eg.Wait(); err != nil { 181 return err 182 } 183 184 if format == "json" { 185 outJSON, err := formatter.ToJSON(imagePrintables, "", "") 186 if err != nil { 187 return err 188 } 189 _, err = fmt.Fprint(cmd.OutOrStdout(), outJSON) 190 return err 191 } 192 193 w := tabwriter.NewWriter(cmd.OutOrStdout(), 4, 8, 4, ' ', 0) 194 fmt.Fprintln(w, "Container\tRepository\tTag\tImage Id\tSize") 195 for _, p := range imagePrintables { 196 if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", 197 p.ContainerName, 198 p.Repository, 199 p.Tag, 200 p.ImageID, 201 p.Size, 202 ); err != nil { 203 return err 204 } 205 } 206 207 return w.Flush() 208 }