/
docker.go
169 lines (135 loc) · 4.21 KB
/
docker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package docker
import (
"bytes"
"fmt"
"net"
"os/exec"
"strings"
"time"
"github.com/lithammer/shortuuid"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
)
// Package docker provides functionality to create throwaway docker containers in test
//
// The package goal is to provide ssh-enabled (password) containers with the following user setup
//
// | User | Password | Sudo rights | Sudo lecture | SSH keys |
// |--------|-----------|------------- |--------------|----------|
// | root | rootpwd | ALL | N/A | N/A |
// | gossh | gosshpwd | ALL | never | TODO |
// | hobgob | hobgobpwd | NOPASSWD:ALL | never | TODO |
// | joxter | joxterpwd | ALL | allways | TODO |
// | groke | grokepwd | NOPASSWD:ALL | allways | TODO |
// | stinky | stinkypwd | NO | N/A | TODO |
//
// findPort finds and returns a free port on the host machine
// inspired by https://github.com/Konstantin8105/FreePort/blob/master/freeport.go
func findPort() (port int, err error) {
ln, err := net.Listen("tcp", "[::]:0")
if err != nil {
return 0, err
}
defer ln.Close()
return ln.Addr().(*net.TCPAddr).Port, nil
}
// Container represents a docker container
type Container struct {
name string
port int
killed bool
image string
}
// Kill issues 'docker kill' on the container
func (c *Container) Kill() error {
cmd := exec.Command("docker", "kill", c.name)
err := cmd.Run()
if err != nil {
return errors.Wrap(err, "could not stop throwaway container")
}
return nil
}
// Addr is a handy function for getting the container address string localhost:port
func (c *Container) Addr() string {
return fmt.Sprintf("localhost:%d", c.port)
}
// Image returns the image name
func (c *Container) Image() string {
return c.image
}
// Exec issues 'docker exec' to the container.
// Returns stdout, stderr and exitcode if found.
// Leading and trailing spaces and newlines are trimmed from stdout and stderr.
func (c *Container) Exec(cmd string) (string, string, int, error) {
command := exec.Command("docker", "exec", c.name, "bash", "-c", cmd)
o := bytes.Buffer{}
e := bytes.Buffer{}
var s int = 0
command.Stdout = &o
command.Stderr = &e
err := command.Run()
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
s = exitError.ExitCode()
err = nil
} else {
s = -1
}
}
return strings.Trim(o.String(), " \n"), strings.Trim(e.String(), " \n"), s, err
}
// Port gets the port that SSH is listening on
func (c *Container) Port() int {
return c.port
}
// NewSSHClient returns a new ssh client for the container
// user must be eiter root, gossh or hobgob
func (c *Container) NewSSHClient(user string) (*ssh.Client, error) {
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(user + "pwd"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
return ssh.Dial("tcp", fmt.Sprintf("localhost:%d", c.port), config)
}
// New creates runs a docker container with ssh enabled.
func New(image Image) (*Container, error) {
c := Container{
image: image.Name(),
}
cmd := exec.Command("docker", "build", "-t", image.Name(), "-")
cmd.Stdin = strings.NewReader(string(image.Dockerfile()))
b, err := cmd.CombinedOutput()
if err != nil {
return &c, errors.Wrapf(err, "could not create throwaway container: %s", string(b))
}
c.name = shortuuid.New()
c.port, err = findPort()
if err != nil {
return &c, errors.Wrap(err, "could not get free port")
}
cmd = exec.Command("docker", "run", "-d", "--rm", "--name", c.name, "-p", fmt.Sprintf("%d:22", c.port), image.Name())
b, err = cmd.CombinedOutput()
if err != nil {
return &c, errors.Wrapf(err, "could not run throwaway container %s on port %d: %s", c.name, c.port, string(b))
}
cc := ssh.ClientConfig{
User: "gossh",
Auth: []ssh.AuthMethod{
ssh.Password("gosshpwd"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// This is to wait for sshd to be ready to accept connections
for i := 0; i < 6; i++ {
time.Sleep(time.Duration(int(time.Millisecond) * 150 * i))
client, err := ssh.Dial("tcp", fmt.Sprintf(":%d", c.port), &cc)
if err == nil {
client.Close()
break
}
}
return &c, err
}