@@ -2,6 +2,7 @@ package dockertest
2
2
3
3
import (
4
4
"fmt"
5
+ "io"
5
6
"io/ioutil"
6
7
"net"
7
8
"os"
@@ -16,12 +17,27 @@ import (
16
17
"github.com/pkg/errors"
17
18
)
18
19
20
+ var (
21
+ ErrNotInContainer = errors .New ("not running in container" )
22
+ )
23
+
19
24
// Pool represents a connection to the docker API and is used to create and remove docker images.
20
25
type Pool struct {
21
26
Client * dc.Client
22
27
MaxWait time.Duration
23
28
}
24
29
30
+ // Network represents a docker network.
31
+ type Network struct {
32
+ pool * Pool
33
+ Network * dc.Network
34
+ }
35
+
36
+ // Close removes network by calling pool.RemoveNetwork.
37
+ func (n * Network ) Close () error {
38
+ return n .pool .RemoveNetwork (n )
39
+ }
40
+
25
41
// Resource represents a docker container.
26
42
type Resource struct {
27
43
pool * Pool
@@ -74,6 +90,118 @@ func (r *Resource) GetHostPort(portID string) string {
74
90
return net .JoinHostPort (ip , m [0 ].HostPort )
75
91
}
76
92
93
+ type ExecOptions struct {
94
+ // Command environment, optional.
95
+ Env []string
96
+
97
+ // StdIn will be attached as command stdin if provided.
98
+ StdIn io.Reader
99
+
100
+ // StdOut will be attached as command stdout if provided.
101
+ StdOut io.Writer
102
+
103
+ // StdErr will be attached as command stdout if provided.
104
+ StdErr io.Writer
105
+
106
+ // Allocate TTY for command or not.
107
+ TTY bool
108
+ }
109
+
110
+ // Exec executes command within container.
111
+ func (r * Resource ) Exec (cmd []string , opts ExecOptions ) (exitCode int , err error ) {
112
+ exec , err := r .pool .Client .CreateExec (dc.CreateExecOptions {
113
+ Container : r .Container .ID ,
114
+ Cmd : cmd ,
115
+ Env : opts .Env ,
116
+ AttachStderr : opts .StdErr != nil ,
117
+ AttachStdout : opts .StdOut != nil ,
118
+ AttachStdin : opts .StdIn != nil ,
119
+ Tty : opts .TTY ,
120
+ })
121
+ if err != nil {
122
+ return - 1 , errors .Wrap (err , "Create exec failed" )
123
+ }
124
+
125
+ err = r .pool .Client .StartExec (exec .ID , dc.StartExecOptions {
126
+ InputStream : opts .StdIn ,
127
+ OutputStream : opts .StdOut ,
128
+ ErrorStream : opts .StdErr ,
129
+ Tty : opts .TTY ,
130
+ })
131
+ if err != nil {
132
+ return - 1 , errors .Wrap (err , "Start exec failed" )
133
+ }
134
+
135
+ inspectExec , err := r .pool .Client .InspectExec (exec .ID )
136
+ if err != nil {
137
+ return - 1 , errors .Wrap (err , "Inspect exec failed" )
138
+ }
139
+
140
+ return inspectExec .ExitCode , nil
141
+ }
142
+
143
+ // GetIPInNetwork returns container IP address in network.
144
+ func (r * Resource ) GetIPInNetwork (network * Network ) string {
145
+ if r .Container == nil || r .Container .NetworkSettings == nil {
146
+ return ""
147
+ }
148
+
149
+ netCfg , ok := r .Container .NetworkSettings .Networks [network .Network .Name ]
150
+ if ! ok {
151
+ return ""
152
+ }
153
+
154
+ return netCfg .IPAddress
155
+ }
156
+
157
+ // ConnectToNetwork connects container to network.
158
+ func (r * Resource ) ConnectToNetwork (network * Network ) error {
159
+ err := r .pool .Client .ConnectNetwork (
160
+ network .Network .ID ,
161
+ dc.NetworkConnectionOptions {Container : r .Container .ID },
162
+ )
163
+ if err != nil {
164
+ return errors .Wrap (err , "Failed to connect container to network" )
165
+ }
166
+
167
+ // refresh internal representation
168
+ r .Container , err = r .pool .Client .InspectContainer (r .Container .ID )
169
+ if err != nil {
170
+ return errors .Wrap (err , "Failed to refresh container information" )
171
+ }
172
+
173
+ network .Network , err = r .pool .Client .NetworkInfo (network .Network .ID )
174
+ if err != nil {
175
+ return errors .Wrap (err , "Failed to refresh network information" )
176
+ }
177
+
178
+ return nil
179
+ }
180
+
181
+ // DisconnectFromNetwork disconnects container from network.
182
+ func (r * Resource ) DisconnectFromNetwork (network * Network ) error {
183
+ err := r .pool .Client .DisconnectNetwork (
184
+ network .Network .ID ,
185
+ dc.NetworkConnectionOptions {Container : r .Container .ID },
186
+ )
187
+ if err != nil {
188
+ return errors .Wrap (err , "Failed to connect container to network" )
189
+ }
190
+
191
+ // refresh internal representation
192
+ r .Container , err = r .pool .Client .InspectContainer (r .Container .ID )
193
+ if err != nil {
194
+ return errors .Wrap (err , "Failed to refresh container information" )
195
+ }
196
+
197
+ network .Network , err = r .pool .Client .NetworkInfo (network .Network .ID )
198
+ if err != nil {
199
+ return errors .Wrap (err , "Failed to refresh network information" )
200
+ }
201
+
202
+ return nil
203
+ }
204
+
77
205
// Close removes a container and linked volumes from docker by calling pool.Purge.
78
206
func (r * Resource ) Close () error {
79
207
return r .pool .Purge (r )
@@ -167,6 +295,7 @@ type RunOptions struct {
167
295
DNS []string
168
296
WorkingDir string
169
297
NetworkID string
298
+ Networks []* Network // optional networks to join
170
299
Labels map [string ]string
171
300
Auth dc.AuthConfiguration
172
301
PortBindings map [dc.Port ][]dc.PortBinding
@@ -259,6 +388,9 @@ func (d *Pool) RunWithOptions(opts *RunOptions, hcOpts ...func(*dc.HostConfig))
259
388
if opts .NetworkID != "" {
260
389
networkingConfig .EndpointsConfig [opts .NetworkID ] = & dc.EndpointConfig {}
261
390
}
391
+ for _ , network := range opts .Networks {
392
+ networkingConfig .EndpointsConfig [network .Network .ID ] = & dc.EndpointConfig {}
393
+ }
262
394
263
395
_ , err := d .Client .InspectImage (fmt .Sprintf ("%s:%s" , repository , tag ))
264
396
if err != nil {
@@ -316,6 +448,13 @@ func (d *Pool) RunWithOptions(opts *RunOptions, hcOpts ...func(*dc.HostConfig))
316
448
return nil , errors .Wrap (err , "" )
317
449
}
318
450
451
+ for _ , network := range opts .Networks {
452
+ network .Network , err = d .Client .NetworkInfo (network .Network .ID )
453
+ if err != nil {
454
+ return nil , errors .Wrap (err , "" )
455
+ }
456
+ }
457
+
319
458
return & Resource {
320
459
pool : d ,
321
460
Container : c ,
@@ -404,3 +543,57 @@ func (d *Pool) Retry(op func() error) error {
404
543
bo .MaxElapsedTime = d .MaxWait
405
544
return backoff .Retry (op , bo )
406
545
}
546
+
547
+ // CurrentContainer returns current container descriptor if this function called within running container.
548
+ // It returns ErrNotInContainer as error if this function running not in container.
549
+ func (d * Pool ) CurrentContainer () (* Resource , error ) {
550
+ // docker daemon puts short container id into hostname
551
+ hostname , err := os .Hostname ()
552
+ if err != nil {
553
+ return nil , errors .Wrap (err , "Get hostname failed" )
554
+ }
555
+
556
+ container , err := d .Client .InspectContainer (hostname )
557
+ switch err .(type ) {
558
+ case nil :
559
+ return & Resource {
560
+ pool : d ,
561
+ Container : container ,
562
+ }, nil
563
+ case * dc.NoSuchContainer :
564
+ return nil , ErrNotInContainer
565
+ default :
566
+ return nil , errors .Wrap (err , "" )
567
+ }
568
+ }
569
+
570
+ // CreateNetwork creates docker network. It's useful for linking multiple containers.
571
+ func (d * Pool ) CreateNetwork (name string , opts ... func (config * dc.CreateNetworkOptions )) (* Network , error ) {
572
+ var cfg dc.CreateNetworkOptions
573
+ cfg .Name = name
574
+ for _ , opt := range opts {
575
+ opt (& cfg )
576
+ }
577
+
578
+ network , err := d .Client .CreateNetwork (cfg )
579
+ if err != nil {
580
+ return nil , errors .Wrap (err , "" )
581
+ }
582
+
583
+ return & Network {
584
+ pool : d ,
585
+ Network : network ,
586
+ }, nil
587
+ }
588
+
589
+ // RemoveNetwork disconnects containers and removes provided network.
590
+ func (d * Pool ) RemoveNetwork (network * Network ) error {
591
+ for container := range network .Network .Containers {
592
+ _ = d .Client .DisconnectNetwork (
593
+ network .Network .ID ,
594
+ dc.NetworkConnectionOptions {Container : container , Force : true },
595
+ )
596
+ }
597
+
598
+ return d .Client .RemoveNetwork (network .Network .ID )
599
+ }
0 commit comments