Skip to content

Commit

Permalink
Improve crictl inspect[pi] commands to allow filtering
Browse files Browse the repository at this point in the history
The commands now allow filtering based on various fields or just to
inspect all pods, containers or images.

Signed-off-by: Sascha Grunert <[email protected]>
  • Loading branch information
saschagrunert committed Aug 6, 2024
1 parent 8fa1e83 commit 87dd0c4
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 40 deletions.
88 changes: 80 additions & 8 deletions cmd/crictl/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,19 +508,82 @@ var containerStatusCommand = &cli.Command{
Name: "template",
Usage: "The template string is only used when output is go-template; The Template format is golang template",
},
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "Filter by container name regular expression pattern",
},
&cli.StringFlag{
Name: "pod",
Aliases: []string{"p"},
Value: "",
Usage: "Filter by pod id",
},
&cli.StringFlag{
Name: "image",
Value: "",
Usage: "Filter by container image",
},
&cli.StringFlag{
Name: "state",
Aliases: []string{"s"},
Value: "",
Usage: "Filter by container state",
},
&cli.StringSliceFlag{
Name: "label",
Usage: "Filter by key=value label",
},
&cli.BoolFlag{
Name: "latest",
Aliases: []string{"l"},
Usage: "Show the most recently created container (includes all states)",
},
&cli.IntFlag{
Name: "last",
Aliases: []string{"n"},
Usage: "Show last n recently created containers (includes all states). Set 0 for unlimited.",
},
},
Action: func(c *cli.Context) error {
if c.NArg() == 0 {
return errors.New("ID cannot be empty")
}
runtimeClient, err := getRuntimeService(c, 0)
if err != nil {
return err
}

ids := c.Args().Slice()

if len(ids) == 0 {
opts := &listOptions{
nameRegexp: c.String("name"),
podID: c.String("pod"),
image: c.String("image"),
state: c.String("state"),
latest: c.Bool("latest"),
last: c.Int("last"),
}
opts.labels, err = parseLabelStringSlice(c.StringSlice("label"))
if err != nil {
return err
}

ctrs, err := ListContainers(runtimeClient, opts)
if err != nil {
return fmt.Errorf("listing containers: %w", err)
}
for _, ctr := range ctrs {
ids = append(ids, ctr.GetId())
}
}

if len(ids) == 0 {
logrus.Error("No IDs provided or nothing found per filter")
return cli.ShowSubcommandHelp(c)
}

if err := containerStatus(
runtimeClient,
c.Args().Slice(),
ids,
c.String("output"),
c.String("template"),
c.Bool("quiet"),
Expand Down Expand Up @@ -644,7 +707,7 @@ var listContainersCommand = &cli.Command{
return err
}

if err = ListContainers(runtimeClient, imageClient, opts); err != nil {
if err = OutputContainers(runtimeClient, imageClient, opts); err != nil {
return fmt.Errorf("listing containers: %w", err)
}
return nil
Expand Down Expand Up @@ -1091,7 +1154,7 @@ func outputContainerStatusTable(r *pb.ContainerStatusResponse, verbose bool) {

// ListContainers sends a ListContainerRequest to the server, and parses
// the returned ListContainerResponse.
func ListContainers(runtimeClient internalapi.RuntimeService, imageClient internalapi.ImageManagerService, opts *listOptions) error {
func ListContainers(runtimeClient internalapi.RuntimeService, opts *listOptions) ([]*pb.Container, error) {
filter := &pb.ContainerFilter{}
if opts.id != "" {
filter.Id = opts.id
Expand Down Expand Up @@ -1135,9 +1198,18 @@ func ListContainers(runtimeClient internalapi.RuntimeService, imageClient intern
})
logrus.Debugf("ListContainerResponse: %v", r)
if err != nil {
return err
return nil, fmt.Errorf("call list containers RPC: %w", err)
}
return getContainersList(r, opts), nil
}

// OutputContainers sends a ListContainerRequest to the server, and parses
// the returned ListContainerResponse for output.
func OutputContainers(runtimeClient internalapi.RuntimeService, imageClient internalapi.ImageManagerService, opts *listOptions) error {
r, err := ListContainers(runtimeClient, opts)
if err != nil {
return fmt.Errorf("list containers: %w", err)
}
r = getContainersList(r, opts)

switch opts.output {
case outputTypeJSON:
Expand Down
59 changes: 41 additions & 18 deletions cmd/crictl/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,20 +195,11 @@ var listImageCommand = &cli.Command{
return err
}

r, err := ListImages(imageClient, c.Args().First())
r, err := ListImages(imageClient, c.Args().First(), c.StringSlice("filter"))
if err != nil {
return fmt.Errorf("listing images: %w", err)
}

sort.Sort(imageByRef(r.Images))

if len(c.StringSlice("filter")) > 0 && len(r.Images) > 0 {
r.Images, err = filterImagesList(r.Images, c.StringSlice("filter"))
if err != nil {
return fmt.Errorf("listing images: %w", err)
}
}

switch c.String("output") {
case outputTypeJSON:
return outputProtobufObjAsJSON(r)
Expand Down Expand Up @@ -307,11 +298,18 @@ var imageStatusCommand = &cli.Command{
Name: "template",
Usage: "The template string is only used when output is go-template; The Template format is golang template",
},
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "Filter by image name",
},
&cli.StringSliceFlag{
Name: "filter",
Aliases: []string{"f"},
Usage: "Filter output based on provided conditions.\nAvailable filters: \n* dangling=(boolean - true or false)\n* reference=/regular expression/\n* before=<image-name>[:<tag>]|<image id>|<image@digest>\n* since=<image-name>[:<tag>]|<image id>|<image@digest>\nMultiple filters can be combined together.",
},
},
Action: func(c *cli.Context) error {
if c.NArg() == 0 {
return cli.ShowSubcommandHelp(c)
}
imageClient, err := getImageService(c)
if err != nil {
return err
Expand All @@ -324,10 +322,25 @@ var imageStatusCommand = &cli.Command{
}
tmplStr := c.String("template")

statuses := []statusData{}
for i := range c.NArg() {
id := c.Args().Get(i)
ids := c.Args().Slice()

if len(ids) == 0 {
r, err := ListImages(imageClient, c.String("name"), c.StringSlice("filter"))
if err != nil {
return fmt.Errorf("listing images: %w", err)
}
for _, img := range r.GetImages() {
ids = append(ids, img.GetId())
}
}

if len(ids) == 0 {
logrus.Error("No IDs provided or nothing found per filter")
return cli.ShowSubcommandHelp(c)
}

statuses := []statusData{}
for _, id := range ids {
r, err := ImageStatus(imageClient, id, verbose)
if err != nil {
return fmt.Errorf("image status for %q request: %w", id, err)
Expand Down Expand Up @@ -688,8 +701,8 @@ func PullImageWithSandbox(client internalapi.ImageManagerService, image string,

// ListImages sends a ListImagesRequest to the server, and parses
// the returned ListImagesResponse.
func ListImages(client internalapi.ImageManagerService, image string) (*pb.ListImagesResponse, error) {
request := &pb.ListImagesRequest{Filter: &pb.ImageFilter{Image: &pb.ImageSpec{Image: image}}}
func ListImages(client internalapi.ImageManagerService, nameFilter string, conditionFilters []string) (*pb.ListImagesResponse, error) {
request := &pb.ListImagesRequest{Filter: &pb.ImageFilter{Image: &pb.ImageSpec{Image: nameFilter}}}
logrus.Debugf("ListImagesRequest: %v", request)
res, err := InterruptableRPC(nil, func(ctx context.Context) ([]*pb.Image, error) {
return client.ListImages(ctx, request.Filter)
Expand All @@ -699,6 +712,16 @@ func ListImages(client internalapi.ImageManagerService, image string) (*pb.ListI
}
resp := &pb.ListImagesResponse{Images: res}
logrus.Debugf("ListImagesResponse: %v", resp)

sort.Sort(imageByRef(resp.Images))

if len(conditionFilters) > 0 && len(resp.Images) > 0 {
resp.Images, err = filterImagesList(resp.Images, conditionFilters)
if err != nil {
return nil, fmt.Errorf("filter images: %w", err)
}
}

return resp, nil
}

Expand Down
92 changes: 78 additions & 14 deletions cmd/crictl/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,19 +219,74 @@ var podStatusCommand = &cli.Command{
Name: "template",
Usage: "The template string is only used when output is go-template; The Template format is golang template",
},
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "Filter by pod name regular expression pattern",
},
&cli.StringFlag{
Name: "namespace",
Value: "",
Usage: "Filter by pod namespace regular expression pattern",
},
&cli.StringFlag{
Name: "state",
Aliases: []string{"s"},
Value: "",
Usage: "Filter by pod state",
},
&cli.StringSliceFlag{
Name: "label",
Usage: "Filter by key=value label",
},
&cli.BoolFlag{
Name: "latest",
Aliases: []string{"l"},
Usage: "Show the most recently created pod",
},
&cli.IntFlag{
Name: "last",
Aliases: []string{"n"},
Usage: "Show last n recently created pods. Set 0 for unlimited",
},
},
Action: func(c *cli.Context) error {
if c.NArg() == 0 {
return cli.ShowSubcommandHelp(c)
}
runtimeClient, err := getRuntimeService(c, 0)
if err != nil {
return err
}

ids := c.Args().Slice()

if len(ids) == 0 {
opts := &listOptions{
nameRegexp: c.String("name"),
podNamespaceRegexp: c.String("namespace"),
state: c.String("state"),
latest: c.Bool("latest"),
last: c.Int("last"),
}
opts.labels, err = parseLabelStringSlice(c.StringSlice("label"))
if err != nil {
return fmt.Errorf("parse label string slice: %w", err)
}
sbs, err := ListPodSandboxes(runtimeClient, opts)
if err != nil {
return fmt.Errorf("listing pod sandboxes: %w", err)
}
for _, sb := range sbs {
ids = append(ids, sb.GetId())
}
}

if len(ids) == 0 {
logrus.Error("No IDs provided or nothing found per filter")
return cli.ShowSubcommandHelp(c)
}

if err := podSandboxStatus(
runtimeClient,
c.Args().Slice(),
ids,
c.String("output"),
c.Bool("quiet"),
c.String("template"),
Expand All @@ -256,32 +311,32 @@ var listPodCommand = &cli.Command{
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "filter by pod name regular expression pattern",
Usage: "Filter by pod name regular expression pattern",
},
&cli.StringFlag{
Name: "namespace",
Value: "",
Usage: "filter by pod namespace regular expression pattern",
Usage: "Filter by pod namespace regular expression pattern",
},
&cli.StringFlag{
Name: "state",
Aliases: []string{"s"},
Value: "",
Usage: "filter by pod state",
Usage: "Filter by pod state",
},
&cli.StringSliceFlag{
Name: "label",
Usage: "filter by key=value label",
Usage: "Filter by key=value label",
},
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "show verbose info for pods",
Usage: "Show verbose info for pods",
},
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "list only pod IDs",
Usage: "List only pod IDs",
},
&cli.StringFlag{
Name: "output",
Expand Down Expand Up @@ -327,7 +382,7 @@ var listPodCommand = &cli.Command{
if err != nil {
return err
}
if err = ListPodSandboxes(runtimeClient, opts); err != nil {
if err = OutputPodSandboxes(runtimeClient, opts); err != nil {
return fmt.Errorf("listing pod sandboxes: %w", err)
}
return nil
Expand Down Expand Up @@ -487,7 +542,7 @@ func outputPodSandboxStatusTable(r *pb.PodSandboxStatusResponse, verbose bool) {

// ListPodSandboxes sends a ListPodSandboxRequest to the server, and parses
// the returned ListPodSandboxResponse.
func ListPodSandboxes(client internalapi.RuntimeService, opts *listOptions) error {
func ListPodSandboxes(client internalapi.RuntimeService, opts *listOptions) ([]*pb.PodSandbox, error) {
filter := &pb.PodSandboxFilter{}
if opts.id != "" {
filter.Id = opts.id
Expand Down Expand Up @@ -518,9 +573,18 @@ func ListPodSandboxes(client internalapi.RuntimeService, opts *listOptions) erro
})
logrus.Debugf("ListPodSandboxResponse: %v", r)
if err != nil {
return err
return nil, fmt.Errorf("call list sandboxes RPC: %w", err)
}
return getSandboxesList(r, opts), nil
}

// OutputPodSandboxes sends a ListPodSandboxRequest to the server, and parses
// the returned ListPodSandboxResponse for output.
func OutputPodSandboxes(client internalapi.RuntimeService, opts *listOptions) error {
r, err := ListPodSandboxes(client, opts)
if err != nil {
return fmt.Errorf("list pod sandboxes: %w", err)
}
r = getSandboxesList(r, opts)

switch opts.output {
case outputTypeJSON:
Expand Down

0 comments on commit 87dd0c4

Please sign in to comment.