Skip to content

Commit

Permalink
crictl exec: allow running single command in multiple containers
Browse files Browse the repository at this point in the history
This patch adds the following filter flags to `crictl exec`:

```
--image value                    Exec command for all containers matching this container image filter
--label value [ --label value ]  Exec command for all containers matching this key=value label filter
--last value, -n value           Exec command for all last n containers, set to 0 for unlimited (default: 0)
--latest, -l                     Exec command for the most recently created container (default: false)
--name value                     Exec command for all containers matching this name filter regular expression pattern
--pod value, -p value            Exec command for all containers matching this pod ID filter
--state value                    Exec command for all containers matching this container state filter
```

If none of the above filters are set, then crictl will assume that the
first provided argument is the container ID. If one of them is set, then
crictl will filter and execute for all containers:

```
> sudo ./build/bin/linux/amd64/crictl exec --name pod ps aux
8f92b841d898828833d7dc55245e5df91c517cb0fce8a61f5def7cb9e432530c:
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   2472  1344 ?        Ss   07:09   0:00 /bin/sleep 6000 6000
root          67  0.0  0.0   5536  2700 ?        Rs   07:38   0:00 ps aux
39f78a7b1835b8363a519424031ddbb370b743afad46cc401eeb73717c84e57d:
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   2472  1500 ?        Ss   07:08   0:00 /bin/sleep 6000 6000
root         122  0.0  0.0   5536  2732 ?        Rs   07:38   0:00 ps aux
```

The container ID is printed when a filter command has been used to
ensure existing backwards compatibility.

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
  • Loading branch information
saschagrunert committed Sep 20, 2024
1 parent 0c5b64c commit d1d09eb
Showing 1 changed file with 116 additions and 21 deletions.
137 changes: 116 additions & 21 deletions cmd/crictl/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@ const (
transportFlag = "transport"
transportWebsocket = "websocket"
transportSpdy = "spdy"
)

const detachSequence = "ctrl-p,ctrl-q"
detachSequence = "ctrl-p,ctrl-q"
)

var runtimeExecCommand = &cli.Command{
Name: "exec",
Usage: "Run a command in a running container",
ArgsUsage: "CONTAINER-ID COMMAND [ARG...]",
Name: "exec",
Usage: "Run a command in a running container",
ArgsUsage: "[CONTAINER-ID] COMMAND [ARG...]",
Description: `The CONTAINER-ID is only required if none of the following filter flags are set:
--image, --label, --last, --latest, --name, --pod, --state`,
UseShortOptionHandling: true,
Flags: []cli.Flag{
&cli.BoolFlag{
Expand Down Expand Up @@ -76,9 +78,40 @@ var runtimeExecCommand = &cli.Command{
Value: transportSpdy,
Usage: fmt.Sprintf("Transport protocol to use, one of: %s|%s", transportSpdy, transportWebsocket),
},
&cli.StringFlag{
Name: "name",
Usage: "Exec command for all containers matching this name filter regular expression pattern",
},
&cli.StringFlag{
Name: "pod",
Aliases: []string{"p"},
Usage: "Exec command for all containers matching this pod ID filter",
},
&cli.StringFlag{
Name: "image",
Usage: "Exec command for all containers matching this container image filter",
},
&cli.StringFlag{
Name: "state",
Usage: "Exec command for all containers matching this container state filter",
},
&cli.StringSliceFlag{
Name: "label",
Usage: "Exec command for all containers matching this key=value label filter",
},
&cli.BoolFlag{
Name: "latest",
Aliases: []string{"l"},
Usage: "Exec command for the most recently created container",
},
&cli.IntFlag{
Name: "last",
Aliases: []string{"n"},
Usage: "Exec command for all last n containers, set to 0 for unlimited",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 2 {
if c.NArg() < 1 {
return cli.ShowSubcommandHelp(c)
}

Expand All @@ -87,32 +120,94 @@ var runtimeExecCommand = &cli.Command{
return err
}

// Assume a regular exec where the first arg is the container ID.
ids := []string{c.Args().First()}
cmd := c.Args().Slice()[1:]

// If any of the filter flags are set, then we assume that no
// CONTAINER-ID is provided as CLI parameter.
if c.IsSet("name") ||
c.IsSet("pod") ||
c.IsSet("image") ||
c.IsSet("state") ||
c.IsSet("label") ||
c.IsSet("latest") ||
c.IsSet("last") {

ids = []string{}
cmd = c.Args().Slice()

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 containers found per filter flags")
return cli.ShowSubcommandHelp(c)
}
} else if c.NArg() < 2 {
return cli.ShowSubcommandHelp(c)
}

opts := execOptions{
id: c.Args().First(),
timeout: c.Int64("timeout"),
tty: c.Bool("tty"),
stdin: c.Bool("interactive"),
cmd: c.Args().Slice()[1:],
cmd: cmd,
transport: c.String(transportFlag),
}
if c.Bool("sync") {
exitCode, err := ExecSync(runtimeClient, opts)
if err != nil {
return fmt.Errorf("execing command in container synchronously: %w", err)

outputID := false
if len(ids) > 1 {
outputID = true
}

for _, id := range ids {
opts.id = id

if outputID {
fmt.Println(id + ":")
}
if exitCode != 0 {
return cli.Exit("non-zero exit code", exitCode)

if c.Bool("sync") {
exitCode, err := ExecSync(runtimeClient, opts)
if err != nil {
return fmt.Errorf("execing command in container synchronously: %w", err)
}
if exitCode != 0 {
return cli.Exit("non-zero exit code", exitCode)
}
continue
}
return nil
}

ctx, cancel := context.WithCancel(c.Context)
defer cancel()
ctx, cancel := context.WithCancel(c.Context)
defer cancel()
err = Exec(ctx, runtimeClient, opts)
if err != nil {
return fmt.Errorf("execing command in container: %w", err)
}

err = Exec(ctx, runtimeClient, opts)
if err != nil {
return fmt.Errorf("execing command in container: %w", err)
}

return nil
},
}
Expand Down

0 comments on commit d1d09eb

Please sign in to comment.