Skip to content

Commit f0f909b

Browse files
committed
Merge pull request #18 from namshi/local-execution
Allowing local execution
2 parents 60aba8b + 1c95156 commit f0f909b

File tree

3 files changed

+92
-18
lines changed

3 files changed

+92
-18
lines changed

README.md

+15-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# godo
22

3-
Remote execution level 9000: **go** and **do**
3+
Local and remote execution level 9000: **go** and **do**
44
stuff.
55

66
![example](https://raw.githubusercontent.com/namshi/godo/master/images/gif-example.gif)
77

88
`godo` is a very simple yet powerful tool that
99
let's you specify a list of repetitive / useful
10-
commands you run on remote hosts, and run them
11-
with ease, without having to remember them or,
12-
worse, login on each server and execute them
13-
manually.
10+
commands you run on remote hosts or even locally,
11+
and run them with ease, without having to remember
12+
them or, even worse, login on each server and
13+
execute them manually.
1414

1515
## Installation
1616

@@ -75,6 +75,10 @@ commands:
7575
nginx-logs:
7676
target: web
7777
exec: "sudo tail -10f /var/log/nginx/access.log"
78+
my-uptime:
79+
target: local
80+
exec: "uptime"
81+
description: "Retrieves uptime info for the current machine"
7882
hostfile: "/home/YOU/.ssh/known_hosts"
7983
timeout: 2
8084
```
@@ -115,9 +119,12 @@ godo uptime @ db
115119
godo uptime@db
116120
```
117121

118-
Godo provides a special group, called `all`, that
119-
represents all servers, so you can always run
120-
something like `godo uptime @ all`.
122+
Godo provides some special groups:
123+
124+
* `all`, represents all servers, so you can
125+
always run something like `godo uptime @ all`
126+
* `local`, which references the current machine,
127+
so you will be running the command locally (`godo uptime @ local`)
121128

122129
## Additional documentation
123130

src/cli/cli.go

+17-7
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ func addCommands(app *cli.App) {
5454

5555
if command, ok := cfg.Commands[cmd]; ok {
5656
fmt.Printf("Executing '%s'", info(cmd))
57+
58+
if target == "" {
59+
target = command.Target
60+
}
61+
5762
runCommand(command, cfg, target)
5863
} else {
5964
printAvailableCommands(app, cfg.Commands, c)
@@ -125,18 +130,23 @@ func addTargetFromServer(targets map[string]config.Server, target string, cfg co
125130
// we simply look at the configuration of the
126131
// command.
127132
//
128-
// A target can be a server or a group of servers.
133+
// A target can be a server, group of servers
134+
// or a special alias.
135+
//
136+
// The supported aliases are
137+
// - all: will execute the command on all servers
138+
// - local: instead of executing the command remotely
139+
// it will execute it on the current machine
129140
func getTargets(command config.Command, cfg config.Config, target string) map[string]config.Server {
130141
targets := make(map[string]config.Server)
131142

132143
if target == "all" {
133144
targets = cfg.Servers
134-
} else if target != "" {
145+
} else if target == "local" {
146+
targets["local"] = config.Server{}
147+
} else {
135148
addTargetFromGroups(targets, target, cfg)
136149
addTargetFromServer(targets, target, cfg)
137-
} else {
138-
addTargetFromGroups(targets, command.Target, cfg)
139-
addTargetFromServer(targets, command.Target, cfg)
140150
}
141151

142152
return targets
@@ -154,10 +164,10 @@ func runCommand(command config.Command, cfg config.Config, target string) {
154164
}
155165

156166
if len(targets) > 0 {
157-
fmt.Printf("\nExecuting on server %s", info(strings.Join(targetNames, ", ")))
167+
fmt.Printf("\nExecuting on server '%s'", info(strings.Join(targetNames, ", ")))
158168
fmt.Println()
159169
fmt.Println()
160-
exec.ExecuteRemoteCommands(command.Exec, targets, cfg)
170+
exec.ExecuteCommands(command.Exec, targets, cfg)
161171
} else {
162172
fmt.Printf(err("\nNo target server / group with the name '%s' could be found, maybe a typo?"), target)
163173
}

src/exec/exec.go

+60-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
package exec
55

66
import (
7+
"fmt"
78
gossh "github.com/coreos/fleet/Godeps/_workspace/src/golang.org/x/crypto/ssh"
9+
"github.com/mgutz/ansi"
10+
goexec "os/exec"
811
"sync"
912
"time"
1013

@@ -13,14 +16,68 @@ import (
1316
"github.com/namshi/godo/src/ssh"
1417
)
1518

19+
// Checks whether a command needs
20+
// to be run locally or remotely.
21+
//
22+
// If the only server is "local"
23+
// then it means that we need to
24+
// simply run the command on this
25+
// local machine.
26+
func isLocalCommand(servers map[string]config.Server) bool {
27+
if len(servers) == 1 {
28+
if _, ok := servers["local"]; ok {
29+
return true
30+
}
31+
}
32+
33+
return false
34+
}
35+
36+
// Executes the command on the given
37+
// servers.
38+
//
39+
// The main goal of this method is to
40+
// figure out whether this command needs
41+
// to be executed locally or remotely,
42+
// and go ahead with the proper execution
43+
// strategy.
44+
func ExecuteCommands(command string, servers map[string]config.Server, cfg config.Config) {
45+
if isLocalCommand(servers) {
46+
executeLocalCommand(command)
47+
} else {
48+
executeRemoteCommands(command, servers, cfg)
49+
}
50+
}
51+
52+
// Executes a command locally.
53+
func executeLocalCommand(command string) {
54+
cmd := goexec.Command(command)
55+
stdout, stderr := log.GetRemoteLoggers("local")
56+
cmd.Stdout = stdout
57+
cmd.Stderr = stderr
58+
59+
err := cmd.Start()
60+
61+
// failed to spawn new process
62+
if err != nil {
63+
fmt.Println(ansi.Color(err.Error(), "red+h"))
64+
}
65+
66+
// Failed to execute?
67+
err = cmd.Wait()
68+
if err != nil {
69+
fmt.Println(ansi.Color(err.Error(), "red+h"))
70+
}
71+
}
72+
1673
// Executes the given command on a series
1774
// of servers.
1875
//
1976
// We will launch N goroutins based on how
2077
// many commands we need to remotely execute,
2178
// and stop the execution once everyone is
2279
// done.
23-
func ExecuteRemoteCommands(command string, servers map[string]config.Server, cfg config.Config) {
80+
func executeRemoteCommands(command string, servers map[string]config.Server, cfg config.Config) {
2481
var wg sync.WaitGroup
2582
wg.Add(len(servers))
2683

@@ -29,7 +86,7 @@ func ExecuteRemoteCommands(command string, servers map[string]config.Server, cfg
2986
c := &ssh.Config{Address: serverConfig.Address, Alias: server, Tunnel: serverConfig.Tunnel, User: serverConfig.User, Hostfile: cfg.Hostfile}
3087
c.Timeout = time.Duration(cfg.Timeout) * time.Second
3188
session, _ := ssh.CreateClient(c).NewSession()
32-
ExecuteRemoteCommand(command, session, server)
89+
executeRemoteCommand(command, session, server)
3390
defer wg.Done()
3491
}(server, serverConfig)
3592
}
@@ -39,7 +96,7 @@ func ExecuteRemoteCommands(command string, servers map[string]config.Server, cfg
3996

4097
// Executes the given command through SSH,
4198
// connecting with the given config.
42-
func ExecuteRemoteCommand(command string, session *gossh.Session, server string) {
99+
func executeRemoteCommand(command string, session *gossh.Session, server string) {
43100
stdout, stderr := log.GetRemoteLoggers(server)
44101
session.Stdout = stdout
45102
session.Stderr = stderr

0 commit comments

Comments
 (0)