Skip to content

Commit

Permalink
Merge pull request #465 from srl-labs/execjson
Browse files Browse the repository at this point in the history
json output for `exec` command
  • Loading branch information
hellt committed Jun 25, 2021
2 parents 7627467 + 041bfaf commit 828c564
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 18 deletions.
76 changes: 59 additions & 17 deletions cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,45 @@ package cmd

import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"

"github.com/google/shlex"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/srl-labs/containerlab/clab"
"github.com/srl-labs/containerlab/types"
)

var labels []string
var (
labels []string
execFormat string
execCommand string
)

// execCmd represents the exec command
var execCmd = &cobra.Command{
Use: "exec",
Short: "execute a command on one or multiple containers",
PreRunE: sudoCheck,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
if name == "" && topo == "" {
fmt.Println("provide either lab name (--name) or topology file path (--topo)")
return
return errors.New("provide either lab name (--name) or topology file path (--topo)")

}

if execCommand == "" {
return errors.New("provide command to execute")
}
log.Debugf("raw command: %v", args)
if len(args) == 0 {
fmt.Println("provide command to execute")
return

switch execFormat {
case "json",
"plain":
// expected values, go on
default:
log.Error("format is expected to be either json or plain")
}
opts := []clab.ClabOption{
clab.WithDebug(debug),
Expand All @@ -52,14 +66,18 @@ var execCmd = &cobra.Command{
log.Fatalf("could not list containers: %v", err)
}
if len(containers) == 0 {
log.Println("no containers found")
return
return errors.New("no containers found")
}
cmds := make([]string, 0, len(args))
for _, a := range args {
cmds = append(cmds, strings.Split(a, " ")...)

cmds, err := shlex.Split(execCommand)
if err != nil {
return err
}

jsonResult := make(map[string]map[string]interface{})

for _, cont := range containers {
var doc interface{}
if cont.State != "running" {
continue
}
Expand All @@ -68,17 +86,41 @@ var execCmd = &cobra.Command{
log.Errorf("%s: failed to execute cmd: %v", cont.Names, err)
continue
}
if len(stdout) > 0 {
log.Infof("%s: stdout:\n%s", cont.Names, string(stdout))
contName := strings.TrimLeft(cont.Names[0], "/")
switch execFormat {
case "json":
jsonResult[contName] = make(map[string]interface{})
err := json.Unmarshal([]byte(stdout), &doc)
if err == nil {
jsonResult[contName]["stdout"] = doc
} else {
jsonResult[contName]["stdout"] = string(stdout)
}
jsonResult[contName]["stderr"] = string(stderr)
case "plain":
if len(stdout) > 0 {
log.Infof("%s: stdout:\n%s", contName, string(stdout))
}
if len(stderr) > 0 {
log.Infof("%s: stderr:\n%s", contName, string(stderr))
}

}
if len(stderr) > 0 {
log.Infof("%s: stderr:\n%s", cont.Names, string(stderr))
}
if execFormat == "json" {
result, err := json.Marshal(jsonResult)
if err != nil {
log.Errorf("Issue converting to json %v", err)
}
fmt.Println(string(result))
}
return err
},
}

func init() {
rootCmd.AddCommand(execCmd)
execCmd.Flags().StringVarP(&execCommand, "cmd", "", "", "command to execute")
execCmd.Flags().StringSliceVarP(&labels, "label", "", []string{}, "labels to filter container subset")
execCmd.Flags().StringVarP(&execFormat, "format", "f", "plain", "output format. One of [json, plain]")
}
118 changes: 118 additions & 0 deletions docs/cmd/exec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# exec command

### Description

The `exec` command allows to run a command inside the nodes that part of a certain lab.

This command does exactly the same thing as `docker exec` does, but it allows to run the same command across all the nodes of a lab.

### Usage

`containerlab [global-flags] exec [local-flags]`

### Flags

#### topology

With the global `--topo | -t` flag a user specifies from which lab to take the containers and perform the exec command.

#### cmd
The command to be executed on the nodes is provided with `--cmd` flag. The command is provided as a string, thus it needs to be quoted to accommodate for spaces or special characters.

#### format
The `--format | -f` flag allows to select between plain text format output or a json variant. Consult with the examples below to see the differences between these two formatting options.

Defaults to `plain` output format.

#### label
By default `exec` command will attempt to execute the command across all the nodes of a lab. To limit the scope of the execution, the users can leverage the `--label` flag to filter out the nodes of interest.

### Examples

```bash
# show ipv4 information from all the nodes of the lab
# with a plain text output
❯ containerlab exec -t srl02.yml --cmd 'ip -4 a show dummy-mgmt0'
INFO[0000] clab-srl02-srl1: stdout:
6: dummy-mgmt0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
inet 172.20.20.3/24 brd 172.20.20.255 scope global dummy-mgmt0
valid_lft forever preferred_lft forever
INFO[0000] clab-srl02-srl2: stdout:
6: dummy-mgmt0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
inet 172.20.20.2/24 brd 172.20.20.255 scope global dummy-mgmt0
valid_lft forever preferred_lft forever


# execute a CLI command with a plain text output
❯ containerlab exec -t srl02.yml --cmd 'sr_cli "show version"'
INFO[0001] clab-srl02-srl1: stdout:
----------------------------------------------------
Hostname : srl1
Chassis Type : 7250 IXR-6
Part Number : Sim Part No.
Serial Number : Sim Serial No.
System MAC Address: 02:00:6B:FF:00:00
Software Version : v20.6.3
Build Number : 145-g93496a3f8c
Architecture : x86_64
Last Booted : 2021-06-24T10:25:26.722Z
Total Memory : 24052875 kB
Free Memory : 21911906 kB
----------------------------------------------------
INFO[0003] clab-srl02-srl2: stdout:
----------------------------------------------------
Hostname : srl2
Chassis Type : 7250 IXR-6
Part Number : Sim Part No.
Serial Number : Sim Serial No.
System MAC Address: 02:D8:A9:FF:00:00
Software Version : v20.6.3
Build Number : 145-g93496a3f8c
Architecture : x86_64
Last Booted : 2021-06-24T10:25:26.904Z
Total Memory : 24052875 kB
Free Memory : 21911914 kB
----------------------------------------------------


# execute a CLI command with a json output
❯ containerlab exec -t srl02.yml --cmd 'sr_cli "show version | as json"' -f json | jq
{
"clab-srl02-srl1": {
"stderr": "",
"stdout": {
"basic system info": {
"Architecture": "x86_64",
"Build Number": "145-g93496a3f8c",
"Chassis Type": "7250 IXR-6",
"Free Memory": "21911367 kB",
"Hostname": "srl1",
"Last Booted": "2021-06-24T10:25:26.722Z",
"Part Number": "Sim Part No.",
"Serial Number": "Sim Serial No.",
"Software Version": "v20.6.3",
"System MAC Address": "02:00:6B:FF:00:00",
"Total Memory": "24052875 kB"
}
}
},
"clab-srl02-srl2": {
"stderr": "",
"stdout": {
"basic system info": {
"Architecture": "x86_64",
"Build Number": "145-g93496a3f8c",
"Chassis Type": "7250 IXR-6",
"Free Memory": "21911367 kB",
"Hostname": "srl2",
"Last Booted": "2021-06-24T10:25:26.904Z",
"Part Number": "Sim Part No.",
"Serial Number": "Sim Serial No.",
"Software Version": "v20.6.3",
"System MAC Address": "02:D8:A9:FF:00:00",
"Total Memory": "24052875 kB"
}
}
}
}
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ nav:
- destroy: cmd/destroy.md
- inspect: cmd/inspect.md
- save: cmd/save.md
- exec: cmd/exec.md
- generate: cmd/generate.md
- graph: cmd/graph.md
- tools:
Expand Down
2 changes: 1 addition & 1 deletion tests/01-smoke/03-bridges-and-host.robot
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Verify management network is using user-specified bridge
# show management interface info and cut the information about the ifindex of the remote veth
# note that exec returns the info in the stderr stream, thus we use stderr to parse the ifindex
${rc} ${iface} = OperatingSystem.Run And Return Rc And Output
... sudo containerlab --runtime ${runtime} exec -t ${CURDIR}/${lab-file} --label clab-node-name\=l1 ip l show eth0 2>&1 | cut -d ' ' -f5 | cut -d '@' -f2 | cut -c3-
... sudo containerlab --runtime ${runtime} exec -t ${CURDIR}/${lab-file} --label clab-node-name\=l1 --cmd "ip l show eth0" 2>&1 | cut -d ' ' -f5 | cut -d '@' -f2 | cut -c3-
Log ${iface}
Should Be Equal As Integers ${rc} 0
${rc} ${res} = OperatingSystem.Run And Return Rc And Output
Expand Down

0 comments on commit 828c564

Please sign in to comment.