github.com/greenboxal/deis@v1.12.1/tests/dockercli/dockercli.go (about) 1 // Package dockercli provides helper functions for testing with Docker. 2 package dockercli 3 4 import ( 5 "bufio" 6 "crypto/tls" 7 "fmt" 8 "io" 9 "log" 10 "net" 11 "net/http" 12 "net/url" 13 "os" 14 "path/filepath" 15 "strings" 16 "testing" 17 "time" 18 19 "github.com/deis/deis/tests/utils" 20 "github.com/docker/docker/api/client" 21 ) 22 23 const ( 24 defaultKeyFile = "key.pem" 25 defaultCertFile = "cert.pem" 26 ) 27 28 // CloseWrap ensures that an io.Writer is closed. 29 func CloseWrap(args ...io.Closer) error { 30 e := false 31 ret := fmt.Errorf("Error closing elements") 32 for _, c := range args { 33 if err := c.Close(); err != nil { 34 e = true 35 ret = fmt.Errorf("%s\n%s", ret, err) 36 } 37 } 38 if e { 39 return ret 40 } 41 return nil 42 } 43 44 // DeisServiceTest tries to connect to a container and port using the 45 // specified protocol. 46 func DeisServiceTest( 47 t *testing.T, container string, port string, protocol string) { 48 ipaddr := utils.HostAddress() 49 if ipaddr == "" { 50 ipaddr = GetInspectData( 51 t, "{{ .NetworkSettings.ipaddr }}", container) 52 } 53 fmt.Println("Running service test for " + container) 54 if strings.Contains(ipaddr, "Error") { 55 t.Fatalf("wrong IP %s", ipaddr) 56 } 57 if protocol == "http" { 58 url := "http://" + ipaddr + ":" + port 59 response, err := http.Get(url) 60 if err != nil { 61 t.Fatalf("Not reachable %s", err) 62 } 63 fmt.Println(response) 64 } 65 if protocol == "tcp" || protocol == "udp" { 66 conn, err := net.Dial(protocol, ipaddr+":"+port) 67 if err != nil { 68 t.Fatalf("Not reachable %s", err) 69 } 70 _, err = conn.Write([]byte("HEAD")) 71 if err != nil { 72 t.Fatalf("Not reachable %s", err) 73 } 74 } 75 } 76 77 // DockerHost returns the protocol and address of the docker server. 78 func DockerHost() (string, string, error) { 79 dockerHost := os.Getenv("DOCKER_HOST") 80 if dockerHost == "" { 81 dockerHost = "unix:///var/run/docker.sock" 82 } 83 u, err := url.Parse(dockerHost) 84 if err != nil { 85 return "", "", err 86 } 87 if u.Scheme == "unix" { 88 return u.Scheme, u.Path, nil 89 } 90 return u.Scheme, u.Host, nil 91 } 92 93 // NewClient returns a new docker test client. 94 func NewClient() ( 95 cli *client.DockerCli, stdout *io.PipeReader, stdoutPipe *io.PipeWriter) { 96 proto, addr, _ := DockerHost() 97 stdout, stdoutPipe = io.Pipe() 98 99 dockerCertPath := os.Getenv("DOCKER_CERT_PATH") 100 // Boot2docker use TLS per default, Jenkins not 101 if dockerCertPath != "" { 102 var ( 103 tlsConfig tls.Config 104 ) 105 tlsConfig.InsecureSkipVerify = true 106 107 flCert := filepath.Join(dockerCertPath, defaultCertFile) 108 flKey := filepath.Join(dockerCertPath, defaultKeyFile) 109 110 _, errCert := os.Stat(flCert) 111 _, errKey := os.Stat(flKey) 112 if errCert == nil && errKey == nil { 113 cert, err := tls.LoadX509KeyPair(flCert, flKey) 114 if err != nil { 115 log.Fatalf("Couldn't load X509 key pair: %s. Key encrypted?", err) 116 } 117 tlsConfig.Certificates = []tls.Certificate{cert} 118 } 119 // Avoid fallback to SSL protocols < TLS1.0 120 tlsConfig.MinVersion = tls.VersionTLS10 121 cli = client.NewDockerCli(nil, stdoutPipe, nil, "", proto, addr, &tlsConfig) 122 } else { 123 cli = client.NewDockerCli(nil, stdoutPipe, nil, "", proto, addr, nil) 124 } 125 return 126 } 127 128 func CreateVolume(t *testing.T, cli *client.DockerCli, name string, path string) { 129 err := RunContainer(cli, 130 "--name", name, 131 "-v", path, 132 "ubuntu-debootstrap:14.04", "/bin/true") 133 134 if err != nil { 135 t.Fatal(err) 136 } 137 } 138 139 // PrintToStdout prints a string to stdout. 140 func PrintToStdout(t *testing.T, stdout *io.PipeReader, 141 stdoutPipe *io.PipeWriter, stoptag string) string { 142 var result string 143 r := bufio.NewReader(stdout) 144 for { 145 cmdBytes, err := r.ReadString('\n') 146 if err != nil { 147 break 148 } 149 result = cmdBytes 150 fmt.Print(cmdBytes) 151 if strings.Contains(cmdBytes, stoptag) == true { 152 if err := CloseWrap(stdout, stdoutPipe); err != nil { 153 t.Fatal(err) 154 } 155 } 156 } 157 return result 158 } 159 160 func WaitForLine(t *testing.T, stdout *io.PipeReader, stoptag string, trace bool) string { 161 var result string 162 r := bufio.NewReader(stdout) 163 for { 164 cmdBytes, err := r.ReadString('\n') 165 if err != nil { 166 break 167 } 168 result = cmdBytes 169 if trace { 170 fmt.Print(cmdBytes) 171 } 172 if strings.Contains(cmdBytes, stoptag) == true { 173 break 174 } 175 } 176 return result 177 } 178 179 // GetInspectData prints and returns `docker inspect` data for a container. 180 func GetInspectData(t *testing.T, format string, container string) string { 181 var inspectData string 182 cli, stdout, stdoutPipe := NewClient() 183 fmt.Println("Getting inspect data :" + format + ":" + container) 184 go func() { 185 err := cli.CmdInspect("--format", format, container) 186 if err != nil { 187 fmt.Printf("%s %s", format, err) 188 } 189 if err = CloseWrap(stdout, stdoutPipe); err != nil { 190 t.Fatalf("inspect data failed %s", err) 191 } 192 }() 193 go func() { 194 time.Sleep(3000 * time.Millisecond) 195 if err := CloseWrap(stdout, stdoutPipe); err != nil { 196 t.Fatalf("Inspect data %s", err) 197 } 198 }() 199 time.Sleep(1000 * time.Millisecond) 200 inspectData = PrintToStdout(t, stdout, stdoutPipe, "get inspect data") 201 return strings.TrimSuffix(inspectData, "\n") 202 } 203 204 // RunContainer runs a docker image with the given arguments. 205 func RunContainer(cli *client.DockerCli, args ...string) error { 206 // fmt.Println("--- Run docker container", args[1]) 207 err := cli.CmdRun(args...) 208 if err != nil { 209 // Ignore certain errors we see in io handling. 210 switch msg := err.Error(); { 211 case strings.Contains(msg, "read/write on closed pipe"): 212 err = nil 213 case strings.Contains(msg, "Code: -1"): 214 err = nil 215 case strings.Contains(msg, "Code: 2"): 216 err = nil 217 } 218 } 219 return err 220 } 221 222 // RunDeisDataTest starts a data container as a prerequisite for a service. 223 func RunDeisDataTest(t *testing.T, args ...string) { 224 done := make(chan bool, 1) 225 cli, stdout, stdoutPipe := NewClient() 226 var hostname string 227 fmt.Println(args[2] + " test") 228 hostname = GetInspectData(t, "{{ .Config.Hostname }}", args[1]) 229 fmt.Println("data container " + hostname) 230 done <- true 231 if strings.Contains(hostname, "Error") { 232 go func() { 233 <-done 234 if err := RunContainer(cli, args...); err != nil { 235 t.Fatal(err) 236 } 237 }() 238 go func() { 239 time.Sleep(3000 * time.Millisecond) 240 if err := CloseWrap(stdout, stdoutPipe); err != nil { 241 t.Fatalf("Inspect Element %s", err) 242 } 243 }() 244 PrintToStdout(t, stdout, stdoutPipe, "running"+args[1]) 245 } 246 } 247 248 // GetImageID returns the ID of a docker image. 249 func GetImageID(t *testing.T, repo string) string { 250 var imageID string 251 cli, stdout, stdoutPipe := NewClient() 252 go func() { 253 err := cli.CmdImages() 254 if err != nil { 255 t.Fatalf("GetImageID %s", err) 256 } 257 if err = CloseWrap(stdout, stdoutPipe); err != nil { 258 t.Fatalf("GetImageID %s", err) 259 } 260 }() 261 imageID = PrintToStdout(t, stdout, stdoutPipe, repo) 262 return strings.Fields(imageID)[2] 263 } 264 265 // RunTestEtcd starts an etcd docker container for testing. 266 func RunTestEtcd(t *testing.T, name string, port string) { 267 var err error 268 cli, stdout, stdoutPipe := NewClient() 269 etcdImage := utils.ImagePrefix() + "test-etcd:" + utils.BuildTag() 270 ipaddr := utils.HostAddress() 271 etcdAddr := ipaddr + ":" + port 272 fmt.Printf("--- Running %s at %s\n", etcdImage, etcdAddr) 273 done2 := make(chan bool, 1) 274 go func() { 275 done2 <- true 276 _ = cli.CmdRm("-f", name) 277 err = RunContainer(cli, 278 "--name", name, 279 "--rm", 280 "-p", port+":"+port, 281 "-e", "HOST_IP="+ipaddr, 282 "-e", "ETCD_ADDR="+etcdAddr, 283 etcdImage) 284 }() 285 go func() { 286 <-done2 287 time.Sleep(5000 * time.Millisecond) 288 if err := CloseWrap(stdout, stdoutPipe); err != nil { 289 t.Fatalf("runEtcdTest %s", err) 290 } 291 }() 292 time.Sleep(1000 * time.Millisecond) 293 PrintToStdout(t, stdout, stdoutPipe, "pulling etcd") 294 if err != nil { 295 t.Fatal(err) 296 } 297 } 298 299 // registryLabel indicates which registry version we want. 300 const registryLabel = "0.9.1" 301 302 // RunTestRegistry runs a Docker registry for testing. 303 // 304 // This uses a stock Docker registry with no storage backend. 305 func RunTestRegistry(t *testing.T, name, host, port string) { 306 var err error 307 cli, stdout, stdoutPipe := NewClient() 308 reg := "registry:" + registryLabel 309 fmt.Printf("--- Running %s at %s:%s\n", reg, host, port) 310 done := make(chan bool, 1) 311 go func() { 312 done <- true 313 _ = cli.CmdRm("-f", name) 314 err = RunContainer(cli, 315 "--name", name, 316 "--rm", 317 "-d", 318 "-p", port+":5000", 319 reg) 320 }() 321 go func() { 322 <-done 323 time.Sleep(5000 * time.Millisecond) 324 if err := CloseWrap(stdout, stdoutPipe); err != nil { 325 t.Fatalf("RunTestRegistry %s", err) 326 } 327 }() 328 }