From 1118d28556511a58a7b1ae348f16c06141c60a2f Mon Sep 17 00:00:00 2001 From: Adam Ross Date: Tue, 31 Oct 2017 11:44:06 -0700 Subject: [PATCH 1/6] Display commands executed by StreamCommand and rig dashboard. --- commands/dashboard.go | 8 ++++---- util/shell_exec.go | 29 ++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/commands/dashboard.go b/commands/dashboard.go index b85cddb..bddd598 100644 --- a/commands/dashboard.go +++ b/commands/dashboard.go @@ -74,9 +74,9 @@ func (cmd *Dashboard) LaunchDashboard(machine Machine) error { util.ForceStreamCommand(exec.Command("docker", args...)) if util.IsMac() { - exec.Command("open", "http://dashboard.outrigger.vm").Run() + util.Run(exec.Command("open", "http://dashboard.outrigger.vm")) } else if util.IsWindows() { - exec.Command("start", "http://dashboard.outrigger.vm").Run() + util.Run(exec.Command("start", "http://dashboard.outrigger.vm")) } else { cmd.out.Info.Println("Outrigger Dashboard is now available at http://dashboard.outrigger.vm") } @@ -86,6 +86,6 @@ func (cmd *Dashboard) LaunchDashboard(machine Machine) error { // StopDashboard stops and removes the dashboard container func (cmd *Dashboard) StopDashboard() { - exec.Command("docker", "stop", dashboardContainerName).Run() - exec.Command("docker", "rm", dashboardContainerName).Run() + util.Run(exec.Command("docker", "stop", dashboardContainerName)) + util.Run(exec.Command("docker", "rm", dashboardContainerName)) } diff --git a/util/shell_exec.go b/util/shell_exec.go index 05f9f5b..9fa3891 100644 --- a/util/shell_exec.go +++ b/util/shell_exec.go @@ -2,8 +2,10 @@ package util import ( "io/ioutil" + "fmt" "os" "os/exec" + "strings" "syscall" "github.com/fatih/color" @@ -31,11 +33,18 @@ func RunCommand(cmd *exec.Cmd, forceOutput bool) error { } color.Set(color.FgCyan) - err := cmd.Run() + + err := Run(cmd) color.Unset() return err } +// Run provides a wrapper to os/exec.Run() that verbose logs the executed command invocation. +func Run(cmd *exec.Cmd) error { + Logger().Verbose.Printf("Executing: %s", CmdToString(cmd)) + return cmd.Run() +} + // PassthruCommand is similar to ForceStreamCommand in that it will issue all output // regardless of verbose mode. Further, this version of the command captures the // exit status of any executed command. This function is intended to simulate @@ -69,3 +78,21 @@ func PassthruCommand(cmd *exec.Cmd) (exitCode int) { return } + +// CmdToString converts a Command to a human-readable string with key context details. +func CmdToString(cmd *exec.Cmd) string { + context := "" + if cmd.Dir != "" { + context = fmt.Sprintf("(WD: %s", cmd.Dir) + } + if cmd.Env != nil { + env := strings.Join(cmd.Env, " ") + if context == "" { + context = fmt.Sprintf("(Env: %s", env) + } else { + context = fmt.Sprintf("%s, Env: %s)", context, env) + } + } + + return fmt.Sprintf("%s %s %s", cmd.Path, strings.Join(cmd.Args[1:], " "), context) +} From fdd19c27bbb62b3583571bbb0f849011116c45da Mon Sep 17 00:00:00 2001 From: Adam Ross Date: Thu, 9 Nov 2017 17:12:33 -0800 Subject: [PATCH 2/6] Add executor: command wrapper. --- commands/dashboard.go | 8 ++--- util/shell_exec.go | 70 ++++++++++++++++++++++++++----------------- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/commands/dashboard.go b/commands/dashboard.go index bddd598..d0db9ba 100644 --- a/commands/dashboard.go +++ b/commands/dashboard.go @@ -74,9 +74,9 @@ func (cmd *Dashboard) LaunchDashboard(machine Machine) error { util.ForceStreamCommand(exec.Command("docker", args...)) if util.IsMac() { - util.Run(exec.Command("open", "http://dashboard.outrigger.vm")) + util.Command("open", "http://dashboard.outrigger.vm").Run() } else if util.IsWindows() { - util.Run(exec.Command("start", "http://dashboard.outrigger.vm")) + util.Command("start", "http://dashboard.outrigger.vm").Run() } else { cmd.out.Info.Println("Outrigger Dashboard is now available at http://dashboard.outrigger.vm") } @@ -86,6 +86,6 @@ func (cmd *Dashboard) LaunchDashboard(machine Machine) error { // StopDashboard stops and removes the dashboard container func (cmd *Dashboard) StopDashboard() { - util.Run(exec.Command("docker", "stop", dashboardContainerName)) - util.Run(exec.Command("docker", "rm", dashboardContainerName)) + util.Command("docker", "stop", dashboardContainerName).Run() + util.Command("docker", "rm", dashboardContainerName).Run() } diff --git a/util/shell_exec.go b/util/shell_exec.go index 9fa3891..3ae996a 100644 --- a/util/shell_exec.go +++ b/util/shell_exec.go @@ -13,36 +13,22 @@ import ( const defaultFailedCode = 1 +type Executor struct { + cmd *exec.Cmd +} + // StreamCommand sets up the output streams (and colors) to stream command output if verbose is configured func StreamCommand(cmd *exec.Cmd) error { - return RunCommand(cmd, false) + return Executor{cmd}.Execute(false) } // ForceStreamCommand sets up the output streams (and colors) to stream command output regardless of verbosity func ForceStreamCommand(cmd *exec.Cmd) error { - return RunCommand(cmd, true) + return Executor{cmd}.Execute(true) } -// RunCommand executes the provided command, it also can sspecify if the output should be forced to print to the console -func RunCommand(cmd *exec.Cmd, forceOutput bool) error { - cmd.Stderr = os.Stderr - if Logger().IsVerbose || forceOutput { - cmd.Stdout = os.Stdout - } else { - cmd.Stdout = ioutil.Discard - } - - color.Set(color.FgCyan) - - err := Run(cmd) - color.Unset() - return err -} - -// Run provides a wrapper to os/exec.Run() that verbose logs the executed command invocation. -func Run(cmd *exec.Cmd) error { - Logger().Verbose.Printf("Executing: %s", CmdToString(cmd)) - return cmd.Run() +func Command(path string, arg ...string) Executor { + return Executor{exec.Command(path, arg...)} } // PassthruCommand is similar to ForceStreamCommand in that it will issue all output @@ -79,14 +65,42 @@ func PassthruCommand(cmd *exec.Cmd) (exitCode int) { return } +// RunCommand executes the provided command, it also can sspecify if the output should be forced to print to the console +func (x Executor) Execute(forceOutput bool) error { + x.cmd.Stderr = os.Stderr + if Logger().IsVerbose || forceOutput { + x.cmd.Stdout = os.Stdout + } else { + x.cmd.Stdout = ioutil.Discard + } + + x.Log("Executing") + color.Set(color.FgCyan) + err := x.Run() + color.Unset() + return err +} + +// Run runs a command via exec.Run() without modification or output of the underlying command. +func (x Executor) Run() error { + return x.cmd.Run() +} + +// Log verbosely logs the command. +func (x Executor) Log(tag string) { + color.Set(color.FgYellow) + Logger().Verbose.Printf("%s: %s", tag, x.ToString()) + color.Unset() +} + // CmdToString converts a Command to a human-readable string with key context details. -func CmdToString(cmd *exec.Cmd) string { +func (x Executor) ToString() string { context := "" - if cmd.Dir != "" { - context = fmt.Sprintf("(WD: %s", cmd.Dir) + if x.cmd.Dir != "" { + context = fmt.Sprintf("(WD: %s", x.cmd.Dir) } - if cmd.Env != nil { - env := strings.Join(cmd.Env, " ") + if x.cmd.Env != nil { + env := strings.Join(x.cmd.Env, " ") if context == "" { context = fmt.Sprintf("(Env: %s", env) } else { @@ -94,5 +108,5 @@ func CmdToString(cmd *exec.Cmd) string { } } - return fmt.Sprintf("%s %s %s", cmd.Path, strings.Join(cmd.Args[1:], " "), context) + return fmt.Sprintf("%s %s %s", x.cmd.Path, strings.Join(x.cmd.Args[1:], " "), context) } From 0364570f5b87b071ddee725458d362f331a80975 Mon Sep 17 00:00:00 2001 From: Adam Ross Date: Thu, 9 Nov 2017 21:05:16 -0800 Subject: [PATCH 3/6] Simplify calling signature of *StreamCommand --- commands/dashboard.go | 5 ++--- commands/dns.go | 24 ++++++++++++------------ commands/kill.go | 5 ++--- commands/machine.go | 16 ++++++++-------- commands/project_create.go | 2 +- commands/start.go | 4 ++-- commands/status.go | 2 +- util/shell_exec.go | 11 ++++++----- 8 files changed, 34 insertions(+), 35 deletions(-) diff --git a/commands/dashboard.go b/commands/dashboard.go index d0db9ba..0c75e37 100644 --- a/commands/dashboard.go +++ b/commands/dashboard.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "os/exec" "github.com/phase2/rig/util" "github.com/urfave/cli" @@ -54,7 +53,7 @@ func (cmd *Dashboard) LaunchDashboard(machine Machine) error { } cmd.out.Verbose.Printf("Attempting to update %s", dashboardImageName) - if err := util.StreamCommand(exec.Command("docker", "pull", dashboardImageName)); err != nil { + if err := util.StreamCommand("docker", "pull", dashboardImageName); err != nil { cmd.out.Verbose.Println("Failed to update dashboard image. Will use local cache if available.") } @@ -71,7 +70,7 @@ func (cmd *Dashboard) LaunchDashboard(machine Machine) error { dashboardImageName, } - util.ForceStreamCommand(exec.Command("docker", args...)) + util.ForceStreamCommand("docker", args...) if util.IsMac() { util.Command("open", "http://dashboard.outrigger.vm").Run() diff --git a/commands/dns.go b/commands/dns.go index f780451..82bf635 100644 --- a/commands/dns.go +++ b/commands/dns.go @@ -74,16 +74,16 @@ func (cmd *DNS) configureMacRoutes(machine Machine) { cmd.removeHostFilter(machineIP) } exec.Command("sudo", "route", "-n", "delete", "-net", "172.17.0.0").Run() - util.StreamCommand(exec.Command("sudo", "route", "-n", "add", "172.17.0.0/16", machineIP)) + util.StreamCommand("sudo", "route", "-n", "add", "172.17.0.0/16", machineIP) if _, err := os.Stat("/usr/sbin/discoveryutil"); err == nil { // Put this here for people running OS X 10.10.0 to 10.10.3 (oy vey.) cmd.out.Verbose.Println("Restarting discoveryutil to flush DNS caches") - util.StreamCommand(exec.Command("sudo", "launchctl", "unload", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist")) - util.StreamCommand(exec.Command("sudo", "launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist")) + util.StreamCommand("sudo", "launchctl", "unload", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist") + util.StreamCommand("sudo", "launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist") } else { // Reset DNS cache. We have seen this suddenly make /etc/resolver/vm work. cmd.out.Verbose.Println("Restarting mDNSResponder to flush DNS caches") - util.StreamCommand(exec.Command("sudo", "killall", "-HUP", "mDNSResponder")) + util.StreamCommand("sudo", "killall", "-HUP", "mDNSResponder") } } @@ -108,13 +108,13 @@ func (cmd *DNS) removeHostFilter(ipAddr string) { member := memberRegexp.FindStringSubmatch(string(ifaceData))[1] // #4: ifconfig -hostfilter - util.StreamCommand(exec.Command("sudo", "ifconfig", iface, "-hostfilter", member)) + util.StreamCommand("sudo", "ifconfig", iface, "-hostfilter", member) } // ConfigureWindowsRoutes configures network routing func (cmd *DNS) configureWindowsRoutes(machine Machine) { exec.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.0.0").Run() - util.StreamCommand(exec.Command("runas", "/noprofile", "/user:Administrator", "route", "-p", "ADD", "172.17.0.0/16", machine.GetIP())) + util.StreamCommand("runas", "/noprofile", "/user:Administrator", "route", "-p", "ADD", "172.17.0.0/16", machine.GetIP()) } // StartDNS will start the dnsdock service @@ -149,7 +149,7 @@ func (cmd *DNS) StartDNS(machine Machine, nameservers string) error { for _, server := range dnsServers { args = append(args, "--nameserver="+server) } - util.ForceStreamCommand(exec.Command("docker", args...)) + util.ForceStreamCommand("docker", args...) // Configure the resolvers based on platform var resolverReturn error @@ -177,12 +177,12 @@ func (cmd *DNS) configureMacResolver(machine Machine) error { if _, err := os.Stat("/usr/sbin/discoveryutil"); err == nil { // Put this here for people running OS X 10.10.0 to 10.10.3 (oy vey.) cmd.out.Verbose.Println("Restarting discoveryutil to flush DNS caches") - util.StreamCommand(exec.Command("sudo", "launchctl", "unload", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist")) - util.StreamCommand(exec.Command("sudo", "launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist")) + util.StreamCommand("sudo", "launchctl", "unload", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist") + util.StreamCommand("sudo", "launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.discoveryd.plist") } else { // Reset DNS cache. We have seen this suddenly make /etc/resolver/vm work. cmd.out.Verbose.Println("Restarting mDNSResponder to flush DNS caches") - util.StreamCommand(exec.Command("sudo", "killall", "-HUP", "mDNSResponder")) + util.StreamCommand("sudo", "killall", "-HUP", "mDNSResponder") } return nil } @@ -198,11 +198,11 @@ func (cmd *DNS) configureLinuxResolver() error { // Is NetworkManager in use if _, err := os.Stat("/etc/NetworkManager/dnsmasq.d"); err == nil { // Install for NetworkManager/dnsmasq connection to dnsdock - util.StreamCommand(exec.Command("bash", "-c", fmt.Sprintf("echo 'server=/vm/%s' | sudo tee /etc/NetworkManager/dnsmasq.d/dnsdock.conf", bridgeIP))) + util.StreamCommand("bash", "-c", fmt.Sprintf("echo 'server=/vm/%s' | sudo tee /etc/NetworkManager/dnsmasq.d/dnsdock.conf", bridgeIP)) // Restart NetworkManager if it is running if err := exec.Command("systemctl", "is-active", "NetworkManager").Run(); err != nil { - util.StreamCommand(exec.Command("sudo", "systemctl", "restart", "NetworkManager")) + util.StreamCommand("sudo", "systemctl", "restart", "NetworkManager") } } diff --git a/commands/kill.go b/commands/kill.go index 533690e..95f50f2 100644 --- a/commands/kill.go +++ b/commands/kill.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "os/exec" "github.com/phase2/rig/util" "github.com/urfave/cli" @@ -42,13 +41,13 @@ func (cmd *Kill) Run(c *cli.Context) error { } cmd.out.Info.Printf("Killing machine '%s'", cmd.machine.Name) - util.StreamCommand(exec.Command("docker-machine", "kill", cmd.machine.Name)) + util.StreamCommand("docker-machine", "kill", cmd.machine.Name) // Ensure the underlying virtualization has stopped driver := cmd.machine.GetDriver() switch driver { case util.VirtualBox: - util.StreamCommand(exec.Command("controlvm", cmd.machine.Name, "poweroff")) + util.StreamCommand("controlvm", cmd.machine.Name, "poweroff") case util.VMWare: cmd.out.Warning.Println("Add vmrun suspend command.") case util.Xhyve: diff --git a/commands/machine.go b/commands/machine.go index 3fed3f4..173f2bc 100644 --- a/commands/machine.go +++ b/commands/machine.go @@ -27,11 +27,11 @@ func (m *Machine) Create(driver string, cpuCount string, memSize string, diskSiz boot2dockerURL := "https://github.com/boot2docker/boot2docker/releases/download/v" + util.GetRawCurrentDockerVersion() + "/boot2docker.iso" - var create *exec.Cmd + var create util.Executor switch driver { case util.VirtualBox: - create = exec.Command( + create = util.Command( "docker-machine", "create", m.Name, "--driver=virtualbox", @@ -43,7 +43,7 @@ func (m *Machine) Create(driver string, cpuCount string, memSize string, diskSiz "--engine-opt", "dns=172.17.0.1", ) case util.VMWare: - create = exec.Command( + create = util.Command( "docker-machine", "create", m.Name, "--driver=vmwarefusion", @@ -57,7 +57,7 @@ func (m *Machine) Create(driver string, cpuCount string, memSize string, diskSiz if err := m.CheckXhyveRequirements(); err != nil { return err } - create = exec.Command( + create = util.Command( "docker-machine", "create", m.Name, "--driver=xhyve", @@ -69,7 +69,7 @@ func (m *Machine) Create(driver string, cpuCount string, memSize string, diskSiz ) } - if err := util.StreamCommand(create); err != nil { + if err := create.Execute(false); err != nil { return fmt.Errorf("error creating machine '%s': %s", m.Name, err) } @@ -97,7 +97,7 @@ func (m Machine) Start() error { if !m.IsRunning() { m.out.Verbose.Printf("The machine '%s' is not running, starting...", m.Name) - if err := util.StreamCommand(exec.Command("docker-machine", "start", m.Name)); err != nil { + if err := util.StreamCommand("docker-machine", "start", m.Name); err != nil { return fmt.Errorf("error starting machine '%s': %s", m.Name, err) } @@ -110,14 +110,14 @@ func (m Machine) Start() error { // Stop halts the Docker Machine func (m Machine) Stop() error { if m.IsRunning() { - return util.StreamCommand(exec.Command("docker-machine", "stop", m.Name)) + return util.StreamCommand("docker-machine", "stop", m.Name) } return nil } // Remove deleted the Docker Machine func (m Machine) Remove() error { - return util.StreamCommand(exec.Command("docker-machine", "rm", "-y", m.Name)) + return util.StreamCommand("docker-machine", "rm", "-y", m.Name) } // WaitForDev will wait a period of time for communication with the docker daemon to be established diff --git a/commands/project_create.go b/commands/project_create.go index 3a59e6d..1939488 100644 --- a/commands/project_create.go +++ b/commands/project_create.go @@ -80,7 +80,7 @@ func (cmd *ProjectCreate) RunGenerator(ctx *cli.Context, machine Machine, image // or that docker operations failed and things will likely go wrong anyway. if err == nil && !ctx.Bool("no-update") { cmd.out.Verbose.Printf("Attempting to update %s", image) - if e := util.StreamCommand(exec.Command("docker", "pull", image)); e != nil { + if e := util.StreamCommand("docker", "pull", image); e != nil { cmd.out.Verbose.Println("Failed to update generator image. Will use local cache if available.") } } else if err == nil && ctx.Bool("no-update") { diff --git a/commands/start.go b/commands/start.go index b5bc6a4..0094972 100644 --- a/commands/start.go +++ b/commands/start.go @@ -98,7 +98,7 @@ func (cmd *Start) Run(c *cli.Context) error { // NFS mounts are Mac-only. if util.IsMac() { cmd.out.Verbose.Println("Enabling NFS file sharing") - if nfsErr := util.StreamCommand(exec.Command("docker-machine-nfs", cmd.machine.Name)); nfsErr != nil { + if nfsErr := util.StreamCommand("docker-machine-nfs", cmd.machine.Name); nfsErr != nil { cmd.out.Error.Printf("Error enabling NFS: %s", nfsErr) } cmd.out.Verbose.Println("NFS is ready to use") @@ -124,7 +124,7 @@ func (cmd *Start) Run(c *cli.Context) error { then echo '===> Creating symlink from /data to /mnt/sda1/data'; sudo ln -s /mnt/sda1/data /data; fi;` - if err := util.StreamCommand(exec.Command("docker-machine", "ssh", cmd.machine.Name, dataMountSetup)); err != nil { + if err := util.StreamCommand("docker-machine", "ssh", cmd.machine.Name, dataMountSetup); err != nil { return cmd.Error(err.Error(), "DATA-MOUNT-FAILED", 13) } diff --git a/commands/status.go b/commands/status.go index d8bd76c..e316c34 100644 --- a/commands/status.go +++ b/commands/status.go @@ -37,7 +37,7 @@ func (cmd *Status) Run(c *cli.Context) error { } if cmd.out.IsVerbose { - util.StreamCommand(exec.Command("docker-machine", "ls", "--filter", "name="+cmd.machine.Name)) + util.StreamCommand("docker-machine", "ls", "--filter", "name="+cmd.machine.Name) } else { output, _ := exec.Command("docker-machine", "status", cmd.machine.Name).CombinedOutput() os.Stdout.Write(output) diff --git a/util/shell_exec.go b/util/shell_exec.go index 3ae996a..2bd02ca 100644 --- a/util/shell_exec.go +++ b/util/shell_exec.go @@ -18,15 +18,16 @@ type Executor struct { } // StreamCommand sets up the output streams (and colors) to stream command output if verbose is configured -func StreamCommand(cmd *exec.Cmd) error { - return Executor{cmd}.Execute(false) +func StreamCommand(path string, arg ...string) error { + return Command(path, arg...).Execute(false) } // ForceStreamCommand sets up the output streams (and colors) to stream command output regardless of verbosity -func ForceStreamCommand(cmd *exec.Cmd) error { - return Executor{cmd}.Execute(true) +func ForceStreamCommand(path string, arg ...string) error { + return Command(path, arg...).Execute(true) } +// Command creates a new Executor instance from the execution arguments. func Command(path string, arg ...string) Executor { return Executor{exec.Command(path, arg...)} } @@ -93,7 +94,7 @@ func (x Executor) Log(tag string) { color.Unset() } -// CmdToString converts a Command to a human-readable string with key context details. +// ToString converts a Command to a human-readable string with key context details. func (x Executor) ToString() string { context := "" if x.cmd.Dir != "" { From 81c8e3cf5d84d6eb1a37812014f041b6072eb026 Mon Sep 17 00:00:00 2001 From: Adam Ross Date: Thu, 9 Nov 2017 22:32:48 -0800 Subject: [PATCH 4/6] Continue re-wrapping exec commands. --- commands/config.go | 5 ++--- commands/data_backup.go | 2 +- commands/dns-records.go | 5 +++-- commands/dns.go | 21 ++++++++++----------- commands/doctor.go | 8 ++++---- commands/machine.go | 19 +++++++++---------- commands/project_sync.go | 16 ++++++++-------- commands/start.go | 3 +-- commands/status.go | 3 +-- commands/stop.go | 9 ++++----- util/docker.go | 13 ++++++------- util/shell_exec.go | 14 +++++++++++++- 12 files changed, 62 insertions(+), 56 deletions(-) diff --git a/commands/config.go b/commands/config.go index becdbbd..3fc6f25 100644 --- a/commands/config.go +++ b/commands/config.go @@ -3,7 +3,6 @@ package commands import ( "fmt" "os" - "os/exec" "strings" "github.com/phase2/rig/util" @@ -44,13 +43,13 @@ func (cmd *Config) Run(c *cli.Context) error { } // Clear out any previous environment variables - if output, err := exec.Command("docker-machine", "env", "-u").Output(); err == nil { + if output, err := util.Command("docker-machine", "env", "-u").Output(); err == nil { os.Stdout.Write(output) } if cmd.machine.Exists() { // Setup new values if machine is running - if output, err := exec.Command("docker-machine", "env", cmd.machine.Name).Output(); err == nil { + if output, err := util.Command("docker-machine", "env", cmd.machine.Name).Output(); err == nil { os.Stdout.Write(output) } } else { diff --git a/commands/data_backup.go b/commands/data_backup.go index 2568fce..057ae53 100644 --- a/commands/data_backup.go +++ b/commands/data_backup.go @@ -54,7 +54,7 @@ func (cmd *DataBackup) Run(c *cli.Context) error { backupFile := fmt.Sprintf("%s%c%s.tgz", backupDir, os.PathSeparator, cmd.machine.Name) if _, err := os.Stat(backupDir); err != nil { cmd.out.Info.Printf("Creating backup directory: %s...", backupDir) - if mkdirErr := exec.Command("mkdir", "-p", backupDir).Run(); mkdirErr != nil { + if mkdirErr := util.Command("mkdir", "-p", backupDir).Run(); mkdirErr != nil { cmd.out.Error.Println(mkdirErr) return cmd.Error(fmt.Sprintf("Could not create backup directory %s", backupDir), "BACKUP-DIR-CREATE-FAILED", 12) } diff --git a/commands/dns-records.go b/commands/dns-records.go index 4050944..9796466 100644 --- a/commands/dns-records.go +++ b/commands/dns-records.go @@ -4,11 +4,12 @@ import ( "fmt" "io/ioutil" "net/http" - "os/exec" "strings" "github.com/bitly/go-simplejson" "github.com/urfave/cli" + + "github.com/phase2/rig/util" ) // DNSRecords is the command for exporting all DNS Records in Outrigger DNS in `hosts` file format @@ -51,7 +52,7 @@ func (cmd *DNSRecords) Run(c *cli.Context) error { // LoadRecords retrieves the records from DNSDock and processes/return them func (cmd *DNSRecords) LoadRecords() ([]map[string]interface{}, error) { - ip, err := exec.Command("docker", "inspect", "--format", "{{.NetworkSettings.IPAddress}}", "dnsdock").Output() + ip, err := util.Command("docker", "inspect", "--format", "{{.NetworkSettings.IPAddress}}", "dnsdock").Output() if err != nil { return nil, fmt.Errorf("failed to discover dnsdock IP address: %s", err) } diff --git a/commands/dns.go b/commands/dns.go index 82bf635..493d6f9 100644 --- a/commands/dns.go +++ b/commands/dns.go @@ -3,7 +3,6 @@ package commands import ( "fmt" "os" - "os/exec" "regexp" "strings" @@ -73,7 +72,7 @@ func (cmd *DNS) configureMacRoutes(machine Machine) { if machine.IsXhyve() { cmd.removeHostFilter(machineIP) } - exec.Command("sudo", "route", "-n", "delete", "-net", "172.17.0.0").Run() + util.Command("sudo", "route", "-n", "delete", "-net", "172.17.0.0").Run() util.StreamCommand("sudo", "route", "-n", "add", "172.17.0.0/16", machineIP) if _, err := os.Stat("/usr/sbin/discoveryutil"); err == nil { // Put this here for people running OS X 10.10.0 to 10.10.3 (oy vey.) @@ -90,7 +89,7 @@ func (cmd *DNS) configureMacRoutes(machine Machine) { // removeHostFilter removes the host filter from the xhyve bridge interface func (cmd *DNS) removeHostFilter(ipAddr string) { // #1: route -n get to find the interface name - routeData, err := exec.Command("route", "-n", "get", ipAddr).CombinedOutput() + routeData, err := util.Command("route", "-n", "get", ipAddr).CombinedOutput() if err != nil { cmd.out.Warning.Println("Unable to determine bridge interface to remove hostfilter") return @@ -99,7 +98,7 @@ func (cmd *DNS) removeHostFilter(ipAddr string) { iface := ifaceRegexp.FindStringSubmatch(string(routeData))[1] // #2: ifconfig to get the details - ifaceData, err := exec.Command("ifconfig", iface).CombinedOutput() + ifaceData, err := util.Command("ifconfig", iface).CombinedOutput() if err != nil { cmd.out.Warning.Println("Unable to determine member to remove hostfilter") return @@ -113,7 +112,7 @@ func (cmd *DNS) removeHostFilter(ipAddr string) { // ConfigureWindowsRoutes configures network routing func (cmd *DNS) configureWindowsRoutes(machine Machine) { - exec.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.0.0").Run() + util.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.0.0").Run() util.StreamCommand("runas", "/noprofile", "/user:Administrator", "route", "-p", "ADD", "172.17.0.0/16", machine.GetIP()) } @@ -168,10 +167,10 @@ func (cmd *DNS) configureMacResolver(machine Machine) error { cmd.out.Verbose.Print("Configuring DNS resolution for macOS") bridgeIP := machine.GetBridgeIP() - if err := exec.Command("sudo", "mkdir", "-p", "/etc/resolver").Run(); err != nil { + if err := util.Command("sudo", "mkdir", "-p", "/etc/resolver").Run(); err != nil { return err } - if err := exec.Command("bash", "-c", fmt.Sprintf("echo 'nameserver %s' | sudo tee /etc/resolver/vm", bridgeIP)).Run(); err != nil { + if err := util.Command("bash", "-c", fmt.Sprintf("echo 'nameserver %s' | sudo tee /etc/resolver/vm", bridgeIP)).Run(); err != nil { return err } if _, err := os.Stat("/usr/sbin/discoveryutil"); err == nil { @@ -201,7 +200,7 @@ func (cmd *DNS) configureLinuxResolver() error { util.StreamCommand("bash", "-c", fmt.Sprintf("echo 'server=/vm/%s' | sudo tee /etc/NetworkManager/dnsmasq.d/dnsdock.conf", bridgeIP)) // Restart NetworkManager if it is running - if err := exec.Command("systemctl", "is-active", "NetworkManager").Run(); err != nil { + if err := util.Command("systemctl", "is-active", "NetworkManager").Run(); err != nil { util.StreamCommand("sudo", "systemctl", "restart", "NetworkManager") } } @@ -209,7 +208,7 @@ func (cmd *DNS) configureLinuxResolver() error { // Is libnss-resolver in use if _, err := os.Stat("/etc/resolver"); err == nil { // Install for libnss-resolver connection to dnsdock - exec.Command("bash", "-c", fmt.Sprintf("echo 'nameserver %s:53' | sudo tee /etc/resolver/vm", bridgeIP)).Run() + util.Command("bash", "-c", fmt.Sprintf("echo 'nameserver %s:53' | sudo tee /etc/resolver/vm", bridgeIP)).Run() } return nil @@ -224,6 +223,6 @@ func (cmd *DNS) configureWindowsResolver(machine Machine) error { // StopDNS stops the dnsdock service and cleans up func (cmd *DNS) StopDNS() { - exec.Command("docker", "stop", "dnsdock").Run() - exec.Command("docker", "rm", "dnsdock").Run() + util.Command("docker", "stop", "dnsdock").Run() + util.Command("docker", "rm", "dnsdock").Run() } diff --git a/commands/doctor.go b/commands/doctor.go index 9810672..7de2288 100644 --- a/commands/doctor.go +++ b/commands/doctor.go @@ -61,7 +61,7 @@ func (cmd *Doctor) Run(c *cli.Context) error { } else { cmd.out.Info.Printf("Docker Machine (%s) name matches your environment configuration.", cmd.machine.Name) } - if output, err := exec.Command("docker-machine", "url", cmd.machine.Name).Output(); err == nil { + if output, err := util.Command("docker-machine", "url", cmd.machine.Name).Output(); err == nil { hostURL := strings.TrimSpace(string(output)) if hostURL != os.Getenv("DOCKER_HOST") { cmd.out.Error.Fatalf("Docker Host configuration should be '%s' but got '%s'. Please re-run 'eval \"$(rig config)\"'.", os.Getenv("DOCKER_HOST"), hostURL) @@ -128,7 +128,7 @@ func (cmd *Doctor) Run(c *cli.Context) error { // 4. Ensure that docker-machine-nfs script is available for our NFS mounts (Mac ONLY) if util.IsMac() { - if err := exec.Command("which", "docker-machine-nfs").Run(); err != nil { + if err := util.Command("which", "docker-machine-nfs").Run(); err != nil { cmd.out.Error.Println("Docker Machine NFS is not installed.") } else { cmd.out.Info.Println("Docker Machine NFS is installed.") @@ -137,7 +137,7 @@ func (cmd *Doctor) Run(c *cli.Context) error { // 5. Check for storage on VM volume if !util.SupportsNativeDocker() { - output, err := exec.Command("docker-machine", "ssh", cmd.machine.Name, "df -h 2> /dev/null | grep /dev/sda1 | head -1 | awk '{print $5}' | sed 's/%//'").Output() + output, err := util.Command("docker-machine", "ssh", cmd.machine.Name, "df -h 2> /dev/null | grep /dev/sda1 | head -1 | awk '{print $5}' | sed 's/%//'").Output() if err == nil { dataUsage := strings.TrimSpace(string(output)) if i, e := strconv.Atoi(dataUsage); e == nil { @@ -158,7 +158,7 @@ func (cmd *Doctor) Run(c *cli.Context) error { // 6. Check for storage on /Users if !util.SupportsNativeDocker() { - output, err := exec.Command("docker-machine", "ssh", cmd.machine.Name, "df -h 2> /dev/null | grep /Users | head -1 | awk '{print $5}' | sed 's/%//'").Output() + output, err := util.Command("docker-machine", "ssh", cmd.machine.Name, "df -h 2> /dev/null | grep /Users | head -1 | awk '{print $5}' | sed 's/%//'").Output() if err == nil { userUsage := strings.TrimSpace(string(output)) if i, e := strconv.Atoi(userUsage); e == nil { diff --git a/commands/machine.go b/commands/machine.go index 173f2bc..9d131aa 100644 --- a/commands/machine.go +++ b/commands/machine.go @@ -3,7 +3,6 @@ package commands import ( "fmt" "os" - "os/exec" "regexp" "strings" "time" @@ -80,12 +79,12 @@ func (m *Machine) Create(driver string, cpuCount string, memSize string, diskSiz // CheckXhyveRequirements verifies that the correct xhyve environment exists func (m Machine) CheckXhyveRequirements() error { // Is xhyve installed locally - if err := exec.Command("which", "xhyve").Run(); err != nil { + if err := util.Command("which", "xhyve").Run(); err != nil { return fmt.Errorf("xhyve is not installed. Install it with 'brew install xhyve'") } // Is docker-machine-driver-xhyve installed locally - if err := exec.Command("which", "docker-machine-driver-xhyve").Run(); err != nil { + if err := util.Command("which", "docker-machine-driver-xhyve").Run(); err != nil { return fmt.Errorf("docker-machine-driver-xhyve is not installed. Install it with 'brew install docker-machine-driver-xhyve'") } @@ -127,7 +126,7 @@ func (m Machine) WaitForDev() error { for i := 1; i <= maxTries; i++ { m.SetEnv() - if err := exec.Command("docker", "ps").Run(); err == nil { + if err := util.Command("docker", "ps").Run(); err == nil { m.out.Verbose.Printf("Machine '%s' has started", m.Name) return nil } @@ -162,7 +161,7 @@ func (m Machine) UnsetEnv() { // Exists determines if the Docker Machine exist func (m Machine) Exists() bool { - if err := exec.Command("docker-machine", "status", m.Name).Run(); err != nil { + if err := util.Command("docker-machine", "status", m.Name).Run(); err != nil { return false } return true @@ -170,7 +169,7 @@ func (m Machine) Exists() bool { // IsRunning returns the Docker Machine running status func (m Machine) IsRunning() bool { - if err := exec.Command("docker-machine", "env", m.Name).Run(); err != nil { + if err := util.Command("docker-machine", "env", m.Name).Run(); err != nil { return false } return true @@ -182,7 +181,7 @@ func (m *Machine) GetData() *simplejson.Json { return m.inspectData } - if inspect, inspectErr := exec.Command("docker-machine", "inspect", m.Name).Output(); inspectErr == nil { + if inspect, inspectErr := util.Command("docker-machine", "inspect", m.Name).Output(); inspectErr == nil { if js, jsonErr := simplejson.NewJson(inspect); jsonErr != nil { m.out.Error.Fatalf("Failed to parse '%s' JSON: %s", m.Name, jsonErr) } else { @@ -224,7 +223,7 @@ func (m Machine) GetBridgeIP() string { // GetDockerVersion returns the Version of Docker running within Docker Machine func (m Machine) GetDockerVersion() (*version.Version, error) { - b2dOutput, err := exec.Command("docker-machine", "version", m.Name).CombinedOutput() + b2dOutput, err := util.Command("docker-machine", "version", m.Name).CombinedOutput() if err != nil { return nil, errors.New(strings.TrimSpace(string(b2dOutput))) } @@ -264,7 +263,7 @@ func (m Machine) GetDiskInGB() int { // GetSysctl returns the configured value for the provided sysctl setting on the Docker Machine func (m Machine) GetSysctl(setting string) (string, error) { - output, err := exec.Command("docker-machine", "ssh", m.Name, "sudo", "sysctl", "-n", setting).CombinedOutput() + output, err := util.Command("docker-machine", "ssh", m.Name, "sudo", "sysctl", "-n", setting).CombinedOutput() if err != nil { return "", err } @@ -275,6 +274,6 @@ func (m Machine) GetSysctl(setting string) (string, error) { func (m Machine) SetSysctl(key string, val string) error { cmd := fmt.Sprintf("sudo sysctl -w %s=%s", key, val) m.out.Verbose.Printf("Modifying Docker Machine kernel settings: %s", cmd) - _, err := exec.Command("docker-machine", "ssh", m.Name, cmd).CombinedOutput() + _, err := util.Command("docker-machine", "ssh", m.Name, cmd).CombinedOutput() return err } diff --git a/commands/project_sync.go b/commands/project_sync.go index bb1166d..a170364 100644 --- a/commands/project_sync.go +++ b/commands/project_sync.go @@ -125,7 +125,7 @@ func (cmd *ProjectSync) StartUnisonSync(ctx *cli.Context, volumeName string, con } cmd.out.Info.Printf("Starting sync volume: %s", volumeName) - if err := exec.Command("docker", "volume", "create", volumeName).Run(); err != nil { + if err := util.Command("docker", "volume", "create", volumeName).Run(); err != nil { return cmd.Error(fmt.Sprintf("Failed to create sync volume: %s", volumeName), "VOLUME-CREATE-FAILED", 13) } @@ -133,7 +133,7 @@ func (cmd *ProjectSync) StartUnisonSync(ctx *cli.Context, volumeName string, con unisonMinorVersion := cmd.GetUnisonMinorVersion() cmd.out.Verbose.Printf("Local Unison version for compatibilty: %s", unisonMinorVersion) - exec.Command("docker", "container", "stop", volumeName).Run() + util.Command("docker", "container", "stop", volumeName).Run() containerArgs := []string{ "container", "run", "--detach", "--rm", "-v", fmt.Sprintf("%s:/unison", volumeName), @@ -143,7 +143,7 @@ func (cmd *ProjectSync) StartUnisonSync(ctx *cli.Context, volumeName string, con "--name", volumeName, fmt.Sprintf("outrigger/unison:%s", unisonMinorVersion), } - if err := exec.Command("docker", containerArgs...).Run(); err != nil { + if err := util.Command("docker", containerArgs...).Run(); err != nil { cmd.Error(fmt.Sprintf("Error starting sync container %s: %v", volumeName, err), "SYNC-CONTAINER-START-FAILED", 13) } @@ -197,7 +197,7 @@ func (cmd *ProjectSync) StartUnisonSync(ctx *cli.Context, volumeName string, con // SetupBindVolume will create minimal Docker Volumes for systems that have native container/volume support func (cmd *ProjectSync) SetupBindVolume(volumeName string, workingDir string) error { cmd.out.Info.Printf("Starting local bind volume: %s", volumeName) - exec.Command("docker", "volume", "rm", volumeName).Run() + util.Command("docker", "volume", "rm", volumeName).Run() volumeArgs := []string{ "volume", "create", @@ -207,7 +207,7 @@ func (cmd *ProjectSync) SetupBindVolume(volumeName string, workingDir string) er volumeName, } - if err := exec.Command("docker", volumeArgs...).Run(); err != nil { + if err := util.Command("docker", volumeArgs...).Run(); err != nil { return cmd.Error(err.Error(), "BIND-VOLUME-FAILURE", 13) } @@ -233,7 +233,7 @@ func (cmd *ProjectSync) RunStop(ctx *cli.Context) error { volumeName := cmd.GetVolumeName(cmd.Config, workingDir) cmd.out.Verbose.Printf("Stopping sync with volume: %s", volumeName) cmd.out.Info.Println("Stopping Unison container") - if err := exec.Command("docker", "container", "stop", volumeName).Run(); err != nil { + if err := util.Command("docker", "container", "stop", volumeName).Run(); err != nil { return cmd.Error(err.Error(), "SYNC-CONTAINER-FAILURE", 13) } @@ -288,7 +288,7 @@ func (cmd *ProjectSync) WaitForUnisonContainer(containerName string, timeoutSeco // * 10 here because we loop once every 100 ms and we want to get to seconds var timeoutLoops = timeoutSeconds * 10 - output, err := exec.Command("docker", "inspect", "--format", "{{.NetworkSettings.IPAddress}}", containerName).Output() + output, err := util.Command("docker", "inspect", "--format", "{{.NetworkSettings.IPAddress}}", containerName).Output() if err != nil { return "", fmt.Errorf("error inspecting sync container %s: %v", containerName, err) } @@ -378,7 +378,7 @@ func (cmd *ProjectSync) WaitForSyncInit(logFile string, workingDir string, timeo // GetUnisonMinorVersion will return the local Unison version to try to load a compatible unison image // This function discovers a semver like 2.48.4 and return 2.48 func (cmd *ProjectSync) GetUnisonMinorVersion() string { - output, _ := exec.Command("unison", "-version").Output() + output, _ := util.Command("unison", "-version").Output() re := regexp.MustCompile(`unison version (\d+\.\d+\.\d+)`) rawVersion := re.FindAllStringSubmatch(string(output), -1)[0][1] v := version.Must(version.NewVersion(rawVersion)) diff --git a/commands/start.go b/commands/start.go index 0094972..4083699 100644 --- a/commands/start.go +++ b/commands/start.go @@ -1,7 +1,6 @@ package commands import ( - "os/exec" "strconv" "github.com/phase2/rig/util" @@ -66,7 +65,7 @@ func (cmd *Start) Run(c *cli.Context) error { cmd.out.Verbose.Println("If something goes wrong, run 'rig doctor'") cmd.out.Verbose.Println("Pre-flight check...") - if err := exec.Command("grep", "-qE", "'^\"?/Users/'", "/etc/exports").Run(); err == nil { + if err := util.Command("grep", "-qE", "'^\"?/Users/'", "/etc/exports").Run(); err == nil { return cmd.Error("Vagrant NFS mount found. Please remove any non-Outrigger mounts that begin with /Users from your /etc/exports file", "NFS-MOUNT-CONFLICT", 12) } diff --git a/commands/status.go b/commands/status.go index e316c34..40338f9 100644 --- a/commands/status.go +++ b/commands/status.go @@ -3,7 +3,6 @@ package commands import ( "fmt" "os" - "os/exec" "github.com/phase2/rig/util" "github.com/urfave/cli" @@ -39,7 +38,7 @@ func (cmd *Status) Run(c *cli.Context) error { if cmd.out.IsVerbose { util.StreamCommand("docker-machine", "ls", "--filter", "name="+cmd.machine.Name) } else { - output, _ := exec.Command("docker-machine", "status", cmd.machine.Name).CombinedOutput() + output, _ := util.Command("docker-machine", "status", cmd.machine.Name).CombinedOutput() os.Stdout.Write(output) } diff --git a/commands/stop.go b/commands/stop.go index e01a4d5..83eabde 100644 --- a/commands/stop.go +++ b/commands/stop.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "os/exec" "github.com/fatih/color" "github.com/phase2/rig/util" @@ -60,11 +59,11 @@ func (cmd *Stop) StopOutrigger() error { cmd.out.Info.Println("Cleaning up local networking (may require your admin password)") if util.IsWindows() { - exec.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.0.0").Run() - exec.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.42.1").Run() + util.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.0.0").Run() + util.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.42.1").Run() } else { - exec.Command("sudo", "route", "-n", "delete", "-net", "172.17.0.0").Run() - exec.Command("sudo", "route", "-n", "delete", "-net", "172.17.42.1").Run() + util.Command("sudo", "route", "-n", "delete", "-net", "172.17.0.0").Run() + util.Command("sudo", "route", "-n", "delete", "-net", "172.17.42.1").Run() } color.Unset() diff --git a/util/docker.go b/util/docker.go index 5e59aa2..0cd4ee1 100644 --- a/util/docker.go +++ b/util/docker.go @@ -1,7 +1,6 @@ package util import ( - "os/exec" "regexp" "strings" "time" @@ -11,7 +10,7 @@ import ( // GetRawCurrentDockerVersion returns the entire semver string from the docker version cli func GetRawCurrentDockerVersion() string { - output, _ := exec.Command("docker", "--version").Output() + output, _ := Command("docker", "--version").Output() re := regexp.MustCompile("Docker version (.*),") return re.FindAllStringSubmatch(string(output), -1)[0][1] } @@ -24,7 +23,7 @@ func GetCurrentDockerVersion() *version.Version { // GetDockerClientAPIVersion returns a Version for the docker client API version func GetDockerClientAPIVersion() *version.Version { - output, _ := exec.Command("docker", "version", "--format", "{{.Client.APIVersion}}").Output() + output, _ := Command("docker", "version", "--format", "{{.Client.APIVersion}}").Output() re := regexp.MustCompile(`^([\d|\.]+)`) versionNumber := re.FindAllStringSubmatch(string(output), -1)[0][1] return version.Must(version.NewVersion(versionNumber)) @@ -32,7 +31,7 @@ func GetDockerClientAPIVersion() *version.Version { // GetDockerServerAPIVersion returns a Version for the docker server API version func GetDockerServerAPIVersion(name string) (*version.Version, error) { - output, err := exec.Command("docker-machine", "ssh", name, "docker version --format {{.Server.APIVersion}}").Output() + output, err := Command("docker-machine", "ssh", name, "docker version --format {{.Server.APIVersion}}").Output() if err != nil { return nil, err } @@ -41,7 +40,7 @@ func GetDockerServerAPIVersion(name string) (*version.Version, error) { // GetDockerServerMinAPIVersion returns the minimum compatability version for the docker server func GetDockerServerMinAPIVersion(name string) (*version.Version, error) { - output, err := exec.Command("docker-machine", "ssh", name, "docker version --format {{.Server.MinAPIVersion}}").Output() + output, err := Command("docker-machine", "ssh", name, "docker version --format {{.Server.MinAPIVersion}}").Output() if err != nil { return nil, err } @@ -50,7 +49,7 @@ func GetDockerServerMinAPIVersion(name string) (*version.Version, error) { // ImageOlderThan determines the age of the Docker Image and whether the image is older than the designated timestamp. func ImageOlderThan(image string, elapsedSeconds float64) (bool, float64, error) { - output, err := exec.Command("docker", "inspect", "--format", "{{.Created}}", image).Output() + output, err := Command("docker", "inspect", "--format", "{{.Created}}", image).Output() if err != nil { return false, 0, err } @@ -67,7 +66,7 @@ func ImageOlderThan(image string, elapsedSeconds float64) (bool, float64, error) // GetBridgeIP returns the IP address of the Docker bridge network gateway func GetBridgeIP() (string, error) { - output, err := exec.Command("docker", "network", "inspect", "bridge", "--format", "{{(index .IPAM.Config 0).Gateway}}").Output() + output, err := Command("docker", "network", "inspect", "bridge", "--format", "{{(index .IPAM.Config 0).Gateway}}").Output() if err != nil { return "", err } diff --git a/util/shell_exec.go b/util/shell_exec.go index 2bd02ca..02a4868 100644 --- a/util/shell_exec.go +++ b/util/shell_exec.go @@ -75,7 +75,6 @@ func (x Executor) Execute(forceOutput bool) error { x.cmd.Stdout = ioutil.Discard } - x.Log("Executing") color.Set(color.FgCyan) err := x.Run() color.Unset() @@ -84,9 +83,22 @@ func (x Executor) Execute(forceOutput bool) error { // Run runs a command via exec.Run() without modification or output of the underlying command. func (x Executor) Run() error { + x.Log("Executing") return x.cmd.Run() } +// Output runs a command via exec.Output() without modification or output of the underlying command. +func (x Executor) Output() ([]byte, error) { + x.Log("Executing") + return x.cmd.Output() +} + +// CombinedOutput runs a command via exec.CombinedOutput() without modification or output of the underlying command. +func (x Executor) CombinedOutput() ([]byte, error) { + x.Log("Executing") + return x.cmd.CombinedOutput() +} + // Log verbosely logs the command. func (x Executor) Log(tag string) { color.Set(color.FgYellow) From fc31760d87c80d37b6b42054c5c1d424a9719d5c Mon Sep 17 00:00:00 2001 From: Adam Ross Date: Fri, 10 Nov 2017 11:29:40 -0800 Subject: [PATCH 5/6] Executorize all commands. --- commands/data_backup.go | 11 +---------- commands/data_restore.go | 11 +---------- commands/doctor.go | 7 +++---- commands/project_sync.go | 2 +- util/shell_exec.go | 23 ++++++++++++++++++----- 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/commands/data_backup.go b/commands/data_backup.go index 057ae53..376feec 100644 --- a/commands/data_backup.go +++ b/commands/data_backup.go @@ -3,9 +3,7 @@ package commands import ( "fmt" "os" - "os/exec" - "github.com/fatih/color" "github.com/phase2/rig/util" "github.com/urfave/cli" ) @@ -68,14 +66,7 @@ func (cmd *DataBackup) Run(c *cli.Context) error { // Stream the archive to stdout and capture it in a local file so we don't waste // space storing an archive on the VM filesystem. There may not be enough space. archiveCmd := fmt.Sprintf("sudo tar czf - -C %s .", dataDir) - backup := exec.Command("docker-machine", "ssh", cmd.machine.Name, archiveCmd, ">", backupFile) - backup.Stderr = os.Stderr - - color.Set(color.FgCyan) - err := backup.Run() - color.Unset() - - if err != nil { + if err := util.StreamCommand("docker-machine", "ssh", cmd.machine.Name, archiveCmd, ">", backupFile); err != nil { return cmd.Error(err.Error(), "COMMAND-ERROR", 13) } diff --git a/commands/data_restore.go b/commands/data_restore.go index 33a92da..16705e4 100644 --- a/commands/data_restore.go +++ b/commands/data_restore.go @@ -3,10 +3,8 @@ package commands import ( "fmt" "os" - "os/exec" "strings" - "github.com/fatih/color" "github.com/phase2/rig/util" "github.com/urfave/cli" ) @@ -64,14 +62,7 @@ func (cmd *DataRestore) Run(c *cli.Context) error { // Send the archive via stdin and extract inline. Saves on disk & performance extractCmd := fmt.Sprintf("cat %s | docker-machine ssh %s \"sudo tar xzf - -C %s\"", backupFile, cmd.machine.Name, dataDir) cmd.out.Info.Printf(extractCmd) - backup := exec.Command("bash", "-c", extractCmd) - backup.Stderr = os.Stderr - - color.Set(color.FgCyan) - err := backup.Run() - color.Unset() - - if err != nil { + if err := util.StreamCommand("bash", "-c", extractCmd); err != nil { return cmd.Error(err.Error(), "COMMAND-ERROR", 13) } diff --git a/commands/doctor.go b/commands/doctor.go index 7de2288..790e4a2 100644 --- a/commands/doctor.go +++ b/commands/doctor.go @@ -3,7 +3,6 @@ package commands import ( "fmt" "os" - "os/exec" "strconv" "strings" @@ -33,19 +32,19 @@ func (cmd *Doctor) Commands() []cli.Command { // nolint: gocyclo func (cmd *Doctor) Run(c *cli.Context) error { // 0. Ensure all of rig's dependencies are available in the PATH. - if err := exec.Command("docker", "-h").Start(); err == nil { + if err := util.Command("docker", "-h").Start(); err == nil { cmd.out.Info.Println("Docker is installed.") } else { cmd.out.Error.Fatal("Docker (docker) is not installed.") } if !util.SupportsNativeDocker() { - if err := exec.Command("docker-machine", "-h").Start(); err == nil { + if err := util.Command("docker-machine", "-h").Start(); err == nil { cmd.out.Info.Println("Docker Machine is installed.") } else { cmd.out.Error.Fatal("Docker Machine (docker-machine) is not installed.") } } - if err := exec.Command("docker-compose", "-h").Start(); err == nil { + if err := util.Command("docker-compose", "-h").Start(); err == nil { cmd.out.Info.Println("Docker Compose is installed.") } else { cmd.out.Warning.Printf("Docker Compose (docker-compose) is not installed.") diff --git a/commands/project_sync.go b/commands/project_sync.go index a170364..8fa7f67 100644 --- a/commands/project_sync.go +++ b/commands/project_sync.go @@ -183,7 +183,7 @@ func (cmd *ProjectSync) StartUnisonSync(ctx *cli.Context, volumeName string, con command := exec.Command("unison", unisonArgs...) command.Dir = workingDir cmd.out.Verbose.Printf("Sync execution - Working Directory: %s", workingDir) - if err = command.Start(); err != nil { + if err = util.Convert(command).Start(); err != nil { return cmd.Error(fmt.Sprintf("Failure starting local Unison process: %v", err), "UNISON-START-FAILED", 13) } diff --git a/util/shell_exec.go b/util/shell_exec.go index 02a4868..779fd3a 100644 --- a/util/shell_exec.go +++ b/util/shell_exec.go @@ -13,6 +13,7 @@ import ( const defaultFailedCode = 1 +// Executor wraps exec.Cmd to allow consistent manipulation of executed commands. type Executor struct { cmd *exec.Cmd } @@ -32,6 +33,11 @@ func Command(path string, arg ...string) Executor { return Executor{exec.Command(path, arg...)} } +// Convert takes a exec.Cmd pointer and wraps it in an executor object. +func Convert(cmd *exec.Cmd) Executor { + return Executor{cmd} +} + // PassthruCommand is similar to ForceStreamCommand in that it will issue all output // regardless of verbose mode. Further, this version of the command captures the // exit status of any executed command. This function is intended to simulate @@ -43,7 +49,8 @@ func PassthruCommand(cmd *exec.Cmd) (exitCode int) { cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin - err := cmd.Run() + bin := Executor{cmd} + err := bin.Run() if err != nil { // Try to get the exit code. @@ -66,7 +73,7 @@ func PassthruCommand(cmd *exec.Cmd) (exitCode int) { return } -// RunCommand executes the provided command, it also can sspecify if the output should be forced to print to the console +// Execute executes the provided command, it also can sspecify if the output should be forced to print to the console func (x Executor) Execute(forceOutput bool) error { x.cmd.Stderr = os.Stderr if Logger().IsVerbose || forceOutput { @@ -81,6 +88,12 @@ func (x Executor) Execute(forceOutput bool) error { return err } +// CombinedOutput runs a command via exec.CombinedOutput() without modification or output of the underlying command. +func (x Executor) CombinedOutput() ([]byte, error) { + x.Log("Executing") + return x.cmd.CombinedOutput() +} + // Run runs a command via exec.Run() without modification or output of the underlying command. func (x Executor) Run() error { x.Log("Executing") @@ -93,10 +106,10 @@ func (x Executor) Output() ([]byte, error) { return x.cmd.Output() } -// CombinedOutput runs a command via exec.CombinedOutput() without modification or output of the underlying command. -func (x Executor) CombinedOutput() ([]byte, error) { +// Start runs a command via exec.Start() without modification or output of the underlying command. +func (x Executor) Start() error { x.Log("Executing") - return x.cmd.CombinedOutput() + return x.cmd.Start() } // Log verbosely logs the command. From 89334681fde649dfdb16ed573330b46683988360 Mon Sep 17 00:00:00 2001 From: Adam Ross Date: Fri, 10 Nov 2017 14:51:49 -0800 Subject: [PATCH 6/6] Correct lint standards and account for API changes in git conflict resolution. --- commands/doctor.go | 2 +- util/shell_exec.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commands/doctor.go b/commands/doctor.go index 288b02b..713827b 100644 --- a/commands/doctor.go +++ b/commands/doctor.go @@ -81,7 +81,7 @@ func (cmd *Doctor) Run(c *cli.Context) error { cmd.out.Info.Printf("Docker Machine (%s) is running", cmd.machine.Name) } } else { - if err := exec.Command("docker", "version").Run(); err != nil { + if err := util.Command("docker", "version").Run(); err != nil { cmd.out.Error.Fatalf("Docker is not running. You may need to run 'systemctl start docker'") } else { cmd.out.Info.Println("Docker is running") diff --git a/util/shell_exec.go b/util/shell_exec.go index 779fd3a..a3d0fea 100644 --- a/util/shell_exec.go +++ b/util/shell_exec.go @@ -1,8 +1,8 @@ package util import ( - "io/ioutil" "fmt" + "io/ioutil" "os" "os/exec" "strings"