diff options
Diffstat (limited to 'vendor/github.com/fsouza/go-dockerclient/testing')
11 files changed, 1457 insertions, 0 deletions
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/Dockerfile b/vendor/github.com/fsouza/go-dockerclient/testing/data/Dockerfile new file mode 100644 index 0000000..0948dcf --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/Dockerfile | |||
@@ -0,0 +1,15 @@ | |||
1 | # this file describes how to build tsuru python image | ||
2 | # to run it: | ||
3 | # 1- install docker | ||
4 | # 2- run: $ docker build -t tsuru/python https://raw.github.com/tsuru/basebuilder/master/python/Dockerfile | ||
5 | |||
6 | from base:ubuntu-quantal | ||
7 | run apt-get install wget -y --force-yes | ||
8 | run wget http://github.com/tsuru/basebuilder/tarball/master -O basebuilder.tar.gz --no-check-certificate | ||
9 | run mkdir /var/lib/tsuru | ||
10 | run tar -xvf basebuilder.tar.gz -C /var/lib/tsuru --strip 1 | ||
11 | run cp /var/lib/tsuru/python/deploy /var/lib/tsuru | ||
12 | run cp /var/lib/tsuru/base/restart /var/lib/tsuru | ||
13 | run cp /var/lib/tsuru/base/start /var/lib/tsuru | ||
14 | run /var/lib/tsuru/base/install | ||
15 | run /var/lib/tsuru/base/setup | ||
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/barfile b/vendor/github.com/fsouza/go-dockerclient/testing/data/barfile new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/barfile | |||
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/ca.pem b/vendor/github.com/fsouza/go-dockerclient/testing/data/ca.pem new file mode 100644 index 0000000..8e38bba --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/ca.pem | |||
@@ -0,0 +1,18 @@ | |||
1 | -----BEGIN CERTIFICATE----- | ||
2 | MIIC1TCCAb+gAwIBAgIQJ9MsNxrUxumNbAytGi3GEDALBgkqhkiG9w0BAQswFjEU | ||
3 | MBIGA1UEChMLQm9vdDJEb2NrZXIwHhcNMTQxMDE2MjAyMTM4WhcNMTcwOTMwMjAy | ||
4 | MTM4WjAWMRQwEgYDVQQKEwtCb290MkRvY2tlcjCCASIwDQYJKoZIhvcNAQEBBQAD | ||
5 | ggEPADCCAQoCggEBALpFCSARjG+5yXoqr7UMzuE0df7RRZfeRZI06lJ02ZqV4Iii | ||
6 | rgL7ML9yPxX50NbLnjiilSDTUhnyocYFItokzUzz8qpX/nlYhuN2Iqwh4d0aWS8z | ||
7 | f5y248F+H1z+HY2W8NPl/6DVlVwYaNW1/k+RPMlHS0INLR6j+3Ievew7RNE0NnM2 | ||
8 | znELW6NetekDt3GUcz0Z95vDUDfdPnIk1eIFMmYvLxZh23xOca4Q37a3S8F3d+dN | ||
9 | +OOpwjdgY9Qme0NQUaXpgp58jWuQfB8q7mZrdnLlLqRa8gx1HeDSotX7UmWtWPkb | ||
10 | vd9EdlKLYw5PVpxMV1rkwf2t4TdgD5NfkpXlXkkCAwEAAaMjMCEwDgYDVR0PAQH/ | ||
11 | BAQDAgCkMA8GA1UdEwEB/wQFMAMBAf8wCwYJKoZIhvcNAQELA4IBAQBxYjHVSKqE | ||
12 | MJw7CW0GddesULtXXVWGJuZdWJLQlPvPMfIfjIvlcZyS4cdVNiQ3sREFIZz8TpII | ||
13 | CT0/Pg3sgv/FcOQe1CN0xZYZcyiAZHK1z0fJQq2qVpdv7+tJcjI2vvU6NI24iQCo | ||
14 | W1wz25trJz9QbdB2MRLMjyz7TSWuafztIvcfEzaIdQ0Whqund/cSuPGQx5IwF83F | ||
15 | rvlkOyJSH2+VIEBTCIuykJeL0DLTt8cePBQR5L1ISXb4RUMK9ZtqRscBRv8sn7o2 | ||
16 | ixG3wtL0gYF4xLtsQWVxI3iFVrU3WzOH/3c5shVRkWBd+AQRSwCJI4mKH7penJCF | ||
17 | i3/zzlkvOnjV | ||
18 | -----END CERTIFICATE----- | ||
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/cert.pem b/vendor/github.com/fsouza/go-dockerclient/testing/data/cert.pem new file mode 100644 index 0000000..5e7244b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/cert.pem | |||
@@ -0,0 +1,18 @@ | |||
1 | -----BEGIN CERTIFICATE----- | ||
2 | MIIC6DCCAdKgAwIBAgIRANO6ymxQAjp66KmEka1G6b0wCwYJKoZIhvcNAQELMBYx | ||
3 | FDASBgNVBAoTC0Jvb3QyRG9ja2VyMB4XDTE0MTAxNjIwMjE1MloXDTE3MDkzMDIw | ||
4 | MjE1MlowFjEUMBIGA1UEChMLQm9vdDJEb2NrZXIwggEiMA0GCSqGSIb3DQEBAQUA | ||
5 | A4IBDwAwggEKAoIBAQDGA1mAhSOpZspD1dpZ7qVEQrIJw4Xo8252jHaORnEdDiFm | ||
6 | b6brEmr6jw8t4P3IGxbqBc/TqRV+SSXxwYEVvfpeQKH+SmqStoMNtD3Ura161az4 | ||
7 | V0BcxMtSlsUGpoz+//QCAq8qiaxMwgiyc5253mkQm88anj2cNt7xbewiu/KFWuf7 | ||
8 | BVpNK1+ltpJmlukfcj/G+I1bw7j1KxBjDrFqe5cyDuuZcDL2tmUXP/ZWDyXwSv+H | ||
9 | AOckqn44z6aXlBkVvOXDBZJqY76d/vWVDNCuZeXRnqlhP3t1kH4V0RQXo+JD2tgt | ||
10 | JgdU0unzyoFOSWNUBPm73tqmjUGGAmGHBmeegJr/AgMBAAGjNTAzMA4GA1UdDwEB | ||
11 | /wQEAwIAgDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMAsGCSqG | ||
12 | SIb3DQEBCwOCAQEABVTWl5SmBP+j5He5bQsgnIXjviSKqe40/10V4LJAOmilycRF | ||
13 | zLrzM+YMwfjg6PLIs8CldAMWHw9y9ktZY4MxkgCktaiaN/QmMTMwFWEcN4wy5IpM | ||
14 | U5l93eAg7xsnY430h3QBBADujX4wdF3fs8rSL8zAAQFL0ihurwU124K3yXKsrwpb | ||
15 | CiVUGfIN4sPwjy8Ws9oxHFDC9/P8lgjHZ1nBIf8KSHnMzlxDGj7isQfhtH+7mcCL | ||
16 | cM1qO2NirS2v7uaEPPY+MJstAz+W7EJCW9dfMSmHna2SDC37Xkin7uEY9z+qaKFL | ||
17 | 8d/XxOB/L8Ucy8VZhdsv0dsBq5KfJntITM0ksQ== | ||
18 | -----END CERTIFICATE----- | ||
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/container.tar b/vendor/github.com/fsouza/go-dockerclient/testing/data/container.tar new file mode 100644 index 0000000..e4b066e --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/container.tar | |||
Binary files differ | |||
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/dockerfile.tar b/vendor/github.com/fsouza/go-dockerclient/testing/data/dockerfile.tar new file mode 100644 index 0000000..32c9ce6 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/dockerfile.tar | |||
Binary files differ | |||
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/foofile b/vendor/github.com/fsouza/go-dockerclient/testing/data/foofile new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/foofile | |||
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/key.pem b/vendor/github.com/fsouza/go-dockerclient/testing/data/key.pem new file mode 100644 index 0000000..a9346bc --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/key.pem | |||
@@ -0,0 +1,27 @@ | |||
1 | -----BEGIN RSA PRIVATE KEY----- | ||
2 | MIIEowIBAAKCAQEAxgNZgIUjqWbKQ9XaWe6lREKyCcOF6PNudox2jkZxHQ4hZm+m | ||
3 | 6xJq+o8PLeD9yBsW6gXP06kVfkkl8cGBFb36XkCh/kpqkraDDbQ91K2tetWs+FdA | ||
4 | XMTLUpbFBqaM/v/0AgKvKomsTMIIsnOdud5pEJvPGp49nDbe8W3sIrvyhVrn+wVa | ||
5 | TStfpbaSZpbpH3I/xviNW8O49SsQYw6xanuXMg7rmXAy9rZlFz/2Vg8l8Er/hwDn | ||
6 | JKp+OM+ml5QZFbzlwwWSamO+nf71lQzQrmXl0Z6pYT97dZB+FdEUF6PiQ9rYLSYH | ||
7 | VNLp88qBTkljVAT5u97apo1BhgJhhwZnnoCa/wIDAQABAoIBAQCaGy9EC9pmU95l | ||
8 | DwGh7k5nIrUnTilg1FwLHWSDdCVCZKXv8ENrPelOWZqJrUo1u4eI2L8XTsewgkNq | ||
9 | tJu/DRzWz9yDaO0qg6rZNobMh+K076lvmZA44twOydJLS8H+D7ua+PXU2FLlZjmY | ||
10 | kMyXRJZmW6zCXZc7haTbJx6ZJccoquk/DkS4FcFurJP177u1YrWS9TTw9kensUtU | ||
11 | jQ63uf56UTN1i+0+Rxl7OW1TZlqwlri5I4njg5249+FxwwHzIq8+l7zD7K9pl8c/ | ||
12 | nG1HuulvU2bVlDlRdyslMPAH34vw9Sku1BD8furrJLr1na5lRSLKJODEaIPEsLwv | ||
13 | CdEUwP9JAoGBAO76ZW80RyNB2fA+wbTq70Sr8CwrXxYemXrez5LKDC7SsohKFCPE | ||
14 | IedpO/n+nmymiiJvMm874EExoG6BVrbkWkeb+2vinEfOQNlDMsDx7WLjPekP3t6i | ||
15 | rXHO3CjFooVFq2z3mZa/Nc5NZqu8fNWNCKJxZDJphdoj6sORNJIUvZVjAoGBANQd | ||
16 | ++J+ITcu3/+A6JrGcgLunBFQYPqkiItk0J4QKYKuX5ik9rWcQDN8TTtfW2mDuiQ4 | ||
17 | NrCwuVPq1V1kB16JzH017SsYLo9g8I20YjnBZge9pKTeUaLVTb3C50LW8FBylop0 | ||
18 | Bnm597dNbtSjphjoTMg0XyC19o3Esf2YeWG0QNS1AoGAWWDfFRNJU99qIldmXULM | ||
19 | 0DM6NVrXSk+ReYnhunXEzrJQwXZrR+EwCPurydk36Uz0NuK9yypquhdUeF/5TZfk | ||
20 | SAoHo5byekyipl9imRUigqyY2BTudvgCxKDoaHtaSFwBPFTyZZYICquaLbrmOXxw | ||
21 | 8UhVgCFFRYvPXuts7QHC0h8CgYBWEvy9gfU0kV7wLX02IUTuj6jhFb7ktpN6DSTi | ||
22 | nyhZES1VoctDEu6ydcRZTW6ouH12aSE4Pd5WgTqntQmQgVZrkNB25k8ue2Xh+srJ | ||
23 | KQOgLIJ9LIHwE6KCWG7DnrjRzE3uTPq7to0g4tkQjH/AJ7PQof/gJDayfJjFkXPg | ||
24 | A+cy6QKBgEPbKpiqscm03gT2QanBut5pg4dqPOxp0SlErA3kSFNTRK3oYBQPC+LH | ||
25 | qA5nD5brdkeNBB58Rll8Zpzxiff50bcvLP/7/Sb3NjaXFTEY0gVbdRof3n6N0YP3 | ||
26 | Hu5XDNJ9RNkNzE5RIG1g86KE+aKlcrKMaigqAiuIy2PSnjkQeGk8 | ||
27 | -----END RSA PRIVATE KEY----- | ||
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/server.pem b/vendor/github.com/fsouza/go-dockerclient/testing/data/server.pem new file mode 100644 index 0000000..89cc445 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/server.pem | |||
@@ -0,0 +1,18 @@ | |||
1 | -----BEGIN CERTIFICATE----- | ||
2 | MIIC/DCCAeagAwIBAgIQMUILcXtvmSOK63zEBo0VXzALBgkqhkiG9w0BAQswFjEU | ||
3 | MBIGA1UEChMLQm9vdDJEb2NrZXIwHhcNMTQxMDE2MjAyMTQ2WhcNMTcwOTMwMjAy | ||
4 | MTQ2WjAWMRQwEgYDVQQKEwtCb290MkRvY2tlcjCCASIwDQYJKoZIhvcNAQEBBQAD | ||
5 | ggEPADCCAQoCggEBANxUOUhNnqFnrTlLsBYzfFRZWQo268l+4K4lOJCVbfDonP3g | ||
6 | Mz0vGi9fcyFqEWSA8Y+ShXna625HTnReCwFdsu0861qCIq7v95hFFCyOe0iIxpd0 | ||
7 | AKLnl90d+1vonE7andgFgoobbTiMly4UK4H6z8D148fFNIihoteOG3PIF89TFxP7 | ||
8 | CJ/3wXnx/IKpdlO8PAnub3tBPJHvGDj7KORLy4IBxRX5VBAdfGNybE66fcrehEva | ||
9 | rLA4m9pgiaR/Nnr9FdKhPyqYdjflLNvzydxNvMIV4M0hFlhXmYvpMjA5/XsTnsyV | ||
10 | t9JHJa5Upwqsbne08t7rsm7liZNxZlko8xPOTQcCAwEAAaNKMEgwDgYDVR0PAQH/ | ||
11 | BAQDAgCgMAwGA1UdEwEB/wQCMAAwKAYDVR0RBCEwH4ILYm9vdDJkb2NrZXKHBH8A | ||
12 | AAGHBAoAAg+HBMCoO2cwCwYJKoZIhvcNAQELA4IBAQAYoYcDkDWkl73FZ0WnPmAj | ||
13 | LiF7HU95Qg3KyEpFsAJeShSLPPbQntmwhdekEzY4tQ3eKQB/+zHFjzsCr/lmDUmH | ||
14 | Ea/ryQ17C+jyH+Ykg0IWW6L6veZhvRDg6Z9focVtPVBRxPTqC/Qhb54blWRASV+W | ||
15 | UreMuXQ5+1dQptAM7ixOeLVHjBi/bd9TL3jvwBVCr9QedteMjjK4TCF9Tbcou+MF | ||
16 | 2w3OJJZMDhcD+YwoK9uJDqlKmcTm/vVMbSsp/pTMcnQ7jxCeR8/XyX+VwTZwaHAa | ||
17 | o92Q/eg3THAiWhvyT/SzyH9dHHBAyXynUwGCggKawHktfvW4QXRPuLxLrJ7iB5cy | ||
18 | -----END CERTIFICATE----- | ||
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/serverkey.pem b/vendor/github.com/fsouza/go-dockerclient/testing/data/serverkey.pem new file mode 100644 index 0000000..c897e5d --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/data/serverkey.pem | |||
@@ -0,0 +1,27 @@ | |||
1 | -----BEGIN RSA PRIVATE KEY----- | ||
2 | MIIEoAIBAAKCAQEA3FQ5SE2eoWetOUuwFjN8VFlZCjbryX7griU4kJVt8Oic/eAz | ||
3 | PS8aL19zIWoRZIDxj5KFedrrbkdOdF4LAV2y7TzrWoIiru/3mEUULI57SIjGl3QA | ||
4 | oueX3R37W+icTtqd2AWCihttOIyXLhQrgfrPwPXjx8U0iKGi144bc8gXz1MXE/sI | ||
5 | n/fBefH8gql2U7w8Ce5ve0E8ke8YOPso5EvLggHFFflUEB18Y3JsTrp9yt6ES9qs | ||
6 | sDib2mCJpH82ev0V0qE/Kph2N+Us2/PJ3E28whXgzSEWWFeZi+kyMDn9exOezJW3 | ||
7 | 0kclrlSnCqxud7Ty3uuybuWJk3FmWSjzE85NBwIDAQABAoIBAG0ak+cW8LeShHf7 | ||
8 | 3+2Of0GxoOLrAWWdG5uAuPr31CJYve0FybnBimDtDjD8ujIfm/7xmoEWBEFutA3x | ||
9 | x9dcU88gvJbsHEqub9gKVQwfXjMz78tt2SbSMiR/xUnk7QorPcCMMfE71aEMFYzu | ||
10 | 1gCed6Rg3vO81t/V0rKVH0j9S7UQz5v/oX15eVDV5LOqyCHwAi6K0eXXbqnbI0TH | ||
11 | SOQ/nexM2msVXWbO9t6ra6f5V7FXziDK5Xi+rPxRbX9mkrDzxDAevfuRqYBx5vtL | ||
12 | W2Q2hKjUAHFgXFniNSZBS7dCdAtz0el/3ct+cNmpuTMhhs7M6wC1CuYiZ/DxLiFh | ||
13 | Si73VckCgYEA+/ceh3+VjtQ0rgEw8sD9bqYEA8IaBiObjneIoFnKBYRG7yZd8JMm | ||
14 | HD4M/aQ1qhcRLPN7GR03YQULgQJURbKSjJHnhfTXHyeHC3NN4gMVHQXewu2MHCh6 | ||
15 | 7FCQ9CfK0KcYLgegVVvL3PrF3hyWGnmTu+G0UkDQRYVnaNrB7snrW6UCgYEA39tq | ||
16 | +MCQdu0moJ5szSZf02undg9EeW6isk9qzi7TId3/MLci2eH7PEnipipPUK3+DERq | ||
17 | aba0y0TKgBR2EXvXLFJA/+kfdo2loIEHOfox85HVfxgUaFRti63ZI0uF8D0QT2Yy | ||
18 | oJal+RFghVoSnv4LjhRKEPbIkScTXGjdK+7wFjsCfz79iKRXQQx0ALd/lL0bgkAn | ||
19 | QNmvrNHcFQeI2p8700WNzC39aX67SsvEt3qxkrjzC1gxhpTAuReIK1gVPPwvqHN8 | ||
20 | BmV20FD5kMlMCix2mNCopwgUWvKvLAvoGFTxncKMA39+aJbuXAjiqJTekKgNvOE7 | ||
21 | i9kEWw0GTNPp3JHV6QECgYAPwb0M11kT1euDIMOdyRazpf86kyaJuZzgGjD1ZFxe | ||
22 | JOcigbGFTp/FhZnbglzk2+pm6KXo3QBq0mPCki4hWusxZnTGzpz1VlETNCHTFeZQ | ||
23 | M7KoaIR/N3oie9Et59H8r/+m5xWnMhNqratyl316DX24uXrhKM3DUdHODl+LCR2D | ||
24 | IwKBgE1MbHuwolUPEw3HeO4R7NMFVTFei7E/fpUsimPfArGg8UydwvloNT1myJos | ||
25 | N2JzfGGjN2KPVcBk9fOs71mJ6VcK3C3g5JIccplk6h9VNaw55+zdQvKPTzoBoTvy | ||
26 | A+Fwx2AlF61KeRF87DL2YTRJ6B9MHmWgf7+GVZOxomLgEAcZ | ||
27 | -----END RSA PRIVATE KEY----- | ||
diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/server.go b/vendor/github.com/fsouza/go-dockerclient/testing/server.go new file mode 100644 index 0000000..7b65a8c --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/testing/server.go | |||
@@ -0,0 +1,1334 @@ | |||
1 | // Copyright 2015 go-dockerclient authors. All rights reserved. | ||
2 | // Use of this source code is governed by a BSD-style | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | // Package testing provides a fake implementation of the Docker API, useful for | ||
6 | // testing purpose. | ||
7 | package testing | ||
8 | |||
9 | import ( | ||
10 | "archive/tar" | ||
11 | "crypto/rand" | ||
12 | "encoding/json" | ||
13 | "errors" | ||
14 | "fmt" | ||
15 | "io/ioutil" | ||
16 | mathrand "math/rand" | ||
17 | "net" | ||
18 | "net/http" | ||
19 | "regexp" | ||
20 | "strconv" | ||
21 | "strings" | ||
22 | "sync" | ||
23 | "time" | ||
24 | |||
25 | "github.com/fsouza/go-dockerclient" | ||
26 | "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy" | ||
27 | "github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux" | ||
28 | ) | ||
29 | |||
30 | var nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`) | ||
31 | |||
32 | // DockerServer represents a programmable, concurrent (not much), HTTP server | ||
33 | // implementing a fake version of the Docker remote API. | ||
34 | // | ||
35 | // It can used in standalone mode, listening for connections or as an arbitrary | ||
36 | // HTTP handler. | ||
37 | // | ||
38 | // For more details on the remote API, check http://goo.gl/G3plxW. | ||
39 | type DockerServer struct { | ||
40 | containers []*docker.Container | ||
41 | uploadedFiles map[string]string | ||
42 | execs []*docker.ExecInspect | ||
43 | execMut sync.RWMutex | ||
44 | cMut sync.RWMutex | ||
45 | images []docker.Image | ||
46 | iMut sync.RWMutex | ||
47 | imgIDs map[string]string | ||
48 | networks []*docker.Network | ||
49 | netMut sync.RWMutex | ||
50 | listener net.Listener | ||
51 | mux *mux.Router | ||
52 | hook func(*http.Request) | ||
53 | failures map[string]string | ||
54 | multiFailures []map[string]string | ||
55 | execCallbacks map[string]func() | ||
56 | statsCallbacks map[string]func(string) docker.Stats | ||
57 | customHandlers map[string]http.Handler | ||
58 | handlerMutex sync.RWMutex | ||
59 | cChan chan<- *docker.Container | ||
60 | volStore map[string]*volumeCounter | ||
61 | volMut sync.RWMutex | ||
62 | } | ||
63 | |||
64 | type volumeCounter struct { | ||
65 | volume docker.Volume | ||
66 | count int | ||
67 | } | ||
68 | |||
69 | // NewServer returns a new instance of the fake server, in standalone mode. Use | ||
70 | // the method URL to get the URL of the server. | ||
71 | // | ||
72 | // It receives the bind address (use 127.0.0.1:0 for getting an available port | ||
73 | // on the host), a channel of containers and a hook function, that will be | ||
74 | // called on every request. | ||
75 | // | ||
76 | // The fake server will send containers in the channel whenever the container | ||
77 | // changes its state, via the HTTP API (i.e.: create, start and stop). This | ||
78 | // channel may be nil, which means that the server won't notify on state | ||
79 | // changes. | ||
80 | func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request)) (*DockerServer, error) { | ||
81 | listener, err := net.Listen("tcp", bind) | ||
82 | if err != nil { | ||
83 | return nil, err | ||
84 | } | ||
85 | server := DockerServer{ | ||
86 | listener: listener, | ||
87 | imgIDs: make(map[string]string), | ||
88 | hook: hook, | ||
89 | failures: make(map[string]string), | ||
90 | execCallbacks: make(map[string]func()), | ||
91 | statsCallbacks: make(map[string]func(string) docker.Stats), | ||
92 | customHandlers: make(map[string]http.Handler), | ||
93 | uploadedFiles: make(map[string]string), | ||
94 | cChan: containerChan, | ||
95 | } | ||
96 | server.buildMuxer() | ||
97 | go http.Serve(listener, &server) | ||
98 | return &server, nil | ||
99 | } | ||
100 | |||
101 | func (s *DockerServer) notify(container *docker.Container) { | ||
102 | if s.cChan != nil { | ||
103 | s.cChan <- container | ||
104 | } | ||
105 | } | ||
106 | |||
107 | func (s *DockerServer) buildMuxer() { | ||
108 | s.mux = mux.NewRouter() | ||
109 | s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer)) | ||
110 | s.mux.Path("/containers/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers)) | ||
111 | s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer)) | ||
112 | s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer)) | ||
113 | s.mux.Path("/containers/{id:.*}/rename").Methods("POST").HandlerFunc(s.handlerWrapper(s.renameContainer)) | ||
114 | s.mux.Path("/containers/{id:.*}/top").Methods("GET").HandlerFunc(s.handlerWrapper(s.topContainer)) | ||
115 | s.mux.Path("/containers/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer)) | ||
116 | s.mux.Path("/containers/{id:.*}/kill").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer)) | ||
117 | s.mux.Path("/containers/{id:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer)) | ||
118 | s.mux.Path("/containers/{id:.*}/pause").Methods("POST").HandlerFunc(s.handlerWrapper(s.pauseContainer)) | ||
119 | s.mux.Path("/containers/{id:.*}/unpause").Methods("POST").HandlerFunc(s.handlerWrapper(s.unpauseContainer)) | ||
120 | s.mux.Path("/containers/{id:.*}/wait").Methods("POST").HandlerFunc(s.handlerWrapper(s.waitContainer)) | ||
121 | s.mux.Path("/containers/{id:.*}/attach").Methods("POST").HandlerFunc(s.handlerWrapper(s.attachContainer)) | ||
122 | s.mux.Path("/containers/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer)) | ||
123 | s.mux.Path("/containers/{id:.*}/exec").Methods("POST").HandlerFunc(s.handlerWrapper(s.createExecContainer)) | ||
124 | s.mux.Path("/containers/{id:.*}/stats").Methods("GET").HandlerFunc(s.handlerWrapper(s.statsContainer)) | ||
125 | s.mux.Path("/containers/{id:.*}/archive").Methods("PUT").HandlerFunc(s.handlerWrapper(s.uploadToContainer)) | ||
126 | s.mux.Path("/exec/{id:.*}/resize").Methods("POST").HandlerFunc(s.handlerWrapper(s.resizeExecContainer)) | ||
127 | s.mux.Path("/exec/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startExecContainer)) | ||
128 | s.mux.Path("/exec/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectExecContainer)) | ||
129 | s.mux.Path("/images/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.pullImage)) | ||
130 | s.mux.Path("/build").Methods("POST").HandlerFunc(s.handlerWrapper(s.buildImage)) | ||
131 | s.mux.Path("/images/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listImages)) | ||
132 | s.mux.Path("/images/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeImage)) | ||
133 | s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage)) | ||
134 | s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage)) | ||
135 | s.mux.Path("/images/{name:.*}/tag").Methods("POST").HandlerFunc(s.handlerWrapper(s.tagImage)) | ||
136 | s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents) | ||
137 | s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker)) | ||
138 | s.mux.Path("/images/load").Methods("POST").HandlerFunc(s.handlerWrapper(s.loadImage)) | ||
139 | s.mux.Path("/images/{id:.*}/get").Methods("GET").HandlerFunc(s.handlerWrapper(s.getImage)) | ||
140 | s.mux.Path("/networks").Methods("GET").HandlerFunc(s.handlerWrapper(s.listNetworks)) | ||
141 | s.mux.Path("/networks/{id:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.networkInfo)) | ||
142 | s.mux.Path("/networks").Methods("POST").HandlerFunc(s.handlerWrapper(s.createNetwork)) | ||
143 | s.mux.Path("/volumes").Methods("GET").HandlerFunc(s.handlerWrapper(s.listVolumes)) | ||
144 | s.mux.Path("/volumes/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createVolume)) | ||
145 | s.mux.Path("/volumes/{name:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectVolume)) | ||
146 | s.mux.Path("/volumes/{name:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeVolume)) | ||
147 | s.mux.Path("/info").Methods("GET").HandlerFunc(s.handlerWrapper(s.infoDocker)) | ||
148 | } | ||
149 | |||
150 | // SetHook changes the hook function used by the server. | ||
151 | // | ||
152 | // The hook function is a function called on every request. | ||
153 | func (s *DockerServer) SetHook(hook func(*http.Request)) { | ||
154 | s.hook = hook | ||
155 | } | ||
156 | |||
157 | // PrepareExec adds a callback to a container exec in the fake server. | ||
158 | // | ||
159 | // This function will be called whenever the given exec id is started, and the | ||
160 | // given exec id will remain in the "Running" start while the function is | ||
161 | // running, so it's useful for emulating an exec that runs for two seconds, for | ||
162 | // example: | ||
163 | // | ||
164 | // opts := docker.CreateExecOptions{ | ||
165 | // AttachStdin: true, | ||
166 | // AttachStdout: true, | ||
167 | // AttachStderr: true, | ||
168 | // Tty: true, | ||
169 | // Cmd: []string{"/bin/bash", "-l"}, | ||
170 | // } | ||
171 | // // Client points to a fake server. | ||
172 | // exec, err := client.CreateExec(opts) | ||
173 | // // handle error | ||
174 | // server.PrepareExec(exec.ID, func() {time.Sleep(2 * time.Second)}) | ||
175 | // err = client.StartExec(exec.ID, docker.StartExecOptions{Tty: true}) // will block for 2 seconds | ||
176 | // // handle error | ||
177 | func (s *DockerServer) PrepareExec(id string, callback func()) { | ||
178 | s.execCallbacks[id] = callback | ||
179 | } | ||
180 | |||
181 | // PrepareStats adds a callback that will be called for each container stats | ||
182 | // call. | ||
183 | // | ||
184 | // This callback function will be called multiple times if stream is set to | ||
185 | // true when stats is called. | ||
186 | func (s *DockerServer) PrepareStats(id string, callback func(string) docker.Stats) { | ||
187 | s.statsCallbacks[id] = callback | ||
188 | } | ||
189 | |||
190 | // PrepareFailure adds a new expected failure based on a URL regexp it receives | ||
191 | // an id for the failure. | ||
192 | func (s *DockerServer) PrepareFailure(id string, urlRegexp string) { | ||
193 | s.failures[id] = urlRegexp | ||
194 | } | ||
195 | |||
196 | // PrepareMultiFailures enqueues a new expected failure based on a URL regexp | ||
197 | // it receives an id for the failure. | ||
198 | func (s *DockerServer) PrepareMultiFailures(id string, urlRegexp string) { | ||
199 | s.multiFailures = append(s.multiFailures, map[string]string{"error": id, "url": urlRegexp}) | ||
200 | } | ||
201 | |||
202 | // ResetFailure removes an expected failure identified by the given id. | ||
203 | func (s *DockerServer) ResetFailure(id string) { | ||
204 | delete(s.failures, id) | ||
205 | } | ||
206 | |||
207 | // ResetMultiFailures removes all enqueued failures. | ||
208 | func (s *DockerServer) ResetMultiFailures() { | ||
209 | s.multiFailures = []map[string]string{} | ||
210 | } | ||
211 | |||
212 | // CustomHandler registers a custom handler for a specific path. | ||
213 | // | ||
214 | // For example: | ||
215 | // | ||
216 | // server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
217 | // http.Error(w, "Something wrong is not right", http.StatusInternalServerError) | ||
218 | // })) | ||
219 | func (s *DockerServer) CustomHandler(path string, handler http.Handler) { | ||
220 | s.handlerMutex.Lock() | ||
221 | s.customHandlers[path] = handler | ||
222 | s.handlerMutex.Unlock() | ||
223 | } | ||
224 | |||
225 | // MutateContainer changes the state of a container, returning an error if the | ||
226 | // given id does not match to any container "running" in the server. | ||
227 | func (s *DockerServer) MutateContainer(id string, state docker.State) error { | ||
228 | for _, container := range s.containers { | ||
229 | if container.ID == id { | ||
230 | container.State = state | ||
231 | return nil | ||
232 | } | ||
233 | } | ||
234 | return errors.New("container not found") | ||
235 | } | ||
236 | |||
237 | // Stop stops the server. | ||
238 | func (s *DockerServer) Stop() { | ||
239 | if s.listener != nil { | ||
240 | s.listener.Close() | ||
241 | } | ||
242 | } | ||
243 | |||
244 | // URL returns the HTTP URL of the server. | ||
245 | func (s *DockerServer) URL() string { | ||
246 | if s.listener == nil { | ||
247 | return "" | ||
248 | } | ||
249 | return "http://" + s.listener.Addr().String() + "/" | ||
250 | } | ||
251 | |||
252 | // ServeHTTP handles HTTP requests sent to the server. | ||
253 | func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
254 | s.handlerMutex.RLock() | ||
255 | defer s.handlerMutex.RUnlock() | ||
256 | for re, handler := range s.customHandlers { | ||
257 | if m, _ := regexp.MatchString(re, r.URL.Path); m { | ||
258 | handler.ServeHTTP(w, r) | ||
259 | return | ||
260 | } | ||
261 | } | ||
262 | s.mux.ServeHTTP(w, r) | ||
263 | if s.hook != nil { | ||
264 | s.hook(r) | ||
265 | } | ||
266 | } | ||
267 | |||
268 | // DefaultHandler returns default http.Handler mux, it allows customHandlers to | ||
269 | // call the default behavior if wanted. | ||
270 | func (s *DockerServer) DefaultHandler() http.Handler { | ||
271 | return s.mux | ||
272 | } | ||
273 | |||
274 | func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { | ||
275 | return func(w http.ResponseWriter, r *http.Request) { | ||
276 | for errorID, urlRegexp := range s.failures { | ||
277 | matched, err := regexp.MatchString(urlRegexp, r.URL.Path) | ||
278 | if err != nil { | ||
279 | http.Error(w, err.Error(), http.StatusBadRequest) | ||
280 | return | ||
281 | } | ||
282 | if !matched { | ||
283 | continue | ||
284 | } | ||
285 | http.Error(w, errorID, http.StatusBadRequest) | ||
286 | return | ||
287 | } | ||
288 | for i, failure := range s.multiFailures { | ||
289 | matched, err := regexp.MatchString(failure["url"], r.URL.Path) | ||
290 | if err != nil { | ||
291 | http.Error(w, err.Error(), http.StatusBadRequest) | ||
292 | return | ||
293 | } | ||
294 | if !matched { | ||
295 | continue | ||
296 | } | ||
297 | http.Error(w, failure["error"], http.StatusBadRequest) | ||
298 | s.multiFailures = append(s.multiFailures[:i], s.multiFailures[i+1:]...) | ||
299 | return | ||
300 | } | ||
301 | f(w, r) | ||
302 | } | ||
303 | } | ||
304 | |||
305 | func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) { | ||
306 | all := r.URL.Query().Get("all") | ||
307 | s.cMut.RLock() | ||
308 | result := make([]docker.APIContainers, 0, len(s.containers)) | ||
309 | for _, container := range s.containers { | ||
310 | if all == "1" || container.State.Running { | ||
311 | result = append(result, docker.APIContainers{ | ||
312 | ID: container.ID, | ||
313 | Image: container.Image, | ||
314 | Command: fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")), | ||
315 | Created: container.Created.Unix(), | ||
316 | Status: container.State.String(), | ||
317 | Ports: container.NetworkSettings.PortMappingAPI(), | ||
318 | Names: []string{fmt.Sprintf("/%s", container.Name)}, | ||
319 | }) | ||
320 | } | ||
321 | } | ||
322 | s.cMut.RUnlock() | ||
323 | w.Header().Set("Content-Type", "application/json") | ||
324 | w.WriteHeader(http.StatusOK) | ||
325 | json.NewEncoder(w).Encode(result) | ||
326 | } | ||
327 | |||
328 | func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) { | ||
329 | s.cMut.RLock() | ||
330 | result := make([]docker.APIImages, len(s.images)) | ||
331 | for i, image := range s.images { | ||
332 | result[i] = docker.APIImages{ | ||
333 | ID: image.ID, | ||
334 | Created: image.Created.Unix(), | ||
335 | } | ||
336 | for tag, id := range s.imgIDs { | ||
337 | if id == image.ID { | ||
338 | result[i].RepoTags = append(result[i].RepoTags, tag) | ||
339 | } | ||
340 | } | ||
341 | } | ||
342 | s.cMut.RUnlock() | ||
343 | w.Header().Set("Content-Type", "application/json") | ||
344 | w.WriteHeader(http.StatusOK) | ||
345 | json.NewEncoder(w).Encode(result) | ||
346 | } | ||
347 | |||
348 | func (s *DockerServer) findImage(id string) (string, error) { | ||
349 | s.iMut.RLock() | ||
350 | defer s.iMut.RUnlock() | ||
351 | image, ok := s.imgIDs[id] | ||
352 | if ok { | ||
353 | return image, nil | ||
354 | } | ||
355 | image, _, err := s.findImageByID(id) | ||
356 | return image, err | ||
357 | } | ||
358 | |||
359 | func (s *DockerServer) findImageByID(id string) (string, int, error) { | ||
360 | s.iMut.RLock() | ||
361 | defer s.iMut.RUnlock() | ||
362 | for i, image := range s.images { | ||
363 | if image.ID == id { | ||
364 | return image.ID, i, nil | ||
365 | } | ||
366 | } | ||
367 | return "", -1, errors.New("No such image") | ||
368 | } | ||
369 | |||
370 | func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { | ||
371 | var config struct { | ||
372 | *docker.Config | ||
373 | HostConfig *docker.HostConfig | ||
374 | } | ||
375 | defer r.Body.Close() | ||
376 | err := json.NewDecoder(r.Body).Decode(&config) | ||
377 | if err != nil { | ||
378 | http.Error(w, err.Error(), http.StatusBadRequest) | ||
379 | return | ||
380 | } | ||
381 | name := r.URL.Query().Get("name") | ||
382 | if name != "" && !nameRegexp.MatchString(name) { | ||
383 | http.Error(w, "Invalid container name", http.StatusInternalServerError) | ||
384 | return | ||
385 | } | ||
386 | if _, err := s.findImage(config.Image); err != nil { | ||
387 | http.Error(w, err.Error(), http.StatusNotFound) | ||
388 | return | ||
389 | } | ||
390 | ports := map[docker.Port][]docker.PortBinding{} | ||
391 | for port := range config.ExposedPorts { | ||
392 | ports[port] = []docker.PortBinding{{ | ||
393 | HostIP: "0.0.0.0", | ||
394 | HostPort: strconv.Itoa(mathrand.Int() % 0xffff), | ||
395 | }} | ||
396 | } | ||
397 | |||
398 | //the container may not have cmd when using a Dockerfile | ||
399 | var path string | ||
400 | var args []string | ||
401 | if len(config.Cmd) == 1 { | ||
402 | path = config.Cmd[0] | ||
403 | } else if len(config.Cmd) > 1 { | ||
404 | path = config.Cmd[0] | ||
405 | args = config.Cmd[1:] | ||
406 | } | ||
407 | |||
408 | generatedID := s.generateID() | ||
409 | config.Config.Hostname = generatedID[:12] | ||
410 | container := docker.Container{ | ||
411 | Name: name, | ||
412 | ID: generatedID, | ||
413 | Created: time.Now(), | ||
414 | Path: path, | ||
415 | Args: args, | ||
416 | Config: config.Config, | ||
417 | HostConfig: config.HostConfig, | ||
418 | State: docker.State{ | ||
419 | Running: false, | ||
420 | Pid: mathrand.Int() % 50000, | ||
421 | ExitCode: 0, | ||
422 | StartedAt: time.Now(), | ||
423 | }, | ||
424 | Image: config.Image, | ||
425 | NetworkSettings: &docker.NetworkSettings{ | ||
426 | IPAddress: fmt.Sprintf("172.16.42.%d", mathrand.Int()%250+2), | ||
427 | IPPrefixLen: 24, | ||
428 | Gateway: "172.16.42.1", | ||
429 | Bridge: "docker0", | ||
430 | Ports: ports, | ||
431 | }, | ||
432 | } | ||
433 | s.cMut.Lock() | ||
434 | if container.Name != "" { | ||
435 | for _, c := range s.containers { | ||
436 | if c.Name == container.Name { | ||
437 | defer s.cMut.Unlock() | ||
438 | http.Error(w, "there's already a container with this name", http.StatusConflict) | ||
439 | return | ||
440 | } | ||
441 | } | ||
442 | } | ||
443 | s.containers = append(s.containers, &container) | ||
444 | s.cMut.Unlock() | ||
445 | w.WriteHeader(http.StatusCreated) | ||
446 | s.notify(&container) | ||
447 | |||
448 | json.NewEncoder(w).Encode(container) | ||
449 | } | ||
450 | |||
451 | func (s *DockerServer) generateID() string { | ||
452 | var buf [16]byte | ||
453 | rand.Read(buf[:]) | ||
454 | return fmt.Sprintf("%x", buf) | ||
455 | } | ||
456 | |||
457 | func (s *DockerServer) renameContainer(w http.ResponseWriter, r *http.Request) { | ||
458 | id := mux.Vars(r)["id"] | ||
459 | container, index, err := s.findContainer(id) | ||
460 | if err != nil { | ||
461 | http.Error(w, err.Error(), http.StatusNotFound) | ||
462 | return | ||
463 | } | ||
464 | copy := *container | ||
465 | copy.Name = r.URL.Query().Get("name") | ||
466 | s.cMut.Lock() | ||
467 | defer s.cMut.Unlock() | ||
468 | if s.containers[index].ID == copy.ID { | ||
469 | s.containers[index] = © | ||
470 | } | ||
471 | w.WriteHeader(http.StatusNoContent) | ||
472 | } | ||
473 | |||
474 | func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) { | ||
475 | id := mux.Vars(r)["id"] | ||
476 | container, _, err := s.findContainer(id) | ||
477 | if err != nil { | ||
478 | http.Error(w, err.Error(), http.StatusNotFound) | ||
479 | return | ||
480 | } | ||
481 | w.Header().Set("Content-Type", "application/json") | ||
482 | w.WriteHeader(http.StatusOK) | ||
483 | json.NewEncoder(w).Encode(container) | ||
484 | } | ||
485 | |||
486 | func (s *DockerServer) statsContainer(w http.ResponseWriter, r *http.Request) { | ||
487 | id := mux.Vars(r)["id"] | ||
488 | _, _, err := s.findContainer(id) | ||
489 | if err != nil { | ||
490 | http.Error(w, err.Error(), http.StatusNotFound) | ||
491 | return | ||
492 | } | ||
493 | stream, _ := strconv.ParseBool(r.URL.Query().Get("stream")) | ||
494 | callback := s.statsCallbacks[id] | ||
495 | w.Header().Set("Content-Type", "application/json") | ||
496 | w.WriteHeader(http.StatusOK) | ||
497 | encoder := json.NewEncoder(w) | ||
498 | for { | ||
499 | var stats docker.Stats | ||
500 | if callback != nil { | ||
501 | stats = callback(id) | ||
502 | } | ||
503 | encoder.Encode(stats) | ||
504 | if !stream { | ||
505 | break | ||
506 | } | ||
507 | } | ||
508 | } | ||
509 | |||
510 | func (s *DockerServer) uploadToContainer(w http.ResponseWriter, r *http.Request) { | ||
511 | id := mux.Vars(r)["id"] | ||
512 | container, _, err := s.findContainer(id) | ||
513 | if err != nil { | ||
514 | http.Error(w, err.Error(), http.StatusNotFound) | ||
515 | return | ||
516 | } | ||
517 | if !container.State.Running { | ||
518 | w.WriteHeader(http.StatusInternalServerError) | ||
519 | fmt.Fprintf(w, "Container %s is not running", id) | ||
520 | return | ||
521 | } | ||
522 | path := r.URL.Query().Get("path") | ||
523 | s.uploadedFiles[id] = path | ||
524 | w.WriteHeader(http.StatusOK) | ||
525 | } | ||
526 | |||
527 | func (s *DockerServer) topContainer(w http.ResponseWriter, r *http.Request) { | ||
528 | id := mux.Vars(r)["id"] | ||
529 | container, _, err := s.findContainer(id) | ||
530 | if err != nil { | ||
531 | http.Error(w, err.Error(), http.StatusNotFound) | ||
532 | return | ||
533 | } | ||
534 | if !container.State.Running { | ||
535 | w.WriteHeader(http.StatusInternalServerError) | ||
536 | fmt.Fprintf(w, "Container %s is not running", id) | ||
537 | return | ||
538 | } | ||
539 | w.Header().Set("Content-Type", "application/json") | ||
540 | w.WriteHeader(http.StatusOK) | ||
541 | result := docker.TopResult{ | ||
542 | Titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"}, | ||
543 | Processes: [][]string{ | ||
544 | {"root", "7535", "7516", "0", "03:20", "?", "00:00:00", container.Path + " " + strings.Join(container.Args, " ")}, | ||
545 | }, | ||
546 | } | ||
547 | json.NewEncoder(w).Encode(result) | ||
548 | } | ||
549 | |||
550 | func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) { | ||
551 | id := mux.Vars(r)["id"] | ||
552 | container, _, err := s.findContainer(id) | ||
553 | if err != nil { | ||
554 | http.Error(w, err.Error(), http.StatusNotFound) | ||
555 | return | ||
556 | } | ||
557 | s.cMut.Lock() | ||
558 | defer s.cMut.Unlock() | ||
559 | defer r.Body.Close() | ||
560 | var hostConfig docker.HostConfig | ||
561 | err = json.NewDecoder(r.Body).Decode(&hostConfig) | ||
562 | if err != nil { | ||
563 | http.Error(w, err.Error(), http.StatusInternalServerError) | ||
564 | return | ||
565 | } | ||
566 | container.HostConfig = &hostConfig | ||
567 | if len(hostConfig.PortBindings) > 0 { | ||
568 | ports := map[docker.Port][]docker.PortBinding{} | ||
569 | for key, items := range hostConfig.PortBindings { | ||
570 | bindings := make([]docker.PortBinding, len(items)) | ||
571 | for i := range items { | ||
572 | binding := docker.PortBinding{ | ||
573 | HostIP: items[i].HostIP, | ||
574 | HostPort: items[i].HostPort, | ||
575 | } | ||
576 | if binding.HostIP == "" { | ||
577 | binding.HostIP = "0.0.0.0" | ||
578 | } | ||
579 | if binding.HostPort == "" { | ||
580 | binding.HostPort = strconv.Itoa(mathrand.Int() % 0xffff) | ||
581 | } | ||
582 | bindings[i] = binding | ||
583 | } | ||
584 | ports[key] = bindings | ||
585 | } | ||
586 | container.NetworkSettings.Ports = ports | ||
587 | } | ||
588 | if container.State.Running { | ||
589 | http.Error(w, "", http.StatusNotModified) | ||
590 | return | ||
591 | } | ||
592 | container.State.Running = true | ||
593 | s.notify(container) | ||
594 | } | ||
595 | |||
596 | func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) { | ||
597 | id := mux.Vars(r)["id"] | ||
598 | container, _, err := s.findContainer(id) | ||
599 | if err != nil { | ||
600 | http.Error(w, err.Error(), http.StatusNotFound) | ||
601 | return | ||
602 | } | ||
603 | s.cMut.Lock() | ||
604 | defer s.cMut.Unlock() | ||
605 | if !container.State.Running { | ||
606 | http.Error(w, "Container not running", http.StatusBadRequest) | ||
607 | return | ||
608 | } | ||
609 | w.WriteHeader(http.StatusNoContent) | ||
610 | container.State.Running = false | ||
611 | s.notify(container) | ||
612 | } | ||
613 | |||
614 | func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) { | ||
615 | id := mux.Vars(r)["id"] | ||
616 | container, _, err := s.findContainer(id) | ||
617 | if err != nil { | ||
618 | http.Error(w, err.Error(), http.StatusNotFound) | ||
619 | return | ||
620 | } | ||
621 | s.cMut.Lock() | ||
622 | defer s.cMut.Unlock() | ||
623 | if container.State.Paused { | ||
624 | http.Error(w, "Container already paused", http.StatusBadRequest) | ||
625 | return | ||
626 | } | ||
627 | w.WriteHeader(http.StatusNoContent) | ||
628 | container.State.Paused = true | ||
629 | } | ||
630 | |||
631 | func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) { | ||
632 | id := mux.Vars(r)["id"] | ||
633 | container, _, err := s.findContainer(id) | ||
634 | if err != nil { | ||
635 | http.Error(w, err.Error(), http.StatusNotFound) | ||
636 | return | ||
637 | } | ||
638 | s.cMut.Lock() | ||
639 | defer s.cMut.Unlock() | ||
640 | if !container.State.Paused { | ||
641 | http.Error(w, "Container not paused", http.StatusBadRequest) | ||
642 | return | ||
643 | } | ||
644 | w.WriteHeader(http.StatusNoContent) | ||
645 | container.State.Paused = false | ||
646 | } | ||
647 | |||
648 | func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) { | ||
649 | id := mux.Vars(r)["id"] | ||
650 | container, _, err := s.findContainer(id) | ||
651 | if err != nil { | ||
652 | http.Error(w, err.Error(), http.StatusNotFound) | ||
653 | return | ||
654 | } | ||
655 | hijacker, ok := w.(http.Hijacker) | ||
656 | if !ok { | ||
657 | http.Error(w, "cannot hijack connection", http.StatusInternalServerError) | ||
658 | return | ||
659 | } | ||
660 | w.Header().Set("Content-Type", "application/vnd.docker.raw-stream") | ||
661 | w.WriteHeader(http.StatusOK) | ||
662 | conn, _, err := hijacker.Hijack() | ||
663 | if err != nil { | ||
664 | http.Error(w, err.Error(), http.StatusInternalServerError) | ||
665 | return | ||
666 | } | ||
667 | wg := sync.WaitGroup{} | ||
668 | if r.URL.Query().Get("stdin") == "1" { | ||
669 | wg.Add(1) | ||
670 | go func() { | ||
671 | ioutil.ReadAll(conn) | ||
672 | wg.Done() | ||
673 | }() | ||
674 | } | ||
675 | outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout) | ||
676 | if container.State.Running { | ||
677 | fmt.Fprintf(outStream, "Container is running\n") | ||
678 | } else { | ||
679 | fmt.Fprintf(outStream, "Container is not running\n") | ||
680 | } | ||
681 | fmt.Fprintln(outStream, "What happened?") | ||
682 | fmt.Fprintln(outStream, "Something happened") | ||
683 | wg.Wait() | ||
684 | if r.URL.Query().Get("stream") == "1" { | ||
685 | for { | ||
686 | time.Sleep(1e6) | ||
687 | s.cMut.RLock() | ||
688 | if !container.State.Running { | ||
689 | s.cMut.RUnlock() | ||
690 | break | ||
691 | } | ||
692 | s.cMut.RUnlock() | ||
693 | } | ||
694 | } | ||
695 | conn.Close() | ||
696 | } | ||
697 | |||
698 | func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) { | ||
699 | id := mux.Vars(r)["id"] | ||
700 | container, _, err := s.findContainer(id) | ||
701 | if err != nil { | ||
702 | http.Error(w, err.Error(), http.StatusNotFound) | ||
703 | return | ||
704 | } | ||
705 | for { | ||
706 | time.Sleep(1e6) | ||
707 | s.cMut.RLock() | ||
708 | if !container.State.Running { | ||
709 | s.cMut.RUnlock() | ||
710 | break | ||
711 | } | ||
712 | s.cMut.RUnlock() | ||
713 | } | ||
714 | result := map[string]int{"StatusCode": container.State.ExitCode} | ||
715 | json.NewEncoder(w).Encode(result) | ||
716 | } | ||
717 | |||
718 | func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { | ||
719 | id := mux.Vars(r)["id"] | ||
720 | force := r.URL.Query().Get("force") | ||
721 | s.cMut.Lock() | ||
722 | defer s.cMut.Unlock() | ||
723 | container, index, err := s.findContainerWithLock(id, false) | ||
724 | if err != nil { | ||
725 | http.Error(w, err.Error(), http.StatusNotFound) | ||
726 | return | ||
727 | } | ||
728 | if container.State.Running && force != "1" { | ||
729 | msg := "Error: API error (406): Impossible to remove a running container, please stop it first" | ||
730 | http.Error(w, msg, http.StatusInternalServerError) | ||
731 | return | ||
732 | } | ||
733 | w.WriteHeader(http.StatusNoContent) | ||
734 | s.containers[index] = s.containers[len(s.containers)-1] | ||
735 | s.containers = s.containers[:len(s.containers)-1] | ||
736 | } | ||
737 | |||
738 | func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { | ||
739 | id := r.URL.Query().Get("container") | ||
740 | container, _, err := s.findContainer(id) | ||
741 | if err != nil { | ||
742 | http.Error(w, err.Error(), http.StatusNotFound) | ||
743 | return | ||
744 | } | ||
745 | config := new(docker.Config) | ||
746 | runConfig := r.URL.Query().Get("run") | ||
747 | if runConfig != "" { | ||
748 | err = json.Unmarshal([]byte(runConfig), config) | ||
749 | if err != nil { | ||
750 | http.Error(w, err.Error(), http.StatusBadRequest) | ||
751 | return | ||
752 | } | ||
753 | } | ||
754 | w.WriteHeader(http.StatusOK) | ||
755 | image := docker.Image{ | ||
756 | ID: "img-" + container.ID, | ||
757 | Parent: container.Image, | ||
758 | Container: container.ID, | ||
759 | Comment: r.URL.Query().Get("m"), | ||
760 | Author: r.URL.Query().Get("author"), | ||
761 | Config: config, | ||
762 | } | ||
763 | repository := r.URL.Query().Get("repo") | ||
764 | tag := r.URL.Query().Get("tag") | ||
765 | s.iMut.Lock() | ||
766 | s.images = append(s.images, image) | ||
767 | if repository != "" { | ||
768 | if tag != "" { | ||
769 | repository += ":" + tag | ||
770 | } | ||
771 | s.imgIDs[repository] = image.ID | ||
772 | } | ||
773 | s.iMut.Unlock() | ||
774 | fmt.Fprintf(w, `{"ID":%q}`, image.ID) | ||
775 | } | ||
776 | |||
777 | func (s *DockerServer) findContainer(idOrName string) (*docker.Container, int, error) { | ||
778 | return s.findContainerWithLock(idOrName, true) | ||
779 | } | ||
780 | |||
781 | func (s *DockerServer) findContainerWithLock(idOrName string, shouldLock bool) (*docker.Container, int, error) { | ||
782 | if shouldLock { | ||
783 | s.cMut.RLock() | ||
784 | defer s.cMut.RUnlock() | ||
785 | } | ||
786 | for i, container := range s.containers { | ||
787 | if container.ID == idOrName || container.Name == idOrName { | ||
788 | return container, i, nil | ||
789 | } | ||
790 | } | ||
791 | return nil, -1, errors.New("No such container") | ||
792 | } | ||
793 | |||
794 | func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) { | ||
795 | if ct := r.Header.Get("Content-Type"); ct == "application/tar" { | ||
796 | gotDockerFile := false | ||
797 | tr := tar.NewReader(r.Body) | ||
798 | for { | ||
799 | header, err := tr.Next() | ||
800 | if err != nil { | ||
801 | break | ||
802 | } | ||
803 | if header.Name == "Dockerfile" { | ||
804 | gotDockerFile = true | ||
805 | } | ||
806 | } | ||
807 | if !gotDockerFile { | ||
808 | w.WriteHeader(http.StatusBadRequest) | ||
809 | w.Write([]byte("miss Dockerfile")) | ||
810 | return | ||
811 | } | ||
812 | } | ||
813 | //we did not use that Dockerfile to build image cause we are a fake Docker daemon | ||
814 | image := docker.Image{ | ||
815 | ID: s.generateID(), | ||
816 | Created: time.Now(), | ||
817 | } | ||
818 | |||
819 | query := r.URL.Query() | ||
820 | repository := image.ID | ||
821 | if t := query.Get("t"); t != "" { | ||
822 | repository = t | ||
823 | } | ||
824 | s.iMut.Lock() | ||
825 | s.images = append(s.images, image) | ||
826 | s.imgIDs[repository] = image.ID | ||
827 | s.iMut.Unlock() | ||
828 | w.Write([]byte(fmt.Sprintf("Successfully built %s", image.ID))) | ||
829 | } | ||
830 | |||
831 | func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) { | ||
832 | fromImageName := r.URL.Query().Get("fromImage") | ||
833 | tag := r.URL.Query().Get("tag") | ||
834 | image := docker.Image{ | ||
835 | ID: s.generateID(), | ||
836 | Config: &docker.Config{}, | ||
837 | } | ||
838 | s.iMut.Lock() | ||
839 | s.images = append(s.images, image) | ||
840 | if fromImageName != "" { | ||
841 | if tag != "" { | ||
842 | fromImageName = fmt.Sprintf("%s:%s", fromImageName, tag) | ||
843 | } | ||
844 | s.imgIDs[fromImageName] = image.ID | ||
845 | } | ||
846 | s.iMut.Unlock() | ||
847 | } | ||
848 | |||
849 | func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) { | ||
850 | name := mux.Vars(r)["name"] | ||
851 | tag := r.URL.Query().Get("tag") | ||
852 | if tag != "" { | ||
853 | name += ":" + tag | ||
854 | } | ||
855 | s.iMut.RLock() | ||
856 | if _, ok := s.imgIDs[name]; !ok { | ||
857 | s.iMut.RUnlock() | ||
858 | http.Error(w, "No such image", http.StatusNotFound) | ||
859 | return | ||
860 | } | ||
861 | s.iMut.RUnlock() | ||
862 | fmt.Fprintln(w, "Pushing...") | ||
863 | fmt.Fprintln(w, "Pushed") | ||
864 | } | ||
865 | |||
866 | func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) { | ||
867 | name := mux.Vars(r)["name"] | ||
868 | s.iMut.RLock() | ||
869 | if _, ok := s.imgIDs[name]; !ok { | ||
870 | s.iMut.RUnlock() | ||
871 | http.Error(w, "No such image", http.StatusNotFound) | ||
872 | return | ||
873 | } | ||
874 | s.iMut.RUnlock() | ||
875 | s.iMut.Lock() | ||
876 | defer s.iMut.Unlock() | ||
877 | newRepo := r.URL.Query().Get("repo") | ||
878 | newTag := r.URL.Query().Get("tag") | ||
879 | if newTag != "" { | ||
880 | newRepo += ":" + newTag | ||
881 | } | ||
882 | s.imgIDs[newRepo] = s.imgIDs[name] | ||
883 | w.WriteHeader(http.StatusCreated) | ||
884 | } | ||
885 | |||
886 | func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) { | ||
887 | id := mux.Vars(r)["id"] | ||
888 | s.iMut.RLock() | ||
889 | var tag string | ||
890 | if img, ok := s.imgIDs[id]; ok { | ||
891 | id, tag = img, id | ||
892 | } | ||
893 | var tags []string | ||
894 | for tag, taggedID := range s.imgIDs { | ||
895 | if taggedID == id { | ||
896 | tags = append(tags, tag) | ||
897 | } | ||
898 | } | ||
899 | s.iMut.RUnlock() | ||
900 | _, index, err := s.findImageByID(id) | ||
901 | if err != nil { | ||
902 | http.Error(w, err.Error(), http.StatusNotFound) | ||
903 | return | ||
904 | } | ||
905 | w.WriteHeader(http.StatusNoContent) | ||
906 | s.iMut.Lock() | ||
907 | defer s.iMut.Unlock() | ||
908 | if len(tags) < 2 { | ||
909 | s.images[index] = s.images[len(s.images)-1] | ||
910 | s.images = s.images[:len(s.images)-1] | ||
911 | } | ||
912 | if tag != "" { | ||
913 | delete(s.imgIDs, tag) | ||
914 | } | ||
915 | } | ||
916 | |||
917 | func (s *DockerServer) inspectImage(w http.ResponseWriter, r *http.Request) { | ||
918 | name := mux.Vars(r)["name"] | ||
919 | s.iMut.RLock() | ||
920 | defer s.iMut.RUnlock() | ||
921 | if id, ok := s.imgIDs[name]; ok { | ||
922 | for _, img := range s.images { | ||
923 | if img.ID == id { | ||
924 | w.Header().Set("Content-Type", "application/json") | ||
925 | w.WriteHeader(http.StatusOK) | ||
926 | json.NewEncoder(w).Encode(img) | ||
927 | return | ||
928 | } | ||
929 | } | ||
930 | } | ||
931 | http.Error(w, "not found", http.StatusNotFound) | ||
932 | } | ||
933 | |||
934 | func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) { | ||
935 | w.Header().Set("Content-Type", "application/json") | ||
936 | var events [][]byte | ||
937 | count := mathrand.Intn(20) | ||
938 | for i := 0; i < count; i++ { | ||
939 | data, err := json.Marshal(s.generateEvent()) | ||
940 | if err != nil { | ||
941 | w.WriteHeader(http.StatusInternalServerError) | ||
942 | return | ||
943 | } | ||
944 | events = append(events, data) | ||
945 | } | ||
946 | w.WriteHeader(http.StatusOK) | ||
947 | for _, d := range events { | ||
948 | fmt.Fprintln(w, d) | ||
949 | time.Sleep(time.Duration(mathrand.Intn(200)) * time.Millisecond) | ||
950 | } | ||
951 | } | ||
952 | |||
953 | func (s *DockerServer) pingDocker(w http.ResponseWriter, r *http.Request) { | ||
954 | w.WriteHeader(http.StatusOK) | ||
955 | } | ||
956 | |||
957 | func (s *DockerServer) generateEvent() *docker.APIEvents { | ||
958 | var eventType string | ||
959 | switch mathrand.Intn(4) { | ||
960 | case 0: | ||
961 | eventType = "create" | ||
962 | case 1: | ||
963 | eventType = "start" | ||
964 | case 2: | ||
965 | eventType = "stop" | ||
966 | case 3: | ||
967 | eventType = "destroy" | ||
968 | } | ||
969 | return &docker.APIEvents{ | ||
970 | ID: s.generateID(), | ||
971 | Status: eventType, | ||
972 | From: "mybase:latest", | ||
973 | Time: time.Now().Unix(), | ||
974 | } | ||
975 | } | ||
976 | |||
977 | func (s *DockerServer) loadImage(w http.ResponseWriter, r *http.Request) { | ||
978 | w.WriteHeader(http.StatusOK) | ||
979 | } | ||
980 | |||
981 | func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) { | ||
982 | w.WriteHeader(http.StatusOK) | ||
983 | w.Header().Set("Content-Type", "application/tar") | ||
984 | } | ||
985 | |||
986 | func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) { | ||
987 | id := mux.Vars(r)["id"] | ||
988 | container, _, err := s.findContainer(id) | ||
989 | if err != nil { | ||
990 | http.Error(w, err.Error(), http.StatusNotFound) | ||
991 | return | ||
992 | } | ||
993 | |||
994 | execID := s.generateID() | ||
995 | container.ExecIDs = append(container.ExecIDs, execID) | ||
996 | |||
997 | exec := docker.ExecInspect{ | ||
998 | ID: execID, | ||
999 | Container: *container, | ||
1000 | } | ||
1001 | |||
1002 | var params docker.CreateExecOptions | ||
1003 | err = json.NewDecoder(r.Body).Decode(¶ms) | ||
1004 | if err != nil { | ||
1005 | http.Error(w, err.Error(), http.StatusInternalServerError) | ||
1006 | return | ||
1007 | } | ||
1008 | if len(params.Cmd) > 0 { | ||
1009 | exec.ProcessConfig.EntryPoint = params.Cmd[0] | ||
1010 | if len(params.Cmd) > 1 { | ||
1011 | exec.ProcessConfig.Arguments = params.Cmd[1:] | ||
1012 | } | ||
1013 | } | ||
1014 | |||
1015 | exec.ProcessConfig.User = params.User | ||
1016 | exec.ProcessConfig.Tty = params.Tty | ||
1017 | |||
1018 | s.execMut.Lock() | ||
1019 | s.execs = append(s.execs, &exec) | ||
1020 | s.execMut.Unlock() | ||
1021 | w.WriteHeader(http.StatusOK) | ||
1022 | w.Header().Set("Content-Type", "application/json") | ||
1023 | json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID}) | ||
1024 | } | ||
1025 | |||
1026 | func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) { | ||
1027 | id := mux.Vars(r)["id"] | ||
1028 | if exec, err := s.getExec(id, false); err == nil { | ||
1029 | s.execMut.Lock() | ||
1030 | exec.Running = true | ||
1031 | s.execMut.Unlock() | ||
1032 | if callback, ok := s.execCallbacks[id]; ok { | ||
1033 | callback() | ||
1034 | delete(s.execCallbacks, id) | ||
1035 | } else if callback, ok := s.execCallbacks["*"]; ok { | ||
1036 | callback() | ||
1037 | delete(s.execCallbacks, "*") | ||
1038 | } | ||
1039 | s.execMut.Lock() | ||
1040 | exec.Running = false | ||
1041 | s.execMut.Unlock() | ||
1042 | w.WriteHeader(http.StatusOK) | ||
1043 | return | ||
1044 | } | ||
1045 | w.WriteHeader(http.StatusNotFound) | ||
1046 | } | ||
1047 | |||
1048 | func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) { | ||
1049 | id := mux.Vars(r)["id"] | ||
1050 | if _, err := s.getExec(id, false); err == nil { | ||
1051 | w.WriteHeader(http.StatusOK) | ||
1052 | return | ||
1053 | } | ||
1054 | w.WriteHeader(http.StatusNotFound) | ||
1055 | } | ||
1056 | |||
1057 | func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) { | ||
1058 | id := mux.Vars(r)["id"] | ||
1059 | if exec, err := s.getExec(id, true); err == nil { | ||
1060 | w.WriteHeader(http.StatusOK) | ||
1061 | w.Header().Set("Content-Type", "application/json") | ||
1062 | json.NewEncoder(w).Encode(exec) | ||
1063 | return | ||
1064 | } | ||
1065 | w.WriteHeader(http.StatusNotFound) | ||
1066 | } | ||
1067 | |||
1068 | func (s *DockerServer) getExec(id string, copy bool) (*docker.ExecInspect, error) { | ||
1069 | s.execMut.RLock() | ||
1070 | defer s.execMut.RUnlock() | ||
1071 | for _, exec := range s.execs { | ||
1072 | if exec.ID == id { | ||
1073 | if copy { | ||
1074 | cp := *exec | ||
1075 | exec = &cp | ||
1076 | } | ||
1077 | return exec, nil | ||
1078 | } | ||
1079 | } | ||
1080 | return nil, errors.New("exec not found") | ||
1081 | } | ||
1082 | |||
1083 | func (s *DockerServer) findNetwork(idOrName string) (*docker.Network, int, error) { | ||
1084 | s.netMut.RLock() | ||
1085 | defer s.netMut.RUnlock() | ||
1086 | for i, network := range s.networks { | ||
1087 | if network.ID == idOrName || network.Name == idOrName { | ||
1088 | return network, i, nil | ||
1089 | } | ||
1090 | } | ||
1091 | return nil, -1, errors.New("No such network") | ||
1092 | } | ||
1093 | |||
1094 | func (s *DockerServer) listNetworks(w http.ResponseWriter, r *http.Request) { | ||
1095 | s.netMut.RLock() | ||
1096 | result := make([]docker.Network, 0, len(s.networks)) | ||
1097 | for _, network := range s.networks { | ||
1098 | result = append(result, *network) | ||
1099 | } | ||
1100 | s.netMut.RUnlock() | ||
1101 | w.Header().Set("Content-Type", "application/json") | ||
1102 | w.WriteHeader(http.StatusOK) | ||
1103 | json.NewEncoder(w).Encode(result) | ||
1104 | } | ||
1105 | |||
1106 | func (s *DockerServer) networkInfo(w http.ResponseWriter, r *http.Request) { | ||
1107 | id := mux.Vars(r)["id"] | ||
1108 | network, _, err := s.findNetwork(id) | ||
1109 | if err != nil { | ||
1110 | http.Error(w, err.Error(), http.StatusNotFound) | ||
1111 | return | ||
1112 | } | ||
1113 | w.Header().Set("Content-Type", "application/json") | ||
1114 | w.WriteHeader(http.StatusOK) | ||
1115 | json.NewEncoder(w).Encode(network) | ||
1116 | } | ||
1117 | |||
1118 | // isValidName validates configuration objects supported by libnetwork | ||
1119 | func isValidName(name string) bool { | ||
1120 | if name == "" || strings.Contains(name, ".") { | ||
1121 | return false | ||
1122 | } | ||
1123 | return true | ||
1124 | } | ||
1125 | |||
1126 | func (s *DockerServer) createNetwork(w http.ResponseWriter, r *http.Request) { | ||
1127 | var config *docker.CreateNetworkOptions | ||
1128 | defer r.Body.Close() | ||
1129 | err := json.NewDecoder(r.Body).Decode(&config) | ||
1130 | if err != nil { | ||
1131 | http.Error(w, err.Error(), http.StatusBadRequest) | ||
1132 | return | ||
1133 | } | ||
1134 | if !isValidName(config.Name) { | ||
1135 | http.Error(w, "Invalid network name", http.StatusBadRequest) | ||
1136 | return | ||
1137 | } | ||
1138 | if n, _, _ := s.findNetwork(config.Name); n != nil { | ||
1139 | http.Error(w, "network already exists", http.StatusForbidden) | ||
1140 | return | ||
1141 | } | ||
1142 | |||
1143 | generatedID := s.generateID() | ||
1144 | network := docker.Network{ | ||
1145 | Name: config.Name, | ||
1146 | ID: generatedID, | ||
1147 | Driver: config.Driver, | ||
1148 | } | ||
1149 | s.netMut.Lock() | ||
1150 | s.networks = append(s.networks, &network) | ||
1151 | s.netMut.Unlock() | ||
1152 | w.WriteHeader(http.StatusCreated) | ||
1153 | var c = struct{ ID string }{ID: network.ID} | ||
1154 | json.NewEncoder(w).Encode(c) | ||
1155 | } | ||
1156 | |||
1157 | func (s *DockerServer) listVolumes(w http.ResponseWriter, r *http.Request) { | ||
1158 | s.volMut.RLock() | ||
1159 | result := make([]docker.Volume, 0, len(s.volStore)) | ||
1160 | for _, volumeCounter := range s.volStore { | ||
1161 | result = append(result, volumeCounter.volume) | ||
1162 | } | ||
1163 | s.volMut.RUnlock() | ||
1164 | w.Header().Set("Content-Type", "application/json") | ||
1165 | w.WriteHeader(http.StatusOK) | ||
1166 | json.NewEncoder(w).Encode(result) | ||
1167 | } | ||
1168 | |||
1169 | func (s *DockerServer) createVolume(w http.ResponseWriter, r *http.Request) { | ||
1170 | var data struct { | ||
1171 | *docker.CreateVolumeOptions | ||
1172 | } | ||
1173 | defer r.Body.Close() | ||
1174 | err := json.NewDecoder(r.Body).Decode(&data) | ||
1175 | if err != nil { | ||
1176 | http.Error(w, err.Error(), http.StatusBadRequest) | ||
1177 | return | ||
1178 | } | ||
1179 | volume := &docker.Volume{ | ||
1180 | Name: data.CreateVolumeOptions.Name, | ||
1181 | Driver: data.CreateVolumeOptions.Driver, | ||
1182 | } | ||
1183 | // If the name is not specified, generate one. Just using generateID for now | ||
1184 | if len(volume.Name) == 0 { | ||
1185 | volume.Name = s.generateID() | ||
1186 | } | ||
1187 | // If driver is not specified, use local | ||
1188 | if len(volume.Driver) == 0 { | ||
1189 | volume.Driver = "local" | ||
1190 | } | ||
1191 | // Mount point is a default one with name | ||
1192 | volume.Mountpoint = "/var/lib/docker/volumes/" + volume.Name | ||
1193 | |||
1194 | // If the volume already exists, don't re-add it. | ||
1195 | exists := false | ||
1196 | s.volMut.Lock() | ||
1197 | if s.volStore != nil { | ||
1198 | _, exists = s.volStore[volume.Name] | ||
1199 | } else { | ||
1200 | // No volumes, create volStore | ||
1201 | s.volStore = make(map[string]*volumeCounter) | ||
1202 | } | ||
1203 | if !exists { | ||
1204 | s.volStore[volume.Name] = &volumeCounter{ | ||
1205 | volume: *volume, | ||
1206 | count: 0, | ||
1207 | } | ||
1208 | } | ||
1209 | s.volMut.Unlock() | ||
1210 | w.WriteHeader(http.StatusCreated) | ||
1211 | json.NewEncoder(w).Encode(volume) | ||
1212 | } | ||
1213 | |||
1214 | func (s *DockerServer) inspectVolume(w http.ResponseWriter, r *http.Request) { | ||
1215 | s.volMut.RLock() | ||
1216 | defer s.volMut.RUnlock() | ||
1217 | name := mux.Vars(r)["name"] | ||
1218 | vol, err := s.findVolume(name) | ||
1219 | if err != nil { | ||
1220 | http.Error(w, err.Error(), http.StatusNotFound) | ||
1221 | return | ||
1222 | } | ||
1223 | w.Header().Set("Content-Type", "application/json") | ||
1224 | w.WriteHeader(http.StatusOK) | ||
1225 | json.NewEncoder(w).Encode(vol.volume) | ||
1226 | } | ||
1227 | |||
1228 | func (s *DockerServer) findVolume(name string) (*volumeCounter, error) { | ||
1229 | vol, ok := s.volStore[name] | ||
1230 | if !ok { | ||
1231 | return nil, errors.New("no such volume") | ||
1232 | } | ||
1233 | return vol, nil | ||
1234 | } | ||
1235 | |||
1236 | func (s *DockerServer) removeVolume(w http.ResponseWriter, r *http.Request) { | ||
1237 | s.volMut.Lock() | ||
1238 | defer s.volMut.Unlock() | ||
1239 | name := mux.Vars(r)["name"] | ||
1240 | vol, err := s.findVolume(name) | ||
1241 | if err != nil { | ||
1242 | http.Error(w, err.Error(), http.StatusNotFound) | ||
1243 | return | ||
1244 | } | ||
1245 | if vol.count != 0 { | ||
1246 | http.Error(w, "volume in use and cannot be removed", http.StatusConflict) | ||
1247 | return | ||
1248 | } | ||
1249 | s.volStore[vol.volume.Name] = nil | ||
1250 | w.WriteHeader(http.StatusNoContent) | ||
1251 | } | ||
1252 | |||
1253 | func (s *DockerServer) infoDocker(w http.ResponseWriter, r *http.Request) { | ||
1254 | s.cMut.RLock() | ||
1255 | defer s.cMut.RUnlock() | ||
1256 | s.iMut.RLock() | ||
1257 | defer s.iMut.RUnlock() | ||
1258 | var running, stopped, paused int | ||
1259 | for _, c := range s.containers { | ||
1260 | if c.State.Running { | ||
1261 | running++ | ||
1262 | } else { | ||
1263 | stopped++ | ||
1264 | } | ||
1265 | if c.State.Paused { | ||
1266 | paused++ | ||
1267 | } | ||
1268 | } | ||
1269 | envs := map[string]interface{}{ | ||
1270 | "ID": "AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB", | ||
1271 | "Containers": len(s.containers), | ||
1272 | "ContainersRunning": running, | ||
1273 | "ContainersPaused": paused, | ||
1274 | "ContainersStopped": stopped, | ||
1275 | "Images": len(s.images), | ||
1276 | "Driver": "aufs", | ||
1277 | "DriverStatus": [][]string{}, | ||
1278 | "SystemStatus": nil, | ||
1279 | "Plugins": map[string]interface{}{ | ||
1280 | "Volume": []string{ | ||
1281 | "local", | ||
1282 | }, | ||
1283 | "Network": []string{ | ||
1284 | "bridge", | ||
1285 | "null", | ||
1286 | "host", | ||
1287 | }, | ||
1288 | "Authorization": nil, | ||
1289 | }, | ||
1290 | "MemoryLimit": true, | ||
1291 | "SwapLimit": false, | ||
1292 | "CpuCfsPeriod": true, | ||
1293 | "CpuCfsQuota": true, | ||
1294 | "CPUShares": true, | ||
1295 | "CPUSet": true, | ||
1296 | "IPv4Forwarding": true, | ||
1297 | "BridgeNfIptables": true, | ||
1298 | "BridgeNfIp6tables": true, | ||
1299 | "Debug": false, | ||
1300 | "NFd": 79, | ||
1301 | "OomKillDisable": true, | ||
1302 | "NGoroutines": 101, | ||
1303 | "SystemTime": "2016-02-25T18:13:10.25870078Z", | ||
1304 | "ExecutionDriver": "native-0.2", | ||
1305 | "LoggingDriver": "json-file", | ||
1306 | "NEventsListener": 0, | ||
1307 | "KernelVersion": "3.13.0-77-generic", | ||
1308 | "OperatingSystem": "Ubuntu 14.04.3 LTS", | ||
1309 | "OSType": "linux", | ||
1310 | "Architecture": "x86_64", | ||
1311 | "IndexServerAddress": "https://index.docker.io/v1/", | ||
1312 | "RegistryConfig": map[string]interface{}{ | ||
1313 | "InsecureRegistryCIDRs": []string{}, | ||
1314 | "IndexConfigs": map[string]interface{}{}, | ||
1315 | "Mirrors": nil, | ||
1316 | }, | ||
1317 | "InitSha1": "e2042dbb0fcf49bb9da199186d9a5063cda92a01", | ||
1318 | "InitPath": "/usr/lib/docker/dockerinit", | ||
1319 | "NCPU": 1, | ||
1320 | "MemTotal": 2099204096, | ||
1321 | "DockerRootDir": "/var/lib/docker", | ||
1322 | "HttpProxy": "", | ||
1323 | "HttpsProxy": "", | ||
1324 | "NoProxy": "", | ||
1325 | "Name": "vagrant-ubuntu-trusty-64", | ||
1326 | "Labels": nil, | ||
1327 | "ExperimentalBuild": false, | ||
1328 | "ServerVersion": "1.10.1", | ||
1329 | "ClusterStore": "", | ||
1330 | "ClusterAdvertise": "", | ||
1331 | } | ||
1332 | w.WriteHeader(http.StatusOK) | ||
1333 | json.NewEncoder(w).Encode(envs) | ||
1334 | } | ||