github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/list.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"syscall"
     9  	"text/tabwriter"
    10  	"time"
    11  
    12  	"github.com/moby/sys/user"
    13  	"github.com/opencontainers/runc/libcontainer"
    14  	"github.com/opencontainers/runc/libcontainer/utils"
    15  	"github.com/urfave/cli"
    16  )
    17  
    18  const formatOptions = `table or json`
    19  
    20  // containerState represents the platform agnostic pieces relating to a
    21  // running container's status and state
    22  type containerState struct {
    23  	// Version is the OCI version for the container
    24  	Version string `json:"ociVersion"`
    25  	// ID is the container ID
    26  	ID string `json:"id"`
    27  	// InitProcessPid is the init process id in the parent namespace
    28  	InitProcessPid int `json:"pid"`
    29  	// Status is the current status of the container, running, paused, ...
    30  	Status string `json:"status"`
    31  	// Bundle is the path on the filesystem to the bundle
    32  	Bundle string `json:"bundle"`
    33  	// Rootfs is a path to a directory containing the container's root filesystem.
    34  	Rootfs string `json:"rootfs"`
    35  	// Created is the unix timestamp for the creation time of the container in UTC
    36  	Created time.Time `json:"created"`
    37  	// Annotations is the user defined annotations added to the config.
    38  	Annotations map[string]string `json:"annotations,omitempty"`
    39  	// The owner of the state directory (the owner of the container).
    40  	Owner string `json:"owner"`
    41  }
    42  
    43  var listCommand = cli.Command{
    44  	Name:  "list",
    45  	Usage: "lists containers started by runc with the given root",
    46  	ArgsUsage: `
    47  
    48  Where the given root is specified via the global option "--root"
    49  (default: "/run/runc").
    50  
    51  EXAMPLE 1:
    52  To list containers created via the default "--root":
    53         # runc list
    54  
    55  EXAMPLE 2:
    56  To list containers created using a non-default value for "--root":
    57         # runc --root value list`,
    58  	Flags: []cli.Flag{
    59  		cli.StringFlag{
    60  			Name:  "format, f",
    61  			Value: "table",
    62  			Usage: `select one of: ` + formatOptions,
    63  		},
    64  		cli.BoolFlag{
    65  			Name:  "quiet, q",
    66  			Usage: "display only container IDs",
    67  		},
    68  	},
    69  	Action: func(context *cli.Context) error {
    70  		if err := checkArgs(context, 0, exactArgs); err != nil {
    71  			return err
    72  		}
    73  		s, err := getContainers(context)
    74  		if err != nil {
    75  			return err
    76  		}
    77  
    78  		if context.Bool("quiet") {
    79  			for _, item := range s {
    80  				fmt.Println(item.ID)
    81  			}
    82  			return nil
    83  		}
    84  
    85  		switch context.String("format") {
    86  		case "table":
    87  			w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
    88  			fmt.Fprint(w, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tOWNER\n")
    89  			for _, item := range s {
    90  				fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\n",
    91  					item.ID,
    92  					item.InitProcessPid,
    93  					item.Status,
    94  					item.Bundle,
    95  					item.Created.Format(time.RFC3339Nano),
    96  					item.Owner)
    97  			}
    98  			if err := w.Flush(); err != nil {
    99  				return err
   100  			}
   101  		case "json":
   102  			if err := json.NewEncoder(os.Stdout).Encode(s); err != nil {
   103  				return err
   104  			}
   105  		default:
   106  			return errors.New("invalid format option")
   107  		}
   108  		return nil
   109  	},
   110  }
   111  
   112  func getContainers(context *cli.Context) ([]containerState, error) {
   113  	root := context.GlobalString("root")
   114  	list, err := os.ReadDir(root)
   115  	if err != nil {
   116  		if errors.Is(err, os.ErrNotExist) && context.IsSet("root") {
   117  			// Ignore non-existing default root directory
   118  			// (no containers created yet).
   119  			return nil, nil
   120  		}
   121  		// Report other errors, including non-existent custom --root.
   122  		return nil, err
   123  	}
   124  	var s []containerState
   125  	for _, item := range list {
   126  		if !item.IsDir() {
   127  			continue
   128  		}
   129  		st, err := item.Info()
   130  		if err != nil {
   131  			if errors.Is(err, os.ErrNotExist) {
   132  				// Possible race with runc delete.
   133  				continue
   134  			}
   135  			return nil, err
   136  		}
   137  		// This cast is safe on Linux.
   138  		uid := st.Sys().(*syscall.Stat_t).Uid
   139  		owner, err := user.LookupUid(int(uid))
   140  		if err != nil {
   141  			owner.Name = fmt.Sprintf("#%d", uid)
   142  		}
   143  
   144  		container, err := libcontainer.Load(root, item.Name())
   145  		if err != nil {
   146  			fmt.Fprintf(os.Stderr, "load container %s: %v\n", item.Name(), err)
   147  			continue
   148  		}
   149  		containerStatus, err := container.Status()
   150  		if err != nil {
   151  			fmt.Fprintf(os.Stderr, "status for %s: %v\n", item.Name(), err)
   152  			continue
   153  		}
   154  		state, err := container.State()
   155  		if err != nil {
   156  			fmt.Fprintf(os.Stderr, "state for %s: %v\n", item.Name(), err)
   157  			continue
   158  		}
   159  		pid := state.BaseState.InitProcessPid
   160  		if containerStatus == libcontainer.Stopped {
   161  			pid = 0
   162  		}
   163  		bundle, annotations := utils.Annotations(state.Config.Labels)
   164  		s = append(s, containerState{
   165  			Version:        state.BaseState.Config.Version,
   166  			ID:             state.BaseState.ID,
   167  			InitProcessPid: pid,
   168  			Status:         containerStatus.String(),
   169  			Bundle:         bundle,
   170  			Rootfs:         state.BaseState.Config.Rootfs,
   171  			Created:        state.BaseState.Created,
   172  			Annotations:    annotations,
   173  			Owner:          owner.Name,
   174  		})
   175  	}
   176  	return s, nil
   177  }