github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/pod_ps.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 "sort" 7 "strings" 8 "time" 9 10 "github.com/containers/buildah/pkg/formats" 11 "github.com/containers/libpod/cmd/podman/cliconfig" 12 "github.com/containers/libpod/cmd/podman/shared" 13 "github.com/containers/libpod/libpod/define" 14 "github.com/containers/libpod/pkg/adapter" 15 "github.com/docker/go-units" 16 "github.com/pkg/errors" 17 "github.com/spf13/cobra" 18 ) 19 20 const ( 21 STOPPED = "Stopped" //nolint 22 RUNNING = "Running" 23 PAUSED = "Paused" 24 EXITED = "Exited" 25 ERROR = "Error" 26 CREATED = "Created" 27 NUM_CTR_INFO = 10 28 ) 29 30 var ( 31 bc_opts shared.PsOptions 32 ) 33 34 type podPsCtrInfo struct { 35 Name string `json:"name,omitempty"` 36 Id string `json:"id,omitempty"` 37 Status string `json:"status,omitempty"` 38 } 39 40 type podPsOptions struct { 41 NoTrunc bool 42 Format string 43 Sort string 44 Quiet bool 45 NumberOfContainers bool 46 Cgroup bool 47 NamesOfContainers bool 48 IdsOfContainers bool 49 StatusOfContainers bool 50 } 51 52 type podPsTemplateParams struct { 53 Created string 54 ID string 55 Name string 56 NumberOfContainers int 57 Status string 58 Cgroup string 59 ContainerInfo string 60 InfraID string 61 Namespaces string 62 } 63 64 // podPsJSONParams is used as a base structure for the psParams 65 // If template output is requested, podPsJSONParams will be converted to 66 // podPsTemplateParams. 67 // podPsJSONParams will be populated by data from libpod.Container, 68 // the members of the struct are the sama data types as their sources. 69 type podPsJSONParams struct { 70 CreatedAt time.Time `json:"createdAt"` 71 ID string `json:"id"` 72 Name string `json:"name"` 73 NumberOfContainers int `json:"numberOfContainers"` 74 Status string `json:"status"` 75 CtrsInfo []podPsCtrInfo `json:"containerInfo,omitempty"` 76 Cgroup string `json:"cgroup,omitempty"` 77 InfraID string `json:"infraContainerId,omitempty"` 78 Namespaces []string `json:"namespaces,omitempty"` 79 } 80 81 // Type declaration and functions for sorting the pod PS output 82 type podPsSorted []podPsJSONParams 83 84 func (a podPsSorted) Len() int { return len(a) } 85 func (a podPsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 86 87 type podPsSortedCreated struct{ podPsSorted } 88 89 func (a podPsSortedCreated) Less(i, j int) bool { 90 return a.podPsSorted[i].CreatedAt.After(a.podPsSorted[j].CreatedAt) 91 } 92 93 type podPsSortedId struct{ podPsSorted } 94 95 func (a podPsSortedId) Less(i, j int) bool { return a.podPsSorted[i].ID < a.podPsSorted[j].ID } 96 97 type podPsSortedNumber struct{ podPsSorted } 98 99 func (a podPsSortedNumber) Less(i, j int) bool { 100 return len(a.podPsSorted[i].CtrsInfo) < len(a.podPsSorted[j].CtrsInfo) 101 } 102 103 type podPsSortedName struct{ podPsSorted } 104 105 func (a podPsSortedName) Less(i, j int) bool { return a.podPsSorted[i].Name < a.podPsSorted[j].Name } 106 107 type podPsSortedStatus struct{ podPsSorted } 108 109 func (a podPsSortedStatus) Less(i, j int) bool { 110 return a.podPsSorted[i].Status < a.podPsSorted[j].Status 111 } 112 113 var ( 114 podPsCommand cliconfig.PodPsValues 115 116 podPsDescription = "List all pods on system including their names, ids and current state." 117 _podPsCommand = &cobra.Command{ 118 Use: "ps", 119 Aliases: []string{"ls", "list"}, 120 Args: noSubArgs, 121 Short: "List pods", 122 Long: podPsDescription, 123 RunE: func(cmd *cobra.Command, args []string) error { 124 podPsCommand.InputArgs = args 125 podPsCommand.GlobalFlags = MainGlobalOpts 126 podPsCommand.Remote = remoteclient 127 return podPsCmd(&podPsCommand) 128 }, 129 } 130 ) 131 132 func init() { 133 podPsCommand.Command = _podPsCommand 134 podPsCommand.SetHelpTemplate(HelpTemplate()) 135 podPsCommand.SetUsageTemplate(UsageTemplate()) 136 flags := podPsCommand.Flags() 137 flags.BoolVar(&podPsCommand.CtrNames, "ctr-names", false, "Display the container names") 138 flags.BoolVar(&podPsCommand.CtrIDs, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated") 139 flags.BoolVar(&podPsCommand.CtrStatus, "ctr-status", false, "Display the container status") 140 flags.StringVarP(&podPsCommand.Filter, "filter", "f", "", "Filter output based on conditions given") 141 flags.StringVar(&podPsCommand.Format, "format", "", "Pretty-print pods to JSON or using a Go template") 142 flags.BoolVarP(&podPsCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") 143 flags.BoolVar(&podPsCommand.Namespace, "namespace", false, "Display namespace information of the pod") 144 flags.BoolVar(&podPsCommand.Namespace, "ns", false, "Display namespace information of the pod") 145 flags.BoolVar(&podPsCommand.NoTrunc, "no-trunc", false, "Do not truncate pod and container IDs") 146 flags.BoolVarP(&podPsCommand.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only") 147 flags.StringVar(&podPsCommand.Sort, "sort", "created", "Sort output by created, id, name, or number") 148 markFlagHiddenForRemoteClient("latest", flags) 149 } 150 151 func podPsCmd(c *cliconfig.PodPsValues) error { 152 if err := podPsCheckFlagsPassed(c); err != nil { 153 return errors.Wrapf(err, "error with flags passed") 154 } 155 156 runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) 157 if err != nil { 158 return errors.Wrapf(err, "error creating libpod runtime") 159 } 160 defer runtime.DeferredShutdown(false) 161 162 opts := podPsOptions{ 163 NoTrunc: c.NoTrunc, 164 Quiet: c.Quiet, 165 Sort: c.Sort, 166 IdsOfContainers: c.CtrIDs, 167 NamesOfContainers: c.CtrNames, 168 StatusOfContainers: c.CtrStatus, 169 } 170 171 opts.Format = genPodPsFormat(c) 172 173 var pods []*adapter.Pod 174 175 // If latest is set true filters are ignored. 176 if c.Latest { 177 pod, err := runtime.GetLatestPod() 178 if err != nil { 179 return err 180 } 181 pods = append(pods, pod) 182 return generatePodPsOutput(pods, opts) 183 } 184 185 if c.Filter != "" { 186 pods, err = runtime.GetPodsWithFilters(c.Filter) 187 if err != nil { 188 return err 189 } 190 } else { 191 pods, err = runtime.GetAllPods() 192 if err != nil { 193 return err 194 } 195 } 196 197 return generatePodPsOutput(pods, opts) 198 } 199 200 // podPsCheckFlagsPassed checks if mutually exclusive flags are passed together 201 func podPsCheckFlagsPassed(c *cliconfig.PodPsValues) error { 202 // quiet, and format with Go template are mutually exclusive 203 flags := 0 204 if c.Quiet { 205 flags++ 206 } 207 if c.Flag("format").Changed && c.Format != formats.JSONString { 208 flags++ 209 } 210 if flags > 1 { 211 return errors.Errorf("quiet and format with Go template are mutually exclusive") 212 } 213 return nil 214 } 215 216 // generate the template based on conditions given 217 func genPodPsFormat(c *cliconfig.PodPsValues) string { 218 format := "" 219 switch { 220 case c.Format != "": 221 // "\t" from the command line is not being recognized as a tab 222 // replacing the string "\t" to a tab character if the user passes in "\t" 223 format = strings.Replace(c.Format, `\t`, "\t", -1) 224 case c.Quiet: 225 format = formats.IDString 226 default: 227 format = "table {{.ID}}\t{{.Name}}\t{{.Status}}\t{{.Created}}" 228 if c.Bool("namespace") { 229 format += "\t{{.Cgroup}}\t{{.Namespaces}}" 230 } 231 if c.CtrNames || c.CtrIDs || c.CtrStatus { 232 format += "\t{{.ContainerInfo}}" 233 } else { 234 format += "\t{{.NumberOfContainers}}" 235 } 236 format += "\t{{.InfraID}}" 237 } 238 return format 239 } 240 241 func podPsToGeneric(templParams []podPsTemplateParams, jsonParams []podPsJSONParams) (genericParams []interface{}) { 242 if len(templParams) > 0 { 243 for _, v := range templParams { 244 genericParams = append(genericParams, interface{}(v)) 245 } 246 return 247 } 248 for _, v := range jsonParams { 249 genericParams = append(genericParams, interface{}(v)) 250 } 251 return 252 } 253 254 // generate the accurate header based on template given 255 func (p *podPsTemplateParams) podHeaderMap() map[string]string { 256 v := reflect.Indirect(reflect.ValueOf(p)) 257 values := make(map[string]string) 258 259 for i := 0; i < v.NumField(); i++ { 260 key := v.Type().Field(i).Name 261 value := key 262 if value == "ID" { 263 value = "Pod" + value 264 } 265 if value == "NumberOfContainers" { 266 value = "#OfContainers" 267 } 268 values[key] = strings.ToUpper(splitCamelCase(value)) 269 } 270 return values 271 } 272 273 func sortPodPsOutput(sortBy string, psOutput podPsSorted) (podPsSorted, error) { 274 switch sortBy { 275 case "created": 276 sort.Sort(podPsSortedCreated{psOutput}) 277 case "id": 278 sort.Sort(podPsSortedId{psOutput}) 279 case "name": 280 sort.Sort(podPsSortedName{psOutput}) 281 case "number": 282 sort.Sort(podPsSortedNumber{psOutput}) 283 case "status": 284 sort.Sort(podPsSortedStatus{psOutput}) 285 default: 286 return nil, errors.Errorf("invalid option for --sort, options are: id, names, or number") 287 } 288 return psOutput, nil 289 } 290 291 // getPodTemplateOutput returns the modified container information 292 func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podPsTemplateParams, error) { 293 var ( 294 psOutput []podPsTemplateParams 295 ) 296 297 for _, psParam := range psParams { 298 podID := psParam.ID 299 infraID := psParam.InfraID 300 var ctrStr string 301 302 truncated := "" 303 if !opts.NoTrunc { 304 podID = shortID(podID) 305 if len(psParam.CtrsInfo) > NUM_CTR_INFO { 306 psParam.CtrsInfo = psParam.CtrsInfo[:NUM_CTR_INFO] 307 truncated = "..." 308 } 309 infraID = shortID(infraID) 310 } 311 for _, ctrInfo := range psParam.CtrsInfo { 312 infoSlice := make([]string, 0) 313 if opts.IdsOfContainers { 314 if opts.NoTrunc { 315 infoSlice = append(infoSlice, ctrInfo.Id) 316 } else { 317 infoSlice = append(infoSlice, shortID(ctrInfo.Id)) 318 } 319 } 320 if opts.NamesOfContainers { 321 infoSlice = append(infoSlice, ctrInfo.Name) 322 } 323 if opts.StatusOfContainers { 324 infoSlice = append(infoSlice, ctrInfo.Status) 325 } 326 if len(infoSlice) != 0 { 327 ctrStr += fmt.Sprintf("[%s] ", strings.Join(infoSlice, ",")) 328 } 329 } 330 ctrStr += truncated 331 params := podPsTemplateParams{ 332 Created: units.HumanDuration(time.Since(psParam.CreatedAt)) + " ago", 333 ID: podID, 334 Name: psParam.Name, 335 Status: psParam.Status, 336 NumberOfContainers: psParam.NumberOfContainers, 337 Cgroup: psParam.Cgroup, 338 ContainerInfo: ctrStr, 339 InfraID: infraID, 340 Namespaces: strings.Join(psParam.Namespaces, ","), 341 } 342 343 psOutput = append(psOutput, params) 344 } 345 346 return psOutput, nil 347 } 348 349 func getNamespaces(pod *adapter.Pod) []string { 350 var shared []string 351 if pod.SharesPID() { 352 shared = append(shared, "pid") 353 } 354 if pod.SharesNet() { 355 shared = append(shared, "net") 356 } 357 if pod.SharesMount() { 358 shared = append(shared, "mnt") 359 } 360 if pod.SharesIPC() { 361 shared = append(shared, "ipc") 362 } 363 if pod.SharesUser() { 364 shared = append(shared, "user") 365 } 366 if pod.SharesCgroup() { 367 shared = append(shared, "cgroup") 368 } 369 if pod.SharesUTS() { 370 shared = append(shared, "uts") 371 } 372 return shared 373 } 374 375 // getAndSortPodJSONOutput returns the container info in its raw, sorted form 376 func getAndSortPodJSONParams(pods []*adapter.Pod, opts podPsOptions) ([]podPsJSONParams, error) { 377 var ( 378 psOutput []podPsJSONParams 379 ) 380 381 for _, pod := range pods { 382 ctrs, err := pod.AllContainers() 383 ctrsInfo := make([]podPsCtrInfo, 0) 384 if err != nil { 385 return nil, err 386 } 387 ctrNum := len(ctrs) 388 status, err := pod.GetPodStatus() 389 if err != nil { 390 return nil, err 391 } 392 393 infraID, err := pod.InfraContainerID() 394 if err != nil { 395 return nil, err 396 } 397 for _, ctr := range ctrs { 398 batchInfo, err := adapter.BatchContainerOp(ctr, bc_opts) 399 if err != nil { 400 return nil, err 401 } 402 var status string 403 switch batchInfo.ConState { 404 case define.ContainerStateExited: 405 fallthrough 406 case define.ContainerStateStopped: 407 status = EXITED 408 case define.ContainerStateRunning: 409 status = RUNNING 410 case define.ContainerStatePaused: 411 status = PAUSED 412 case define.ContainerStateCreated, define.ContainerStateConfigured: 413 status = CREATED 414 default: 415 status = ERROR 416 } 417 ctrsInfo = append(ctrsInfo, podPsCtrInfo{ 418 Name: batchInfo.ConConfig.Name, 419 Id: ctr.ID(), 420 Status: status, 421 }) 422 } 423 params := podPsJSONParams{ 424 CreatedAt: pod.CreatedTime(), 425 ID: pod.ID(), 426 Name: pod.Name(), 427 Status: status, 428 Cgroup: pod.CgroupParent(), 429 NumberOfContainers: ctrNum, 430 CtrsInfo: ctrsInfo, 431 Namespaces: getNamespaces(pod), 432 InfraID: infraID, 433 } 434 435 psOutput = append(psOutput, params) 436 } 437 return sortPodPsOutput(opts.Sort, psOutput) 438 } 439 440 func generatePodPsOutput(pods []*adapter.Pod, opts podPsOptions) error { 441 if len(pods) == 0 && opts.Format != formats.JSONString { 442 return nil 443 } 444 psOutput, err := getAndSortPodJSONParams(pods, opts) 445 if err != nil { 446 return err 447 } 448 var out formats.Writer 449 450 switch opts.Format { 451 case formats.JSONString: 452 out = formats.JSONStructArray{Output: podPsToGeneric([]podPsTemplateParams{}, psOutput)} 453 default: 454 psOutput, err := getPodTemplateOutput(psOutput, opts) 455 if err != nil { 456 return errors.Wrapf(err, "unable to create output") 457 } 458 out = formats.StdoutTemplateArray{Output: podPsToGeneric(psOutput, []podPsJSONParams{}), Template: opts.Format, Fields: psOutput[0].podHeaderMap()} 459 } 460 461 return out.Out() 462 }