Skip to content

Commit 3febf79

Browse files
committed
feat(deisctl): extend SSH to allow exec and docker
This feature extends 'deis ssh' to allow for an arbitrary command. If specified, the command will be run as an SSH Exec call, instead of opening a new shell. Further, this adds 'deis dock', which is a convenience wrapper for 'deis ssh THING docker exec -it THING COMMAND'. This now becomes 'deis dock THING COMMAND'.
1 parent e4a54d0 commit 3febf79

7 files changed

Lines changed: 131 additions & 24 deletions

File tree

deisctl/backend/backend.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type Backend interface {
1313
Stop([]string, *sync.WaitGroup, io.Writer, io.Writer)
1414
Scale(string, int, *sync.WaitGroup, io.Writer, io.Writer)
1515
SSH(string) error
16+
SSHExec(string, string) error
1617
ListUnits() error
1718
ListUnitFiles() error
1819
Status(string) error

deisctl/backend/fleet/ssh.go

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,51 +13,66 @@ import (
1313
)
1414

1515
// SSH opens an interactive shell to a machine in the cluster
16-
func (c *FleetClient) SSH(name string) (err error) {
17-
var sshClient *ssh.SSHForwardingClient
16+
func (c *FleetClient) SSH(name string) error {
17+
sshClient, err := c.sshConnect(name)
18+
if err != nil {
19+
return err
20+
}
21+
22+
defer sshClient.Close()
23+
err = ssh.Shell(sshClient)
24+
return err
25+
}
26+
27+
func (c *FleetClient) SSHExec(name, cmd string) error {
28+
fmt.Printf("Excuting '%s' on container '%s'\n", cmd, name)
29+
30+
conn, err := c.sshConnect(name)
31+
if err != nil {
32+
return err
33+
}
34+
35+
err, _ = ssh.Execute(conn, cmd)
36+
return err
37+
}
38+
39+
func (c *FleetClient) sshConnect(name string) (*ssh.SSHForwardingClient, error) {
1840

1941
timeout := time.Duration(Flags.SSHTimeout*1000) * time.Millisecond
2042

2143
ms, err := c.machineState(name)
2244
if err != nil {
23-
return err
45+
return nil, err
2446
}
2547

2648
// If name isn't a machine ID, try it as a unit instead
2749
if ms == nil {
2850
units, err := c.Units(name)
2951

3052
if err != nil {
31-
return err
53+
return nil, err
3254
}
3355

3456
machID, err := c.findUnit(units[0])
3557

3658
if err != nil {
37-
return err
59+
return nil, err
3860
}
3961

4062
ms, err = c.machineState(machID)
4163

4264
if err != nil || ms == nil {
43-
return err
65+
return nil, err
4466
}
4567
}
4668

4769
addr := ms.PublicIP
4870

4971
if tun := getTunnelFlag(); tun != "" {
50-
sshClient, err = ssh.NewTunnelledSSHClient("core", tun, addr, getChecker(), false, timeout)
51-
} else {
52-
sshClient, err = ssh.NewSSHClient("core", addr, getChecker(), false, timeout)
53-
}
54-
if err != nil {
55-
return err
72+
return ssh.NewTunnelledSSHClient("core", tun, addr, getChecker(), false, timeout)
5673
}
74+
return ssh.NewSSHClient("core", addr, getChecker(), false, timeout)
5775

58-
defer sshClient.Close()
59-
err = ssh.Shell(sshClient)
60-
return err
6176
}
6277

6378
// runCommand will attempt to run a command on a given machine. It will attempt

deisctl/client/client.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,16 +229,45 @@ Usage:
229229
func (c *Client) SSH(argv []string) error {
230230
usage := `Open an interactive shell on a machine in the cluster given a unit or machine id.
231231
232+
If an optional <command> is provided, that command is run remotely, and the results returned.
233+
232234
Usage:
233-
deisctl ssh <target>
235+
deisctl ssh <target> [<command>...]
234236
`
235237
// parse command-line arguments
236-
args, err := docopt.Parse(usage, argv, true, "", false)
238+
args, err := docopt.Parse(usage, argv, true, "", true)
237239
if err != nil {
238240
return err
239241
}
240242

241-
return cmd.SSH(args["<target>"].(string), c.Backend)
243+
var vargs []string
244+
if v, ok := args["<command>"]; ok {
245+
vargs = v.([]string)
246+
}
247+
248+
return cmd.SSH(args["<target>"].(string), vargs, c.Backend)
249+
}
250+
251+
func (c *Client) Dock(argv []string) error {
252+
usage := `Connect to the named docker container and run commands on it.
253+
254+
This is equivalent to running 'docker exec -it <target> <command>'.
255+
256+
Usage:
257+
deisctl dock <target> [<command>...]
258+
`
259+
// parse command-line arguments
260+
args, err := docopt.Parse(usage, argv, true, "", true)
261+
if err != nil {
262+
return err
263+
}
264+
265+
var vargs []string
266+
if v, ok := args["<command>"]; ok {
267+
vargs = v.([]string)
268+
}
269+
270+
return cmd.Dock(args["<target>"].(string), vargs, c.Backend)
242271
}
243272

244273
// Start activates the specified components.

deisctl/cmd/cmd.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,10 +451,24 @@ func RefreshUnits(dir, tag, url string) error {
451451
}
452452

453453
// SSH opens an interactive shell on a machine in the cluster
454-
func SSH(target string, b backend.Backend) error {
455-
if err := b.SSH(target); err != nil {
456-
return err
454+
func SSH(target string, cmd []string, b backend.Backend) error {
455+
456+
if len(cmd) > 0 {
457+
return b.SSHExec(target, strings.Join(cmd, " "))
457458
}
458459

459-
return nil
460+
return b.SSH(target)
461+
}
462+
463+
// Dock connects to the appropriate host and runs 'docker exec -it'.
464+
func Dock(target string, cmd []string, b backend.Backend) error {
465+
466+
c := "sh"
467+
if len(cmd) > 0 {
468+
c = strings.Join(cmd, " ")
469+
}
470+
471+
execit := fmt.Sprintf("docker exec -it %s %s", target, c)
472+
473+
return b.SSHExec(target, execit)
460474
}

deisctl/cmd/cmd_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"sync"
1313
"testing"
1414

15+
"github.com/deis/deis/deisctl/backend"
1516
"github.com/deis/deis/deisctl/units"
1617
)
1718

@@ -69,6 +70,14 @@ func (backend *backendStub) SSH(target string) error {
6970
}
7071
return errors.New("Error")
7172
}
73+
func (backend *backendStub) SSHExec(target, command string) error {
74+
if target == "controller" && command == "sh" {
75+
return nil
76+
}
77+
return errors.New("Error")
78+
}
79+
80+
var _ backend.Backend = &backendStub{}
7281

7382
func fakeCheckKeys() error {
7483
return nil
@@ -332,7 +341,17 @@ func TestSSH(t *testing.T) {
332341
t.Parallel()
333342

334343
b := backendStub{}
335-
err := SSH("controller", &b)
344+
err := SSH("controller", []string{}, &b)
345+
346+
if err != nil {
347+
t.Error(err)
348+
}
349+
}
350+
func TestSSHExec(t *testing.T) {
351+
t.Parallel()
352+
353+
b := backendStub{}
354+
err := SSH("controller", []string{"sh"}, &b)
336355

337356
if err != nil {
338357
t.Error(err)
@@ -343,7 +362,7 @@ func TestSSHError(t *testing.T) {
343362
t.Parallel()
344363

345364
b := backendStub{}
346-
err := SSH("registry", &b)
365+
err := SSH("registry", []string{}, &b)
347366

348367
if err == nil {
349368
t.Error("Error expected")

deisctl/deisctl.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ Options:
112112
err = c.RefreshUnits(argv)
113113
case "ssh":
114114
err = c.SSH(argv)
115+
case "dock":
116+
err = c.Dock(argv)
115117
case "help":
116118
fmt.Print(usage)
117119
return 0

docs/troubleshooting_deis/index.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,33 @@ To open a interactive shell on a machine in your cluster:
2424
2525
$ deisctl ssh <unit>
2626
27+
For example, to open a shell session on the machine that is running Controller,
28+
you can run this:
29+
30+
.. code-block:: console
31+
32+
$ deisctl ssh controller
33+
34+
You can execute just a single command instead of opening a shell:
35+
36+
.. code-block:: console
37+
38+
$ deisctl ssh <unit> <command>
39+
40+
You can also connect directly to the Docker instance of that unit:
41+
42+
.. code-block:: console
43+
44+
$ deisctl dock <unit> <command>
45+
46+
For example, to start a Bash session on the Builder Docker container, you can
47+
run the following command:
48+
49+
.. code-block:: console
50+
51+
$ deisctl dock builder bash`
52+
53+
2754
Troubleshooting etcd
2855
--------------------
2956

0 commit comments

Comments
 (0)