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  }