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/dashboard.go b/commands/dashboard.go index c844d97..680e007 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,12 +70,12 @@ func (cmd *Dashboard) LaunchDashboard(machine Machine) error { dashboardImageName, } - util.ForceStreamCommand(exec.Command("docker", args...)) + util.ForceStreamCommand("docker", args...) if util.IsMac() { - exec.Command("open", "http://dashboard.outrigger.vm").Run() + util.Command("open", "http://dashboard.outrigger.vm").Run() } else if util.IsWindows() { - exec.Command("start", "http://dashboard.outrigger.vm").Run() + 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 +85,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.Command("docker", "stop", dashboardContainerName).Run() + util.Command("docker", "rm", dashboardContainerName).Run() } diff --git a/commands/data_backup.go b/commands/data_backup.go index 2568fce..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" ) @@ -54,7 +52,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) } @@ -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/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 f780451..493d6f9 100644 --- a/commands/dns.go +++ b/commands/dns.go @@ -3,7 +3,6 @@ package commands import ( "fmt" "os" - "os/exec" "regexp" "strings" @@ -73,24 +72,24 @@ 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.StreamCommand(exec.Command("sudo", "route", "-n", "add", "172.17.0.0/16", machineIP)) + 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.) 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") } } // 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 @@ -108,13 +107,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.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()) } // StartDNS will start the dnsdock service @@ -149,7 +148,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 @@ -168,21 +167,21 @@ 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 { // 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,18 +197,18 @@ 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")) + if err := util.Command("systemctl", "is-active", "NetworkManager").Run(); err != nil { + util.StreamCommand("sudo", "systemctl", "restart", "NetworkManager") } } // 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 a5004a5..713827b 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.") @@ -61,7 +60,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) @@ -82,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") @@ -138,7 +137,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.") @@ -147,7 +146,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 { @@ -168,7 +167,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/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..9d131aa 100644 --- a/commands/machine.go +++ b/commands/machine.go @@ -3,7 +3,6 @@ package commands import ( "fmt" "os" - "os/exec" "regexp" "strings" "time" @@ -27,11 +26,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 +42,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 +56,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 +68,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) } @@ -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'") } @@ -97,7 +96,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 +109,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 @@ -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_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/project_sync.go b/commands/project_sync.go index bb1166d..8fa7f67 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) } @@ -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) } @@ -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 b5bc6a4..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) } @@ -98,7 +97,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 +123,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..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" @@ -37,9 +36,9 @@ 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() + 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 1c12e58..396124c 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,8 @@ func GetDockerClientAPIVersion() *version.Version { // GetDockerServerAPIVersion returns a Version for the docker server API version func GetDockerServerAPIVersion() (*version.Version, error) { - output, err := exec.Command("docker", "version", "--format", "{{.Server.APIVersion}}").Output() + output, err := Command("docker", "version", "--format", "{{.Server.APIVersion}}").Output() + if err != nil { return nil, err } @@ -41,7 +41,8 @@ func GetDockerServerAPIVersion() (*version.Version, error) { // GetDockerServerMinAPIVersion returns the minimum compatability version for the docker server func GetDockerServerMinAPIVersion() (*version.Version, error) { - output, err := exec.Command("docker", "version", "--format", "{{.Server.MinAPIVersion}}").Output() + output, err := Command("docker", "version", "--format", "{{.Server.MinAPIVersion}}").Output() + if err != nil { return nil, err } @@ -50,7 +51,7 @@ func GetDockerServerMinAPIVersion() (*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 +68,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 05f9f5b..a3d0fea 100644 --- a/util/shell_exec.go +++ b/util/shell_exec.go @@ -1,9 +1,11 @@ package util import ( + "fmt" "io/ioutil" "os" "os/exec" + "strings" "syscall" "github.com/fatih/color" @@ -11,29 +13,29 @@ import ( const defaultFailedCode = 1 +// Executor wraps exec.Cmd to allow consistent manipulation of executed commands. +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) +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 RunCommand(cmd, true) +func ForceStreamCommand(path string, arg ...string) error { + return Command(path, arg...).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 - } +// Command creates a new Executor instance from the execution arguments. +func Command(path string, arg ...string) Executor { + return Executor{exec.Command(path, arg...)} +} - color.Set(color.FgCyan) - err := cmd.Run() - color.Unset() - return err +// 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 @@ -47,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. @@ -69,3 +72,67 @@ func PassthruCommand(cmd *exec.Cmd) (exitCode int) { return } + +// 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 { + x.cmd.Stdout = os.Stdout + } else { + x.cmd.Stdout = ioutil.Discard + } + + color.Set(color.FgCyan) + err := x.Run() + color.Unset() + 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") + 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() +} + +// 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.Start() +} + +// 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() +} + +// ToString converts a Command to a human-readable string with key context details. +func (x Executor) ToString() string { + context := "" + if x.cmd.Dir != "" { + context = fmt.Sprintf("(WD: %s", x.cmd.Dir) + } + if x.cmd.Env != nil { + env := strings.Join(x.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", x.cmd.Path, strings.Join(x.cmd.Args[1:], " "), context) +}