Skip to content

Commit 11cdd5c

Browse files
committed
Merge pull request #4105 from technosophos/feature/deisctl-ssh-exec-docker
feat(deisctl): extend SSH to allow exec and docker
2 parents e998b29 + 3febf79 commit 11cdd5c

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
@@ -241,16 +241,45 @@ Usage:
241241
func (c *Client) SSH(argv []string) error {
242242
usage := `Open an interactive shell on a machine in the cluster given a unit or machine id.
243243
244+
If an optional <command> is provided, that command is run remotely, and the results returned.
245+
244246
Usage:
245-
deisctl ssh <target>
247+
deisctl ssh <target> [<command>...]
246248
`
247249
// parse command-line arguments
248-
args, err := docopt.Parse(usage, argv, true, "", false)
250+
args, err := docopt.Parse(usage, argv, true, "", true)
249251
if err != nil {
250252
return err
251253
}
252254

253-
return cmd.SSH(args["<target>"].(string), c.Backend)
255+
var vargs []string
256+
if v, ok := args["<command>"]; ok {
257+
vargs = v.([]string)
258+
}
259+
260+
return cmd.SSH(args["<target>"].(string), vargs, c.Backend)
261+
}
262+
263+
func (c *Client) Dock(argv []string) error {
264+
usage := `Connect to the named docker container and run commands on it.
265+
266+
This is equivalent to running 'docker exec -it <target> <command>'.
267+
268+
Usage:
269+
deisctl dock <target> [<command>...]
270+
`
271+
// parse command-line arguments
272+
args, err := docopt.Parse(usage, argv, true, "", true)
273+
if err != nil {
274+
return err
275+
}
276+
277+
var vargs []string
278+
if v, ok := args["<command>"]; ok {
279+
vargs = v.([]string)
280+
}
281+
282+
return cmd.Dock(args["<target>"].(string), vargs, c.Backend)
254283
}
255284

256285
// Start activates the specified components.

deisctl/cmd/cmd.go

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

466466
// SSH opens an interactive shell on a machine in the cluster
467-
func SSH(target string, b backend.Backend) error {
468-
if err := b.SSH(target); err != nil {
469-
return err
467+
func SSH(target string, cmd []string, b backend.Backend) error {
468+
469+
if len(cmd) > 0 {
470+
return b.SSHExec(target, strings.Join(cmd, " "))
470471
}
471472

472-
return nil
473+
return b.SSH(target)
474+
}
475+
476+
// Dock connects to the appropriate host and runs 'docker exec -it'.
477+
func Dock(target string, cmd []string, b backend.Backend) error {
478+
479+
c := "sh"
480+
if len(cmd) > 0 {
481+
c = strings.Join(cmd, " ")
482+
}
483+
484+
execit := fmt.Sprintf("docker exec -it %s %s", target, c)
485+
486+
return b.SSHExec(target, execit)
473487
}

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)