diff --git a/cmd/swctl/app/cli.go b/cmd/swctl/app/cli.go index 1611ff0..fa1f9f1 100644 --- a/cmd/swctl/app/cli.go +++ b/cmd/swctl/app/cli.go @@ -24,7 +24,7 @@ type Cli interface { Client() client.API Entities() []Entity GlobalOptions() *GlobalOptions - Exec(cmd string, args []string) (stdout string, stderr string, err error) + Exec(cmd string, args []string, liveOutput bool) (stdout string, stderr string, err error) AppName() string Out() *streams.Out @@ -136,7 +136,7 @@ func (cli *CLI) GlobalOptions() *GlobalOptions { return cli.globalOptions } -func (cli *CLI) Exec(cmd string, args []string) (string, string, error) { +func (cli *CLI) Exec(cmd string, args []string, liveOutput bool) (string, string, error) { if cmd == "" { return "", "", errors.New("cannot execute empty command") } @@ -145,7 +145,8 @@ func (cli *CLI) Exec(cmd string, args []string) (string, string, error) { args = append(cmdParts[1:], args...) } ecmd := newExternalCmd(externalExe(cmdParts[0]), args, cli) - res, err := ecmd.exec() + + res, err := ecmd.exec(liveOutput) if err != nil { return "", "", err } diff --git a/cmd/swctl/app/cmd_config.go b/cmd/swctl/app/cmd_config.go index 18cf314..027f1d4 100644 --- a/cmd/swctl/app/cmd_config.go +++ b/cmd/swctl/app/cmd_config.go @@ -48,7 +48,7 @@ func runConfigCmd(cli Cli, opts ConfigCmdOptions) error { args = append(args, hideInternalFlag) } - stdout, stderr, err := cli.Exec("agentctl config", args) + stdout, stderr, err := cli.Exec("agentctl config", args, false) if err != nil { return err } diff --git a/cmd/swctl/app/cmd_dependency.go b/cmd/swctl/app/cmd_dependency.go index 755cbb2..aa095f5 100644 --- a/cmd/swctl/app/cmd_dependency.go +++ b/cmd/swctl/app/cmd_dependency.go @@ -239,7 +239,7 @@ func linkSetUpDown(cli Cli) *cobra.Command { } } else if args[len(args)-1] == "down" { //link down interface, only assigned network devices have /net directory which is name of interface - stdout, stderr, err := cli.Exec("ls", []string{"/sys/bus/pci/devices/" + physicalInterfaces[matchId].Pci + "/net"}) + stdout, stderr, err := cli.Exec("ls", []string{"/sys/bus/pci/devices/" + physicalInterfaces[matchId].Pci + "/net"}, false) if stderr != "" { return errors.New(stderr) } @@ -247,7 +247,7 @@ func linkSetUpDown(cli Cli) *cobra.Command { return err } if stdout != "" { - _, _, err = cli.Exec("sudo ip link set "+stdout+" down", nil) + _, _, err = cli.Exec("sudo ip link set "+stdout+" down", nil, false) if err != err { return err } @@ -269,7 +269,7 @@ func linkSetUpDown(cli Cli) *cobra.Command { } func IsDockerAvailable(cli Cli) (bool, error) { - out, _, err := cli.Exec("whereis docker", nil) + out, _, err := cli.Exec("whereis docker", nil, false) if err != nil { return false, err } @@ -280,7 +280,7 @@ func IsDockerAvailable(cli Cli) (bool, error) { } func AllocatedHugePages(cli Cli) (int, error) { - out, _, err := cli.Exec("sysctl vm.nr_hugepages -n", nil) + out, _, err := cli.Exec("sysctl vm.nr_hugepages -n", nil, false) if err != nil { return 0, err } @@ -300,7 +300,7 @@ func ResizeHugePages(cli Cli, size uint) error { fmt.Fprintln(cli.Out(), "Skipping hugepages") return nil } - _, _, err := cli.Exec(fmt.Sprintf("sudo sysctl -w vm.nr_hugepages=%d", size), nil) + _, _, err := cli.Exec(fmt.Sprintf("sudo sysctl -w vm.nr_hugepages=%d", size), nil, false) if err != nil { return err } @@ -341,7 +341,7 @@ func InstallDocker(cli Cli, dockerVersion string) error { } for _, command := range commands { - out, stderr, err := cli.Exec("bash -c", []string{command}) + out, stderr, err := cli.Exec("bash -c", []string{command}, false) if stderr != "" { return errors.New(command + ": " + stderr) } @@ -433,7 +433,7 @@ func unbindDevice(cli Cli, pci string, driver string) error { //Mostly path := fmt.Sprintf("/sys/bus/pci/drivers/%s/unbind", driver) - _, stderr, err := cli.Exec("sudo bash -c", []string{"echo \"" + pci + "\" > " + path}) + _, stderr, err := cli.Exec("sudo bash -c", []string{"echo \"" + pci + "\" > " + path}, false) if stderr != "" { return errors.New(stderr) } @@ -446,7 +446,7 @@ func bindDevice(cli Cli, pci string, driver string) error { path := fmt.Sprintf("/sys/bus/pci/drivers/%s/bind", driver) - _, stderr, err := cli.Exec("sudo bash -c", []string{"echo \"" + pci + "\" > " + path}) + _, stderr, err := cli.Exec("sudo bash -c", []string{"echo \"" + pci + "\" > " + path}, false) if stderr != "" { return errors.New(stderr) } @@ -459,7 +459,7 @@ func bindDevice(cli Cli, pci string, driver string) error { func DumpDevices(cli Cli) ([]NetworkInterface, error) { var nics []NetworkInterface - stdout, _, err := cli.Exec("lspci", []string{"-Dvmmnnk"}) + stdout, _, err := cli.Exec("lspci", []string{"-Dvmmnnk"}, false) if err != nil { return nil, err } diff --git a/cmd/swctl/app/cmd_deploy.go b/cmd/swctl/app/cmd_deploy.go index d84ba48..5a5e267 100644 --- a/cmd/swctl/app/cmd_deploy.go +++ b/cmd/swctl/app/cmd_deploy.go @@ -59,12 +59,10 @@ func newDeploymentUp(cli Cli) *cobra.Command { Args: cobra.ArbitraryArgs, DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { - stdout, stderr, err := cli.Exec("docker compose up", args) + _, _, err := cli.Exec("docker compose up", args, true) if err != nil { return err } - fmt.Fprintln(cli.Out(), stdout) - fmt.Fprintln(cli.Err(), stderr) return nil }, } @@ -78,12 +76,10 @@ func newDeploymentDown(cli Cli) *cobra.Command { Args: cobra.ArbitraryArgs, DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { - stdout, stderr, err := cli.Exec("docker compose down", args) + _, _, err := cli.Exec("docker compose down", args, true) if err != nil { return err } - fmt.Fprintln(cli.Out(), stdout) - fmt.Fprintln(cli.Err(), stderr) return nil }, } @@ -97,7 +93,7 @@ func newDeploymentConfig(cli Cli) *cobra.Command { Args: cobra.ArbitraryArgs, DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { - stdout, stderr, err := cli.Exec("docker compose convert", args) + stdout, stderr, err := cli.Exec("docker compose convert", args, false) if err != nil { return err } @@ -116,7 +112,7 @@ func newDeploymentInfo(cli Cli) *cobra.Command { Args: cobra.ArbitraryArgs, DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { - stdout, stderr, err := cli.Exec("docker compose ps", args) + stdout, stderr, err := cli.Exec("docker compose ps", args, false) if err != nil { return err } @@ -135,7 +131,7 @@ func newDeploymentImages(cli Cli) *cobra.Command { Args: cobra.ArbitraryArgs, DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { - stdout, stderr, err := cli.Exec("docker compose images", args) + stdout, stderr, err := cli.Exec("docker compose images", args, false) if err != nil { return err } @@ -154,7 +150,7 @@ func newDeploymentServices(cli Cli) *cobra.Command { Args: cobra.ArbitraryArgs, DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { - stdout, stderr, err := cli.Exec("docker compose ps --services", args) + stdout, stderr, err := cli.Exec("docker compose ps --services", args, false) if err != nil { return err } diff --git a/cmd/swctl/app/cmd_status.go b/cmd/swctl/app/cmd_status.go index a5f50c7..0582b35 100644 --- a/cmd/swctl/app/cmd_status.go +++ b/cmd/swctl/app/cmd_status.go @@ -87,7 +87,7 @@ func runStatusCmd(cli Cli, opts StatusOptions) error { if sn, ok := compo.GetMetadata()["containerServiceName"]; ok { cmd := fmt.Sprintf("vpp-probe --env=%s --query label=%s=%s discover", defaultVppProbeEnv, compose.ServiceLabel, sn) formatArg := fmt.Sprintf("--format=%s", opts.Format) - stdout, stderr, err := cli.Exec(cmd, []string{formatArg}) + stdout, stderr, err := cli.Exec(cmd, []string{formatArg}, false) if err != nil { if ee, ok := err.(*exec.ExitError); ok { logrus.Tracef("vpp-probe discover failed for service %s with error: %v: %s", sn, ee.String(), ee.Stderr) diff --git a/cmd/swctl/app/cmd_support.go b/cmd/swctl/app/cmd_support.go index 196720b..f21f22d 100644 --- a/cmd/swctl/app/cmd_support.go +++ b/cmd/swctl/app/cmd_support.go @@ -220,7 +220,7 @@ func writeInterfaces(cli Cli, w io.Writer, components []client.Component, otherA for _, compo := range components { if sn, ok := compo.GetMetadata()["containerServiceName"]; ok { cmd := fmt.Sprintf("vpp-probe --color never --env=%s --query label=%s=%s discover", defaultVppProbeEnv, compose.ServiceLabel, sn) - stdout, _, err := cli.Exec(cmd, []string{}) + stdout, _, err := cli.Exec(cmd, []string{}, false) if err != nil { if ee, ok := err.(*exec.ExitError); ok { logrus.Tracef("vpp-probe discover failed for service %s with error: %v: %s", sn, ee.String(), ee.Stderr) @@ -258,7 +258,7 @@ func writeStatusAsJson(cli Cli, w io.Writer, components []client.Component, othe func writeDockerComposeConfig(cli Cli, w io.Writer, components []client.Component, otherArgs ...interface{}) error { cmd := "docker compose config" - stdout, stderr, err := cli.Exec(cmd, []string{}) + stdout, stderr, err := cli.Exec(cmd, []string{}, false) if err != nil { return err } @@ -271,7 +271,7 @@ func writeDockerComposeConfig(cli Cli, w io.Writer, components []client.Componen func writeDockerContainers(cli Cli, w io.Writer, components []client.Component, otherArgs ...interface{}) error { cmd := "docker compose ps --all" - stdout, stderr, err := cli.Exec(cmd, []string{}) + stdout, stderr, err := cli.Exec(cmd, []string{}, false) if err != nil { return err } @@ -284,7 +284,7 @@ func writeDockerContainers(cli Cli, w io.Writer, components []client.Component, func writeDockerInspect(cli Cli, w io.Writer, components []client.Component, otherArgs ...interface{}) error { cmd := fmt.Sprintf("docker inspect %s", fmt.Sprintf("%s", otherArgs[0])) - stdout, _, err := cli.Exec(cmd, []string{}) + stdout, _, err := cli.Exec(cmd, []string{}, false) if err != nil { return err } @@ -305,7 +305,7 @@ func writeAgentCtlInfo(cli Cli, w io.Writer, components []client.Component, args cmd := fmt.Sprintf("agentctl --host %s --http-port %d --grpc-port=%d report -i -o %s", host, httpPort, grpcPort, tempDirName) - _, _, err = cli.Exec(cmd, []string{}) + _, _, err = cli.Exec(cmd, []string{}, false) if err != nil { return err } @@ -341,7 +341,7 @@ func writeAgentCtlInfo(cli Cli, w io.Writer, components []client.Component, args func writeDockerLogs(cli Cli, w io.Writer, components []client.Component, args ...interface{}) error { serviceName := args[0] cmd := fmt.Sprintf("docker compose logs --no-color -n 10000 %s", serviceName) - stdout, stderr, err := cli.Exec(cmd, []string{}) + stdout, stderr, err := cli.Exec(cmd, []string{}, false) if err != nil { return err } diff --git a/cmd/swctl/app/cmd_trace.go b/cmd/swctl/app/cmd_trace.go index a321a48..431b9c9 100644 --- a/cmd/swctl/app/cmd_trace.go +++ b/cmd/swctl/app/cmd_trace.go @@ -33,7 +33,7 @@ func runTraceCmd(cli Cli, opts TraceCmdOptions) error { // - consider selecting IP and source network namespace automatically // - consider allowing users to simply select component names for ping src/dst - stdout, stderr, err := cli.Exec(fmt.Sprintf("vpp-probe --env=%s trace", defaultVppProbeEnv), args) + stdout, stderr, err := cli.Exec(fmt.Sprintf("vpp-probe --env=%s trace", defaultVppProbeEnv), args, false) if err != nil { return err } diff --git a/cmd/swctl/app/exec.go b/cmd/swctl/app/exec.go index add5b73..64d3566 100644 --- a/cmd/swctl/app/exec.go +++ b/cmd/swctl/app/exec.go @@ -3,6 +3,7 @@ package app import ( "bytes" "fmt" + "io" "os/exec" "strings" "syscall" @@ -106,12 +107,20 @@ type ExecResult struct { Stderr string } -func (ec *externalCmd) exec() (*ExecResult, error) { +func (ec *externalCmd) exec(liveOutput bool) (*ExecResult, error) { var stdout, stderr bytes.Buffer cmd := exec.Command(ec.name, ec.args...) + if liveOutput { + stdoutMultiWriter := io.MultiWriter(&stdout, ec.cli.out) + stderrMultiWriter := io.MultiWriter(&stderr, ec.cli.err) + cmd.Stdout = stdoutMultiWriter + cmd.Stderr = stderrMultiWriter + } else { + cmd.Stdout = &stdout + cmd.Stderr = &stderr + } + cmd.Env = ec.env - cmd.Stdout = &stdout - cmd.Stderr = &stderr now := time.Now() logrus.Tracef("[%s] %q", color.Gray.Sprint("EXEC"), cmd.String())