mirror of
https://github.com/clearlinux/docker.git
synced 2026-09-05 21:21:42 +00:00
Compare commits
262
Commits
docs
...
v1.7.0-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ddecf74be | ||
|
|
8e7ea1a8fd | ||
|
|
c11128520c | ||
|
|
25fc200dcf | ||
|
|
d17f27a13f | ||
|
|
f2f2c492e1 | ||
|
|
18af7fdbba | ||
|
|
339f0a128a | ||
|
|
5a0fa9545a | ||
|
|
930f691919 | ||
|
|
bf371ab2a5 | ||
|
|
d283999b1c | ||
|
|
2786c4d0e2 | ||
|
|
2939617c8b | ||
|
|
f5e3c68c93 | ||
|
|
c96b03797a | ||
|
|
0f06c54f40 | ||
|
|
32fcacdedb | ||
|
|
140e36a77e | ||
|
|
4945a51f73 | ||
|
|
c881349e5e | ||
|
|
ecdf1297a3 | ||
|
|
db3daa7bdd | ||
|
|
c0fc839e2b | ||
|
|
2e29eadb5c | ||
|
|
ecda5c0a6d | ||
|
|
4872f86d00 | ||
|
|
6ca78fff63 | ||
|
|
e9c51c3edc | ||
|
|
b38c720f19 | ||
|
|
3bed793ba1 | ||
|
|
ab7e7a7338 | ||
|
|
4fefcde5a6 | ||
|
|
63e3b7433f | ||
|
|
736c216d58 | ||
|
|
85f0e01833 | ||
|
|
eb3ed436a4 | ||
|
|
d77d7a0056 | ||
|
|
a0aad0d4de | ||
|
|
baf9ea5ce4 | ||
|
|
a6fe70c696 | ||
|
|
eee959a9b9 | ||
|
|
d000ba05fd | ||
|
|
d8eff999e0 | ||
|
|
fb124bcad0 | ||
|
|
9827107dcf | ||
|
|
e57d649057 | ||
|
|
d6ff6e2c6f | ||
|
|
cc2944c7af | ||
|
|
9cee8c4ed0 | ||
|
|
7c8fca2ddb | ||
|
|
376188dcd3 | ||
|
|
dc610864aa | ||
|
|
97cd073598 | ||
|
|
d5ebb60bdd | ||
|
|
83c5131acd | ||
|
|
b6a9dc399b | ||
|
|
614a9690e7 | ||
|
|
545b440a80 | ||
|
|
3162024e28 | ||
|
|
769acfec29 | ||
|
|
47496519da | ||
|
|
fdd21bf032 | ||
|
|
d928dad8c8 | ||
|
|
82366ce059 | ||
|
|
6410c3c066 | ||
|
|
9231dc9cc0 | ||
|
|
6a3f37386b | ||
|
|
d9a0c05208 | ||
|
|
24cb9df189 | ||
|
|
c51cd3298c | ||
|
|
10affa8018 | ||
|
|
ce27fa2716 | ||
|
|
8d83409e85 | ||
|
|
3a73b6a2bf | ||
|
|
f99269882f | ||
|
|
568a9703ac | ||
|
|
faaeb5162d | ||
|
|
b5613baac2 | ||
|
|
c956efcd52 | ||
|
|
5455864187 | ||
|
|
ceb72fab34 | ||
|
|
c6ea062a26 | ||
|
|
0e045ab50c | ||
|
|
eeb05fc081 | ||
|
|
e1381ae328 | ||
|
|
45ad064150 | ||
|
|
72e14a1566 | ||
|
|
7d7bec86c9 | ||
|
|
a39d49d676 | ||
|
|
5bf15a013b | ||
|
|
9461967eec | ||
|
|
3be7d11cee | ||
|
|
d9910b8fd8 | ||
|
|
f115c32f6b | ||
|
|
57939badc3 | ||
|
|
51ee02d478 | ||
|
|
c92860748c | ||
|
|
f582f9717f | ||
|
|
ebcb36a8d2 | ||
|
|
e6e8f2d717 | ||
|
|
317a510261 | ||
|
|
5d3a080178 | ||
|
|
542c84c2d2 | ||
|
|
f1df74d09d | ||
|
|
4ddbc7a62f | ||
|
|
f72b2c02b8 | ||
|
|
af9dab70f8 | ||
|
|
10425e83f2 | ||
|
|
9c528dca85 | ||
|
|
cb2c25ad2d | ||
|
|
962dec81ec | ||
|
|
1eae925a3d | ||
|
|
3ce2cc8ee7 | ||
|
|
054acc4bee | ||
|
|
63cb03a55b | ||
|
|
49b6f23696 | ||
|
|
299ae6a2e6 | ||
|
|
97b521bf10 | ||
|
|
7f5937d46c | ||
|
|
b6166b9496 | ||
|
|
b596d025f5 | ||
|
|
ca32446950 | ||
|
|
5328d6d620 | ||
|
|
d0023242ab | ||
|
|
3ff002aa1a | ||
|
|
ea9b357be2 | ||
|
|
bf1829459f | ||
|
|
4f744ca781 | ||
|
|
7dab04383b | ||
|
|
8a003c8134 | ||
|
|
208178c799 | ||
|
|
03b36f3451 | ||
|
|
7758553239 | ||
|
|
10fb5ce6d0 | ||
|
|
0959aec1a9 | ||
|
|
773f74eb71 | ||
|
|
7070d9255a | ||
|
|
2cb4b7f65c | ||
|
|
2d80652d8a | ||
|
|
81b4691406 | ||
|
|
4bae33ef9f | ||
|
|
a8a31eff10 | ||
|
|
68a8fd5c4e | ||
|
|
8387c5ab65 | ||
|
|
69498943c3 | ||
|
|
1aeb78c2ae | ||
|
|
331d37f35d | ||
|
|
edf3bf7f33 | ||
|
|
9ee8dca246 | ||
|
|
aa98bb6c13 | ||
|
|
2aba3c69f9 | ||
|
|
71a44c769e | ||
|
|
d8381fad2b | ||
|
|
be379580d0 | ||
|
|
7ea8513479 | ||
|
|
3b2fe01c78 | ||
|
|
e8afc22b1f | ||
|
|
4e407e6b77 | ||
|
|
23f1c2ea9e | ||
|
|
788047cafb | ||
|
|
0c0e7b1b60 | ||
|
|
09d41529a0 | ||
|
|
cb288fefee | ||
|
|
f7636796c5 | ||
|
|
cb5af83444 | ||
|
|
96feaf1920 | ||
|
|
1f03944950 | ||
|
|
6060eedf9c | ||
|
|
d217da854a | ||
|
|
d74d6d981b | ||
|
|
0205ac33d2 | ||
|
|
dbb9d47bdc | ||
|
|
ddd1d081d7 | ||
|
|
d6ac36d929 | ||
|
|
715b94f664 | ||
|
|
16baca9277 | ||
|
|
627f8a6cd5 | ||
|
|
a8a7df203a | ||
|
|
580cbcefd3 | ||
|
|
d9c5ce6e97 | ||
|
|
0fe9b95415 | ||
|
|
41d0e4293e | ||
|
|
26fe640da1 | ||
|
|
198ca26969 | ||
|
|
d5365f6fc4 | ||
|
|
5f7e814ee7 | ||
|
|
a84aca0985 | ||
|
|
68ec22876a | ||
|
|
0dcc3559e9 | ||
|
|
d4c731ecd6 | ||
|
|
2dba4e1386 | ||
|
|
06a7f471e0 | ||
|
|
4683d01691 | ||
|
|
6020a06399 | ||
|
|
cc0bfccdf4 | ||
|
|
0c18ec62f3 | ||
|
|
a9825c9bd8 | ||
|
|
908be50c44 | ||
|
|
2a82dba34d | ||
|
|
13fd2a908c | ||
|
|
464891aaf8 | ||
|
|
9974663ed7 | ||
|
|
76269e5c9d | ||
|
|
1121d7c4fd | ||
|
|
7e197575a2 | ||
|
|
3dc3059d94 | ||
|
|
7b6de74c9a | ||
|
|
cad8adacb8 | ||
|
|
6226deeaf4 | ||
|
|
3ec19f56cf | ||
|
|
48c71787ed | ||
|
|
604731a930 | ||
|
|
e8650e01f8 | ||
|
|
817d04d992 | ||
|
|
cdff91a01c | ||
|
|
6f26bd0e16 | ||
|
|
3c090db4e9 | ||
|
|
b7c3fdfd0d | ||
|
|
aa682a845b | ||
|
|
218d0dcc9d | ||
|
|
510d8f8634 | ||
|
|
b65600f6b6 | ||
|
|
79dcea718c | ||
|
|
072b09c45d | ||
|
|
c2d9837745 | ||
|
|
fa5dfbb18b | ||
|
|
6532a075f3 | ||
|
|
3b4a4bf809 | ||
|
|
4602909566 | ||
|
|
588f350b61 | ||
|
|
6e5ff509b2 | ||
|
|
61d341c2ca | ||
|
|
b996d379a1 | ||
|
|
b0935ea730 | ||
|
|
96fe13b49b | ||
|
|
12ccde442a | ||
|
|
4262cfe41f | ||
|
|
ddc2e25546 | ||
|
|
6646cff646 | ||
|
|
ac8fd856c0 | ||
|
|
48754d673c | ||
|
|
723684525a | ||
|
|
32aceadbe6 | ||
|
|
c67d3e159c | ||
|
|
a080e2add7 | ||
|
|
24d81b0ddb | ||
|
|
08f2fad40b | ||
|
|
f91fbe39ce | ||
|
|
018ab080bb | ||
|
|
fe94ecb2c1 | ||
|
|
7b2e67036f | ||
|
|
e130faea1b | ||
|
|
38f09de334 | ||
|
|
f9ba68ddfb | ||
|
|
16913455bd | ||
|
|
32f189cd08 | ||
|
|
526ca42282 | ||
|
|
b98b42d843 | ||
|
|
7bf03dd132 | ||
|
|
034aa3b2c4 | ||
|
|
6da1e01e6c |
@@ -1,5 +1,39 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.7.0 (2015-06-16)
|
||||||
|
|
||||||
|
#### Runtime
|
||||||
|
+ Experimental feature: support for out-of-process volume plugins
|
||||||
|
+ Experimental feature: support for out-of-process network plugins
|
||||||
|
* Logging: syslog logging driver is available
|
||||||
|
* The userland proxy can be disabled in favor of hairpin NAT using the daemon’s `--userland-proxy=false` flag
|
||||||
|
* The `exec` command supports the `-u|--user` flag to specify the new process owner
|
||||||
|
+ Default gateway for containers can be specified daemon-wide using the `--default-gateway` and `--default-gateway-v6` flags
|
||||||
|
+ The CPU CFS (Completely Fair Scheduler) quota can be set in `docker run` using `--cpu-quota`
|
||||||
|
+ Container block IO can be controlled in `docker run` using`--blkio-weight`
|
||||||
|
+ ZFS support
|
||||||
|
+ The `docker logs` command supports a `--since` argument
|
||||||
|
+ UTS namespace can be shared with the host with `docker run --uts=host`
|
||||||
|
|
||||||
|
#### Quality
|
||||||
|
* Networking stack was entirely rewritten as part of the libnetwork effort
|
||||||
|
* Engine internals refactoring (shout out to the contributors?)
|
||||||
|
* Volumes code was entirely rewritten to support the plugins effort
|
||||||
|
+ Sending SIGUSR1 to a daemon will dump all goroutines stacks without exiting
|
||||||
|
|
||||||
|
#### Build
|
||||||
|
+ Support ${variable:-value} and ${variable:+value} syntax for environment variables
|
||||||
|
+ Support resource management flags `--cgroup-parent`, `--cpu-period`, `--cpu-quota`, `--cpuset-cpus`, `--cpuset-mems`
|
||||||
|
+ git context changes with branches and directories
|
||||||
|
* The .dockerignore file support exclusion rules
|
||||||
|
|
||||||
|
#### Distribution
|
||||||
|
+ Client support for v2 mirroring support for the official registry
|
||||||
|
|
||||||
|
#### Bugfixes
|
||||||
|
* Firewalld is now supported and will automatically be used when available
|
||||||
|
* mounting --device recursively
|
||||||
|
|
||||||
## 1.6.2 (2015-05-13)
|
## 1.6.2 (2015-05-13)
|
||||||
|
|
||||||
#### Runtime
|
#### Runtime
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ Contributing to Docker
|
|||||||
======================
|
======================
|
||||||
|
|
||||||
[](https://godoc.org/github.com/docker/docker)
|
[](https://godoc.org/github.com/docker/docker)
|
||||||
[](https://jenkins.dockerproject.com/job/Docker%20Master/)
|
[](https://jenkins.dockerproject.org/job/Docker%20Master/)
|
||||||
|
|
||||||
Want to hack on Docker? Awesome! We have [instructions to help you get
|
Want to hack on Docker? Awesome! We have [instructions to help you get
|
||||||
started contributing code or documentation.](https://docs.docker.com/project/who-written-for/).
|
started contributing code or documentation.](https://docs.docker.com/project/who-written-for/).
|
||||||
@@ -192,12 +192,12 @@ Getting the development builds
|
|||||||
==============================
|
==============================
|
||||||
|
|
||||||
Want to run Docker from a master build? You can download
|
Want to run Docker from a master build? You can download
|
||||||
master builds at [master.dockerproject.com](https://master.dockerproject.com).
|
master builds at [master.dockerproject.org](https://master.dockerproject.org).
|
||||||
They are updated with each commit merged into the master branch.
|
They are updated with each commit merged into the master branch.
|
||||||
|
|
||||||
Don't know how to use that super cool new feature in the master build? Check
|
Don't know how to use that super cool new feature in the master build? Check
|
||||||
out the master docs at
|
out the master docs at
|
||||||
[docs.master.dockerproject.com](http://docs.master.dockerproject.com).
|
[docs.master.dockerproject.org](http://docs.master.dockerproject.org).
|
||||||
|
|
||||||
How the project is run
|
How the project is run
|
||||||
======================
|
======================
|
||||||
|
|||||||
+8
-8
@@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -97,7 +96,7 @@ func (cli *DockerCli) Cmd(args ...string) error {
|
|||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
method, exists := cli.getMethod(args[0])
|
method, exists := cli.getMethod(args[0])
|
||||||
if !exists {
|
if !exists {
|
||||||
return fmt.Errorf("docker: '%s' is not a docker command. See 'docker --help'.", args[0])
|
return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'.", args[0])
|
||||||
}
|
}
|
||||||
return method(args[1:]...)
|
return method(args[1:]...)
|
||||||
}
|
}
|
||||||
@@ -117,18 +116,19 @@ func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bo
|
|||||||
errorHandling = flag.ContinueOnError
|
errorHandling = flag.ContinueOnError
|
||||||
}
|
}
|
||||||
flags := flag.NewFlagSet(name, errorHandling)
|
flags := flag.NewFlagSet(name, errorHandling)
|
||||||
flags.Usage = func() {
|
|
||||||
options := ""
|
|
||||||
if signature != "" {
|
if signature != "" {
|
||||||
signature = " " + signature
|
signature = " " + signature
|
||||||
}
|
}
|
||||||
|
flags.Usage = func() {
|
||||||
|
flags.ShortUsage()
|
||||||
|
flags.PrintDefaults()
|
||||||
|
}
|
||||||
|
flags.ShortUsage = func() {
|
||||||
|
options := ""
|
||||||
if flags.FlagCountUndeprecated() > 0 {
|
if flags.FlagCountUndeprecated() > 0 {
|
||||||
options = " [OPTIONS]"
|
options = " [OPTIONS]"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(cli.out, "\nUsage: docker %s%s%s\n\n%s\n\n", name, options, signature, description)
|
fmt.Fprintf(flags.Out(), "\nUsage: docker %s%s%s\n\n%s\n", name, options, signature, description)
|
||||||
flags.SetOutput(cli.out)
|
|
||||||
flags.PrintDefaults()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
|
|||||||
config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
|
config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.ReportError(err.Error(), true)
|
cmd.ReportError(err.Error(), true)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if config.Image == "" {
|
if config.Image == "" {
|
||||||
cmd.Usage()
|
cmd.Usage()
|
||||||
|
|||||||
+12
-17
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
)
|
)
|
||||||
@@ -15,7 +16,7 @@ import (
|
|||||||
func (cli *DockerCli) CmdInfo(args ...string) error {
|
func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||||
cmd := cli.Subcmd("info", "", "Display system-wide information", true)
|
cmd := cli.Subcmd("info", "", "Display system-wide information", true)
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
cmd.ParseFlags(args, false)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
rdr, _, err := cli.call("GET", "/info", nil, nil)
|
rdr, _, err := cli.call("GET", "/info", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -29,20 +30,20 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
|
|
||||||
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
|
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
|
||||||
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
|
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
|
||||||
fmt.Fprintf(cli.out, "Storage Driver: %s\n", info.Driver)
|
ioutils.FprintfIfNotEmpty(cli.out, "Storage Driver: %s\n", info.Driver)
|
||||||
if info.DriverStatus != nil {
|
if info.DriverStatus != nil {
|
||||||
for _, pair := range info.DriverStatus {
|
for _, pair := range info.DriverStatus {
|
||||||
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
|
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(cli.out, "Execution Driver: %s\n", info.ExecutionDriver)
|
ioutils.FprintfIfNotEmpty(cli.out, "Execution Driver: %s\n", info.ExecutionDriver)
|
||||||
fmt.Fprintf(cli.out, "Logging Driver: %s\n", info.LoggingDriver)
|
ioutils.FprintfIfNotEmpty(cli.out, "Logging Driver: %s\n", info.LoggingDriver)
|
||||||
fmt.Fprintf(cli.out, "Kernel Version: %s\n", info.KernelVersion)
|
ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion)
|
||||||
fmt.Fprintf(cli.out, "Operating System: %s\n", info.OperatingSystem)
|
ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem)
|
||||||
fmt.Fprintf(cli.out, "CPUs: %d\n", info.NCPU)
|
fmt.Fprintf(cli.out, "CPUs: %d\n", info.NCPU)
|
||||||
fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
|
fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
|
||||||
fmt.Fprintf(cli.out, "Name: %s\n", info.Name)
|
ioutils.FprintfIfNotEmpty(cli.out, "Name: %s\n", info.Name)
|
||||||
fmt.Fprintf(cli.out, "ID: %s\n", info.ID)
|
ioutils.FprintfIfNotEmpty(cli.out, "ID: %s\n", info.ID)
|
||||||
|
|
||||||
if info.Debug {
|
if info.Debug {
|
||||||
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
|
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
|
||||||
@@ -55,15 +56,9 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir)
|
fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.HttpProxy != "" {
|
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HttpProxy)
|
||||||
fmt.Fprintf(cli.out, "Http Proxy: %s\n", info.HttpProxy)
|
ioutils.FprintfIfNotEmpty(cli.out, "Https Proxy: %s\n", info.HttpsProxy)
|
||||||
}
|
ioutils.FprintfIfNotEmpty(cli.out, "No Proxy: %s\n", info.NoProxy)
|
||||||
if info.HttpsProxy != "" {
|
|
||||||
fmt.Fprintf(cli.out, "Https Proxy: %s\n", info.HttpsProxy)
|
|
||||||
}
|
|
||||||
if info.NoProxy != "" {
|
|
||||||
fmt.Fprintf(cli.out, "No Proxy: %s\n", info.NoProxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IndexServerAddress != "" {
|
if info.IndexServerAddress != "" {
|
||||||
u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username
|
u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func (cli *DockerCli) CmdLogout(args ...string) error {
|
|||||||
cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
||||||
cmd.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, false)
|
cmd.ParseFlags(args, true)
|
||||||
serverAddress := registry.IndexServerAddress()
|
serverAddress := registry.IndexServerAddress()
|
||||||
if len(cmd.Args()) > 0 {
|
if len(cmd.Args()) > 0 {
|
||||||
serverAddress = cmd.Arg(0)
|
serverAddress = cmd.Arg(0)
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ import (
|
|||||||
func (cli *DockerCli) CmdPause(args ...string) error {
|
func (cli *DockerCli) CmdPause(args ...string) error {
|
||||||
cmd := cli.Subcmd("pause", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true)
|
cmd := cli.Subcmd("pause", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
cmd.ParseFlags(args, false)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var errNames []string
|
var errNames []string
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||||||
// just in case the Parse does not exit
|
// just in case the Parse does not exit
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.ReportError(err.Error(), true)
|
cmd.ReportError(err.Error(), true)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hostConfig.Dns) > 0 {
|
if len(hostConfig.Dns) > 0 {
|
||||||
|
|||||||
+3
-7
@@ -46,7 +46,6 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
var (
|
var (
|
||||||
previousCPU uint64
|
previousCPU uint64
|
||||||
previousSystem uint64
|
previousSystem uint64
|
||||||
start = true
|
|
||||||
dec = json.NewDecoder(stream)
|
dec = json.NewDecoder(stream)
|
||||||
u = make(chan error, 1)
|
u = make(chan error, 1)
|
||||||
)
|
)
|
||||||
@@ -61,10 +60,9 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
||||||
cpuPercent = 0.0
|
cpuPercent = 0.0
|
||||||
)
|
)
|
||||||
if !start {
|
previousCPU = v.PreCpuStats.CpuUsage.TotalUsage
|
||||||
|
previousSystem = v.PreCpuStats.SystemUsage
|
||||||
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
||||||
}
|
|
||||||
start = false
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.CPUPercentage = cpuPercent
|
s.CPUPercentage = cpuPercent
|
||||||
s.Memory = float64(v.MemoryStats.Usage)
|
s.Memory = float64(v.MemoryStats.Usage)
|
||||||
@@ -73,8 +71,6 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
s.NetworkRx = float64(v.Network.RxBytes)
|
s.NetworkRx = float64(v.Network.RxBytes)
|
||||||
s.NetworkTx = float64(v.Network.TxBytes)
|
s.NetworkTx = float64(v.Network.TxBytes)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
previousCPU = v.CpuStats.CpuUsage.TotalUsage
|
|
||||||
previousSystem = v.CpuStats.SystemUsage
|
|
||||||
u <- nil
|
u <- nil
|
||||||
if !streamStats {
|
if !streamStats {
|
||||||
return
|
return
|
||||||
@@ -151,7 +147,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
|||||||
}
|
}
|
||||||
// do a quick pause so that any failed connections for containers that do not exist are able to be
|
// do a quick pause so that any failed connections for containers that do not exist are able to be
|
||||||
// evicted before we display the initial or default values.
|
// evicted before we display the initial or default values.
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(1500 * time.Millisecond)
|
||||||
var errs []string
|
var errs []string
|
||||||
for _, c := range cStats {
|
for _, c := range cStats {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
||||||
cmd := cli.Subcmd("unpause", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true)
|
cmd := cli.Subcmd("unpause", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
cmd.ParseFlags(args, false)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var errNames []string
|
var errNames []string
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdVersion shows Docker version information.
|
// CmdVersion shows Docker version information.
|
||||||
@@ -20,7 +21,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
|
|||||||
cmd := cli.Subcmd("version", "", "Show the Docker version information.", true)
|
cmd := cli.Subcmd("version", "", "Show the Docker version information.", true)
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
cmd.ParseFlags(args, false)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
if dockerversion.VERSION != "" {
|
if dockerversion.VERSION != "" {
|
||||||
fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION)
|
fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION)
|
||||||
@@ -31,6 +32,9 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
|
|||||||
fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT)
|
fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||||
|
if utils.ExperimentalBuild() {
|
||||||
|
fmt.Fprintf(cli.out, "Experimental (client): true\n")
|
||||||
|
}
|
||||||
|
|
||||||
stream, _, err := cli.call("GET", "/version", nil, nil)
|
stream, _, err := cli.call("GET", "/version", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -50,6 +54,8 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
|
|||||||
fmt.Fprintf(cli.out, "Go version (server): %s\n", v.GoVersion)
|
fmt.Fprintf(cli.out, "Go version (server): %s\n", v.GoVersion)
|
||||||
fmt.Fprintf(cli.out, "Git commit (server): %s\n", v.GitCommit)
|
fmt.Fprintf(cli.out, "Git commit (server): %s\n", v.GitCommit)
|
||||||
fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", v.Os, v.Arch)
|
fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", v.Os, v.Arch)
|
||||||
|
if v.Experimental {
|
||||||
|
fmt.Fprintf(cli.out, "Experimental (server): true\n")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,15 @@ func boolValue(r *http.Request, k string) bool {
|
|||||||
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
|
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// boolValueOrDefault returns the default bool passed if the query param is
|
||||||
|
// missing, otherwise it's just a proxy to boolValue above
|
||||||
|
func boolValueOrDefault(r *http.Request, k string, d bool) bool {
|
||||||
|
if _, ok := r.Form[k]; !ok {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
return boolValue(r, k)
|
||||||
|
}
|
||||||
|
|
||||||
func int64ValueOrZero(r *http.Request, k string) int64 {
|
func int64ValueOrZero(r *http.Request, k string) int64 {
|
||||||
val, err := strconv.ParseInt(r.FormValue(k), 10, 64)
|
val, err := strconv.ParseInt(r.FormValue(k), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -33,6 +33,21 @@ func TestBoolValue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBoolValueOrDefault(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", "", nil)
|
||||||
|
if !boolValueOrDefault(r, "queryparam", true) {
|
||||||
|
t.Fatal("Expected to get true default value, got false")
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("param", "")
|
||||||
|
r, _ = http.NewRequest("GET", "", nil)
|
||||||
|
r.Form = v
|
||||||
|
if boolValueOrDefault(r, "param", true) {
|
||||||
|
t.Fatal("Expected not to get true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInt64ValueOrZero(t *testing.T) {
|
func TestInt64ValueOrZero(t *testing.T) {
|
||||||
cases := map[string]int64{
|
cases := map[string]int64{
|
||||||
"": 0,
|
"": 0,
|
||||||
|
|||||||
+39
-9
@@ -97,15 +97,17 @@ func (s *Server) ServeApi(protoAddrs []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.servers = append(s.servers, srv)
|
s.servers = append(s.servers, srv...)
|
||||||
|
|
||||||
go func(proto, addr string) {
|
for _, s := range srv {
|
||||||
logrus.Infof("Listening for HTTP on %s (%s)", proto, addr)
|
logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])
|
||||||
if err := srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") {
|
go func(s serverCloser) {
|
||||||
|
if err := s.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
chErrors <- err
|
chErrors <- err
|
||||||
}(protoAddrParts[0], protoAddrParts[1])
|
}(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(protoAddrs); i++ {
|
for i := 0; i < len(protoAddrs); i++ {
|
||||||
@@ -252,6 +254,11 @@ func (s *Server) getVersion(version version.Version, w http.ResponseWriter, r *h
|
|||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if version.GreaterThanOrEqualTo("1.19") {
|
||||||
|
v.Experimental = utils.ExperimentalBuild()
|
||||||
|
}
|
||||||
|
|
||||||
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
||||||
v.KernelVersion = kernelVersion.String()
|
v.KernelVersion = kernelVersion.String()
|
||||||
}
|
}
|
||||||
@@ -271,14 +278,20 @@ func (s *Server) postContainersKill(version version.Version, w http.ResponseWrit
|
|||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
|
|
||||||
// If we have a signal, look at it. Otherwise, do nothing
|
// If we have a signal, look at it. Otherwise, do nothing
|
||||||
if sigStr := vars["signal"]; sigStr != "" {
|
if sigStr := r.Form.Get("signal"); sigStr != "" {
|
||||||
// Check if we passed the signal as a number:
|
// Check if we passed the signal as a number:
|
||||||
// The largest legal signal is 31, so let's parse on 5 bits
|
// The largest legal signal is 31, so let's parse on 5 bits
|
||||||
sig, err := strconv.ParseUint(sigStr, 10, 5)
|
sigN, err := strconv.ParseUint(sigStr, 10, 5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The signal is not a number, treat it as a string (either like
|
// The signal is not a number, treat it as a string (either like
|
||||||
// "KILL" or like "SIGKILL")
|
// "KILL" or like "SIGKILL")
|
||||||
sig = uint64(signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")])
|
syscallSig, ok := signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Invalid signal: %s", sigStr)
|
||||||
|
}
|
||||||
|
sig = uint64(syscallSig)
|
||||||
|
} else {
|
||||||
|
sig = sigN
|
||||||
}
|
}
|
||||||
|
|
||||||
if sig == 0 {
|
if sig == 0 {
|
||||||
@@ -453,6 +466,12 @@ func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var closeNotify <-chan bool
|
||||||
|
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
||||||
|
closeNotify = closeNotifier.CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case ev := <-l:
|
case ev := <-l:
|
||||||
@@ -465,6 +484,9 @@ func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
return nil
|
return nil
|
||||||
|
case <-closeNotify:
|
||||||
|
logrus.Debug("Client disconnected, stop sending events")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -550,7 +572,7 @@ func (s *Server) getContainersStats(version version.Version, w http.ResponseWrit
|
|||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.daemon.ContainerStats(vars["name"], boolValue(r, "stream"), ioutils.NewWriteFlusher(w))
|
return s.daemon.ContainerStats(vars["name"], boolValueOrDefault(r, "stream", true), ioutils.NewWriteFlusher(w))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getContainersLogs(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *Server) getContainersLogs(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
@@ -1133,6 +1155,14 @@ func (s *Server) getContainersByName(version version.Version, w http.ResponseWri
|
|||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if version.LessThan("1.19") {
|
||||||
|
containerJSONRaw, err := s.daemon.ContainerInspectRaw(vars["name"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return writeJSON(w, http.StatusOK, containerJSONRaw)
|
||||||
|
}
|
||||||
|
|
||||||
containerJSON, err := s.daemon.ContainerInspect(vars["name"])
|
containerJSON, err := s.daemon.ContainerInspect(vars["name"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+15
-24
@@ -12,57 +12,48 @@ import (
|
|||||||
"github.com/docker/docker/pkg/systemd"
|
"github.com/docker/docker/pkg/systemd"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newServer sets up the required serverCloser and does protocol specific checking.
|
// newServer sets up the required serverClosers and does protocol specific checking.
|
||||||
func (s *Server) newServer(proto, addr string) (serverCloser, error) {
|
func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
l net.Listener
|
ls []net.Listener
|
||||||
)
|
)
|
||||||
switch proto {
|
switch proto {
|
||||||
case "fd":
|
case "fd":
|
||||||
ls, err := systemd.ListenFD(addr)
|
ls, err = systemd.ListenFD(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
chErrors := make(chan error, len(ls))
|
|
||||||
// We don't want to start serving on these sockets until the
|
// We don't want to start serving on these sockets until the
|
||||||
// daemon is initialized and installed. Otherwise required handlers
|
// daemon is initialized and installed. Otherwise required handlers
|
||||||
// won't be ready.
|
// won't be ready.
|
||||||
<-s.start
|
<-s.start
|
||||||
// Since ListenFD will return one or more sockets we have
|
|
||||||
// to create a go func to spawn off multiple serves
|
|
||||||
for i := range ls {
|
|
||||||
listener := ls[i]
|
|
||||||
go func() {
|
|
||||||
httpSrv := http.Server{Handler: s.router}
|
|
||||||
chErrors <- httpSrv.Serve(listener)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
for i := 0; i < len(ls); i++ {
|
|
||||||
if err := <-chErrors; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
case "tcp":
|
case "tcp":
|
||||||
l, err = s.initTcpSocket(addr)
|
l, err := s.initTcpSocket(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ls = append(ls, l)
|
||||||
case "unix":
|
case "unix":
|
||||||
if l, err = sockets.NewUnixSocket(addr, s.cfg.SocketGroup, s.start); err != nil {
|
l, err := sockets.NewUnixSocket(addr, s.cfg.SocketGroup, s.start)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ls = append(ls, l)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Invalid protocol format: %q", proto)
|
return nil, fmt.Errorf("Invalid protocol format: %q", proto)
|
||||||
}
|
}
|
||||||
return &HttpServer{
|
var res []serverCloser
|
||||||
|
for _, l := range ls {
|
||||||
|
res = append(res, &HttpServer{
|
||||||
&http.Server{
|
&http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: s.router,
|
Handler: s.router,
|
||||||
},
|
},
|
||||||
l,
|
l,
|
||||||
}, nil
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ type Network struct {
|
|||||||
type Stats struct {
|
type Stats struct {
|
||||||
Read time.Time `json:"read"`
|
Read time.Time `json:"read"`
|
||||||
Network Network `json:"network,omitempty"`
|
Network Network `json:"network,omitempty"`
|
||||||
|
PreCpuStats CpuStats `json:"precpu_stats,omitempty"`
|
||||||
CpuStats CpuStats `json:"cpu_stats,omitempty"`
|
CpuStats CpuStats `json:"cpu_stats,omitempty"`
|
||||||
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
||||||
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
||||||
|
|||||||
+30
-9
@@ -102,15 +102,15 @@ type Port struct {
|
|||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
Names []string `json:",omitempty"`
|
Names []string
|
||||||
Image string `json:",omitempty"`
|
Image string
|
||||||
Command string `json:",omitempty"`
|
Command string
|
||||||
Created int `json:",omitempty"`
|
Created int
|
||||||
Ports []Port `json:",omitempty"`
|
Ports []Port
|
||||||
SizeRw int `json:",omitempty"`
|
SizeRw int `json:",omitempty"`
|
||||||
SizeRootFs int `json:",omitempty"`
|
SizeRootFs int `json:",omitempty"`
|
||||||
Labels map[string]string `json:",omitempty"`
|
Labels map[string]string
|
||||||
Status string `json:",omitempty"`
|
Status string
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST "/containers/"+containerID+"/copy"
|
// POST "/containers/"+containerID+"/copy"
|
||||||
@@ -132,6 +132,7 @@ type Version struct {
|
|||||||
Os string
|
Os string
|
||||||
Arch string
|
Arch string
|
||||||
KernelVersion string `json:",omitempty"`
|
KernelVersion string `json:",omitempty"`
|
||||||
|
Experimental bool `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET "/info"
|
// GET "/info"
|
||||||
@@ -194,12 +195,11 @@ type ContainerState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GET "/containers/{name:.*}/json"
|
// GET "/containers/{name:.*}/json"
|
||||||
type ContainerJSON struct {
|
type ContainerJSONBase struct {
|
||||||
Id string
|
Id string
|
||||||
Created time.Time
|
Created time.Time
|
||||||
Path string
|
Path string
|
||||||
Args []string
|
Args []string
|
||||||
Config *runconfig.Config
|
|
||||||
State *ContainerState
|
State *ContainerState
|
||||||
Image string
|
Image string
|
||||||
NetworkSettings *network.Settings
|
NetworkSettings *network.Settings
|
||||||
@@ -219,3 +219,24 @@ type ContainerJSON struct {
|
|||||||
ExecIDs []string
|
ExecIDs []string
|
||||||
HostConfig *runconfig.HostConfig
|
HostConfig *runconfig.HostConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContainerJSON struct {
|
||||||
|
*ContainerJSONBase
|
||||||
|
Config *runconfig.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// backcompatibility struct along with ContainerConfig
|
||||||
|
type ContainerJSONRaw struct {
|
||||||
|
*ContainerJSONBase
|
||||||
|
Config *ContainerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerConfig struct {
|
||||||
|
*runconfig.Config
|
||||||
|
|
||||||
|
// backward compatibility, they now live in HostConfig
|
||||||
|
Memory int64
|
||||||
|
MemorySwap int64
|
||||||
|
CpuShares int64
|
||||||
|
Cpuset string
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#
|
||||||
|
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/rpm/generate.sh"!
|
||||||
|
#
|
||||||
|
|
||||||
|
FROM fedora:22
|
||||||
|
|
||||||
|
RUN yum install -y @development-tools fedora-packager
|
||||||
|
RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel sqlite-devel tar
|
||||||
|
|
||||||
|
ENV GO_VERSION 1.4.2
|
||||||
|
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||||
|
ENV PATH $PATH:/usr/local/go/bin
|
||||||
|
|
||||||
|
ENV AUTO_GOPATH 1
|
||||||
|
ENV DOCKER_BUILDTAGS selinux
|
||||||
@@ -212,11 +212,12 @@ _docker_docker() {
|
|||||||
--selinux-enabled
|
--selinux-enabled
|
||||||
--tls
|
--tls
|
||||||
--tlsverify
|
--tlsverify
|
||||||
|
--userland-proxy=false
|
||||||
--version -v
|
--version -v
|
||||||
"
|
"
|
||||||
|
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
--graph|-g)
|
--exec-root|--graph|-g)
|
||||||
_filedir -d
|
_filedir -d
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
@@ -267,22 +268,25 @@ _docker_attach() {
|
|||||||
|
|
||||||
_docker_build() {
|
_docker_build() {
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
--tag|-t)
|
--cgroup-parent|--cpuset-cpus|--cpuset-mems|--cpu-shares|-c|--cpu-period|--cpu-quota|--memory|-m|--memory-swap)
|
||||||
__docker_image_repos_and_tags
|
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--file|-f)
|
--file|-f)
|
||||||
_filedir
|
_filedir
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
|
--tag|-t)
|
||||||
|
__docker_image_repos_and_tags
|
||||||
|
return
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "--cpu-shares -c --cpuset-cpus --cpu-quota --file -f --force-rm --help --memory -m --memory-swap --no-cache --pull --quiet -q --rm --tag -t" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "--cgroup-parent --cpuset-cpus --cpuset-mems --cpu-shares -c --cpu-period --cpu-quota --file -f --force-rm --help --memory -m --memory-swap --no-cache --pull --quiet -q --rm --tag -t" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
local counter="$(__docker_pos_first_nonflag '--tag|-t')"
|
local counter="$(__docker_pos_first_nonflag '--cgroup-parent|--cpuset-cpus|--cpuset-mems|--cpu-shares|-c|--cpu-period|--cpu-quota|--file|-f|--memory|-m|--memory-swap|--tag|-t')"
|
||||||
if [ $cword -eq $counter ]; then
|
if [ $cword -eq $counter ]; then
|
||||||
_filedir -d
|
_filedir -d
|
||||||
fi
|
fi
|
||||||
@@ -405,6 +409,12 @@ _docker_events() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_docker_exec() {
|
_docker_exec() {
|
||||||
|
case "$prev" in
|
||||||
|
--user|-u)
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i -t --tty -u --user" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i -t --tty -u --user" -- "$cur" ) )
|
||||||
@@ -586,7 +596,7 @@ _docker_logout() {
|
|||||||
|
|
||||||
_docker_logs() {
|
_docker_logs() {
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
--tail)
|
--since|--tail)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -771,15 +781,16 @@ _docker_rmi() {
|
|||||||
_docker_run() {
|
_docker_run() {
|
||||||
local options_with_args="
|
local options_with_args="
|
||||||
--add-host
|
--add-host
|
||||||
|
--blkio-weight
|
||||||
--attach -a
|
--attach -a
|
||||||
--cap-add
|
--cap-add
|
||||||
--cap-drop
|
--cap-drop
|
||||||
--cgroup-parent
|
--cgroup-parent
|
||||||
--cidfile
|
--cidfile
|
||||||
--cpuset
|
--cpuset
|
||||||
--cpu-shares -c
|
|
||||||
--cpu-period
|
--cpu-period
|
||||||
--cpu-quota
|
--cpu-quota
|
||||||
|
--cpu-shares -c
|
||||||
--device
|
--device
|
||||||
--dns
|
--dns
|
||||||
--dns-search
|
--dns-search
|
||||||
@@ -805,6 +816,7 @@ _docker_run() {
|
|||||||
--security-opt
|
--security-opt
|
||||||
--user -u
|
--user -u
|
||||||
--ulimit
|
--ulimit
|
||||||
|
--uts
|
||||||
--volumes-from
|
--volumes-from
|
||||||
--volume -v
|
--volume -v
|
||||||
--workdir -w
|
--workdir -w
|
||||||
@@ -1156,6 +1168,8 @@ _docker() {
|
|||||||
--api-cors-header
|
--api-cors-header
|
||||||
--bip
|
--bip
|
||||||
--bridge -b
|
--bridge -b
|
||||||
|
--default-gateway
|
||||||
|
--default-gateway-v6
|
||||||
--default-ulimit
|
--default-ulimit
|
||||||
--dns
|
--dns
|
||||||
--dns-search
|
--dns-search
|
||||||
@@ -1203,6 +1217,9 @@ _docker() {
|
|||||||
;;
|
;;
|
||||||
-*)
|
-*)
|
||||||
;;
|
;;
|
||||||
|
=)
|
||||||
|
(( counter++ ))
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
command="${words[$counter]}"
|
command="${words[$counter]}"
|
||||||
cpos=$counter
|
cpos=$counter
|
||||||
|
|||||||
@@ -1054,6 +1054,15 @@ func (container *Container) networkMounts() []execdriver.Mount {
|
|||||||
return mounts
|
return mounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (container *Container) addBindMountPoint(name, source, destination string, rw bool) {
|
||||||
|
container.MountPoints[destination] = &mountPoint{
|
||||||
|
Name: name,
|
||||||
|
Source: source,
|
||||||
|
Destination: destination,
|
||||||
|
RW: rw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (container *Container) addLocalMountPoint(name, destination string, rw bool) {
|
func (container *Container) addLocalMountPoint(name, destination string, rw bool) {
|
||||||
container.MountPoints[destination] = &mountPoint{
|
container.MountPoints[destination] = &mountPoint{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ func getDevicesFromPath(deviceMapping runconfig.DeviceMapping) (devs []*configs.
|
|||||||
|
|
||||||
func populateCommand(c *Container, env []string) error {
|
func populateCommand(c *Container, env []string) error {
|
||||||
var en *execdriver.Network
|
var en *execdriver.Network
|
||||||
if !c.daemon.config.DisableNetwork {
|
if !c.Config.NetworkDisabled {
|
||||||
en = &execdriver.Network{
|
en = &execdriver.Network{
|
||||||
NamespacePath: c.NetworkSettings.SandboxKey,
|
NamespacePath: c.NetworkSettings.SandboxKey,
|
||||||
}
|
}
|
||||||
@@ -227,9 +227,10 @@ func populateCommand(c *Container, env []string) error {
|
|||||||
|
|
||||||
userSpecifiedDevices = append(userSpecifiedDevices, devs...)
|
userSpecifiedDevices = append(userSpecifiedDevices, devs...)
|
||||||
}
|
}
|
||||||
allowedDevices := append(configs.DefaultAllowedDevices, userSpecifiedDevices...)
|
|
||||||
|
|
||||||
autoCreatedDevices := append(configs.DefaultAutoCreatedDevices, userSpecifiedDevices...)
|
allowedDevices := mergeDevices(configs.DefaultAllowedDevices, userSpecifiedDevices)
|
||||||
|
|
||||||
|
autoCreatedDevices := mergeDevices(configs.DefaultAutoCreatedDevices, userSpecifiedDevices)
|
||||||
|
|
||||||
// TODO: this can be removed after lxc-conf is fully deprecated
|
// TODO: this can be removed after lxc-conf is fully deprecated
|
||||||
lxcConfig, err := mergeLxcConfIntoOptions(c.hostConfig)
|
lxcConfig, err := mergeLxcConfIntoOptions(c.hostConfig)
|
||||||
@@ -309,6 +310,25 @@ func populateCommand(c *Container, env []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mergeDevices(defaultDevices, userDevices []*configs.Device) []*configs.Device {
|
||||||
|
if len(userDevices) == 0 {
|
||||||
|
return defaultDevices
|
||||||
|
}
|
||||||
|
|
||||||
|
paths := map[string]*configs.Device{}
|
||||||
|
for _, d := range userDevices {
|
||||||
|
paths[d.Path] = d
|
||||||
|
}
|
||||||
|
|
||||||
|
var devs []*configs.Device
|
||||||
|
for _, d := range defaultDevices {
|
||||||
|
if _, defined := paths[d.Path]; !defined {
|
||||||
|
devs = append(devs, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(devs, userDevices...)
|
||||||
|
}
|
||||||
|
|
||||||
// GetSize, return real size, virtual size
|
// GetSize, return real size, virtual size
|
||||||
func (container *Container) GetSize() (int64, int64) {
|
func (container *Container) GetSize() (int64, int64) {
|
||||||
var (
|
var (
|
||||||
@@ -493,13 +513,23 @@ func (container *Container) buildPortMapInfo(n libnetwork.Network, ep libnetwork
|
|||||||
networkSettings.MacAddress = mac.(net.HardwareAddr).String()
|
networkSettings.MacAddress = mac.(net.HardwareAddr).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
networkSettings.Ports = nat.PortMap{}
|
||||||
|
|
||||||
|
if expData, ok := driverInfo[netlabel.ExposedPorts]; ok {
|
||||||
|
if exposedPorts, ok := expData.([]types.TransportPort); ok {
|
||||||
|
for _, tp := range exposedPorts {
|
||||||
|
natPort := nat.NewPort(tp.Proto.String(), strconv.Itoa(int(tp.Port)))
|
||||||
|
networkSettings.Ports[natPort] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mapData, ok := driverInfo[netlabel.PortMap]
|
mapData, ok := driverInfo[netlabel.PortMap]
|
||||||
if !ok {
|
if !ok {
|
||||||
return networkSettings, nil
|
return networkSettings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if portMapping, ok := mapData.([]types.PortBinding); ok {
|
if portMapping, ok := mapData.([]types.PortBinding); ok {
|
||||||
networkSettings.Ports = nat.PortMap{}
|
|
||||||
for _, pp := range portMapping {
|
for _, pp := range portMapping {
|
||||||
natPort := nat.NewPort(pp.Proto.String(), strconv.Itoa(int(pp.Port)))
|
natPort := nat.NewPort(pp.Proto.String(), strconv.Itoa(int(pp.Port)))
|
||||||
natBndg := nat.PortBinding{HostIp: pp.HostIP.String(), HostPort: strconv.Itoa(int(pp.HostPort))}
|
natBndg := nat.PortBinding{HostIp: pp.HostIP.String(), HostPort: strconv.Itoa(int(pp.HostPort))}
|
||||||
@@ -913,6 +943,12 @@ func (container *Container) ReleaseNetwork() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the container is not attached to any network do not try
|
||||||
|
// to release network and generate spurious error messages.
|
||||||
|
if container.NetworkSettings.NetworkID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
n, err := container.daemon.netController.NetworkByID(container.NetworkSettings.NetworkID)
|
n, err := container.daemon.netController.NetworkByID(container.NetworkSettings.NetworkID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("error locating network id %s: %v", container.NetworkSettings.NetworkID, err)
|
logrus.Errorf("error locating network id %s: %v", container.NetworkSettings.NetworkID, err)
|
||||||
|
|||||||
+4
-3
@@ -213,7 +213,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
|
|||||||
// we'll waste time if we update it for every container
|
// we'll waste time if we update it for every container
|
||||||
daemon.idIndex.Add(container.ID)
|
daemon.idIndex.Add(container.ID)
|
||||||
|
|
||||||
if err := daemon.verifyOldVolumesInfo(container); err != nil {
|
if err := daemon.verifyVolumesInfo(container); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1003,8 +1003,9 @@ func (daemon *Daemon) Shutdown() error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer group.Done()
|
defer group.Done()
|
||||||
if err := c.KillSig(15); err != nil {
|
// If container failed to exit in 10 seconds of SIGTERM, then using the force
|
||||||
logrus.Debugf("kill 15 error for %s - %s", c.ID, err)
|
if err := c.Stop(10); err != nil {
|
||||||
|
logrus.Errorf("Stop container %s with error: %v", c.ID, err)
|
||||||
}
|
}
|
||||||
c.WaitStop(-1 * time.Second)
|
c.WaitStop(-1 * time.Second)
|
||||||
logrus.Debugf("container stopped %s", c.ID)
|
logrus.Debugf("container stopped %s", c.ID)
|
||||||
|
|||||||
+104
-8
@@ -129,12 +129,25 @@ func TestLoadWithVolume(t *testing.T) {
|
|||||||
|
|
||||||
containerId := "d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e"
|
containerId := "d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e"
|
||||||
containerPath := filepath.Join(tmp, containerId)
|
containerPath := filepath.Join(tmp, containerId)
|
||||||
if err = os.MkdirAll(containerPath, 0755); err != nil {
|
if err := os.MkdirAll(containerPath, 0755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostVolumeId := stringid.GenerateRandomID()
|
hostVolumeId := stringid.GenerateRandomID()
|
||||||
volumePath := filepath.Join(tmp, "vfs", "dir", hostVolumeId)
|
vfsPath := filepath.Join(tmp, "vfs", "dir", hostVolumeId)
|
||||||
|
volumePath := filepath.Join(tmp, "volumes", hostVolumeId)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(vfsPath, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(volumePath, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
content := filepath.Join(vfsPath, "helo")
|
||||||
|
if err := ioutil.WriteFile(content, []byte("HELO"), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
config := `{"State":{"Running":true,"Paused":false,"Restarting":false,"OOMKilled":false,"Dead":false,"Pid":2464,"ExitCode":0,
|
config := `{"State":{"Running":true,"Paused":false,"Restarting":false,"OOMKilled":false,"Dead":false,"Pid":2464,"ExitCode":0,
|
||||||
"Error":"","StartedAt":"2015-05-26T16:48:53.869308965Z","FinishedAt":"0001-01-01T00:00:00Z"},
|
"Error":"","StartedAt":"2015-05-26T16:48:53.869308965Z","FinishedAt":"0001-01-01T00:00:00Z"},
|
||||||
@@ -152,7 +165,7 @@ func TestLoadWithVolume(t *testing.T) {
|
|||||||
"Name":"/ubuntu","Driver":"aufs","ExecDriver":"native-0.2","MountLabel":"","ProcessLabel":"","AppArmorProfile":"","RestartCount":0,
|
"Name":"/ubuntu","Driver":"aufs","ExecDriver":"native-0.2","MountLabel":"","ProcessLabel":"","AppArmorProfile":"","RestartCount":0,
|
||||||
"UpdateDns":false,"Volumes":{"/vol1":"%s"},"VolumesRW":{"/vol1":true},"AppliedVolumesFrom":null}`
|
"UpdateDns":false,"Volumes":{"/vol1":"%s"},"VolumesRW":{"/vol1":true},"AppliedVolumesFrom":null}`
|
||||||
|
|
||||||
cfg := fmt.Sprintf(config, volumePath)
|
cfg := fmt.Sprintf(config, vfsPath)
|
||||||
if err = ioutil.WriteFile(filepath.Join(containerPath, "config.json"), []byte(cfg), 0644); err != nil {
|
if err = ioutil.WriteFile(filepath.Join(containerPath, "config.json"), []byte(cfg), 0644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -165,10 +178,6 @@ func TestLoadWithVolume(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = os.MkdirAll(volumePath, 0755); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
daemon := &Daemon{
|
daemon := &Daemon{
|
||||||
repository: tmp,
|
repository: tmp,
|
||||||
root: tmp,
|
root: tmp,
|
||||||
@@ -179,7 +188,7 @@ func TestLoadWithVolume(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = daemon.verifyOldVolumesInfo(c)
|
err = daemon.verifyVolumesInfo(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -204,4 +213,91 @@ func TestLoadWithVolume(t *testing.T) {
|
|||||||
if m.Driver != volume.DefaultDriverName {
|
if m.Driver != volume.DefaultDriverName {
|
||||||
t.Fatalf("Expected mount driver local, was %s\n", m.Driver)
|
t.Fatalf("Expected mount driver local, was %s\n", m.Driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newVolumeContent := filepath.Join(volumePath, "helo")
|
||||||
|
b, err := ioutil.ReadFile(newVolumeContent)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != "HELO" {
|
||||||
|
t.Fatalf("Expected HELO, was %s\n", string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadWithBindMount(t *testing.T) {
|
||||||
|
tmp, err := ioutil.TempDir("", "docker-daemon-test-")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmp)
|
||||||
|
|
||||||
|
containerId := "d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e"
|
||||||
|
containerPath := filepath.Join(tmp, containerId)
|
||||||
|
if err = os.MkdirAll(containerPath, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := `{"State":{"Running":true,"Paused":false,"Restarting":false,"OOMKilled":false,"Dead":false,"Pid":2464,"ExitCode":0,
|
||||||
|
"Error":"","StartedAt":"2015-05-26T16:48:53.869308965Z","FinishedAt":"0001-01-01T00:00:00Z"},
|
||||||
|
"ID":"d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e","Created":"2015-05-26T16:48:53.7987917Z","Path":"top",
|
||||||
|
"Args":[],"Config":{"Hostname":"d59df5276e7b","Domainname":"","User":"","Memory":0,"MemorySwap":0,"CpuShares":0,"Cpuset":"",
|
||||||
|
"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":true,"OpenStdin":true,
|
||||||
|
"StdinOnce":false,"Env":null,"Cmd":["top"],"Image":"ubuntu:latest","Volumes":null,"WorkingDir":"","Entrypoint":null,
|
||||||
|
"NetworkDisabled":false,"MacAddress":"","OnBuild":null,"Labels":{}},"Image":"07f8e8c5e66084bef8f848877857537ffe1c47edd01a93af27e7161672ad0e95",
|
||||||
|
"NetworkSettings":{"IPAddress":"172.17.0.1","IPPrefixLen":16,"MacAddress":"02:42:ac:11:00:01","LinkLocalIPv6Address":"fe80::42:acff:fe11:1",
|
||||||
|
"LinkLocalIPv6PrefixLen":64,"GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"Gateway":"172.17.42.1","IPv6Gateway":"","Bridge":"docker0","PortMapping":null,"Ports":{}},
|
||||||
|
"ResolvConfPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/resolv.conf",
|
||||||
|
"HostnamePath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/hostname",
|
||||||
|
"HostsPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/hosts",
|
||||||
|
"LogPath":"/var/lib/docker/containers/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e/d59df5276e7b219d510fe70565e0404bc06350e0d4b43fe961f22f339980170e-json.log",
|
||||||
|
"Name":"/ubuntu","Driver":"aufs","ExecDriver":"native-0.2","MountLabel":"","ProcessLabel":"","AppArmorProfile":"","RestartCount":0,
|
||||||
|
"UpdateDns":false,"Volumes":{"/vol1": "/vol1"},"VolumesRW":{"/vol1":true},"AppliedVolumesFrom":null}`
|
||||||
|
|
||||||
|
if err = ioutil.WriteFile(filepath.Join(containerPath, "config.json"), []byte(config), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig := `{"Binds":["/vol1:/vol1"],"ContainerIDFile":"","LxcConf":[],"Memory":0,"MemorySwap":0,"CpuShares":0,"CpusetCpus":"",
|
||||||
|
"Privileged":false,"PortBindings":{},"Links":null,"PublishAllPorts":false,"Dns":null,"DnsSearch":null,"ExtraHosts":null,"VolumesFrom":null,
|
||||||
|
"Devices":[],"NetworkMode":"bridge","IpcMode":"","PidMode":"","CapAdd":null,"CapDrop":null,"RestartPolicy":{"Name":"no","MaximumRetryCount":0},
|
||||||
|
"SecurityOpt":null,"ReadonlyRootfs":false,"Ulimits":null,"LogConfig":{"Type":"","Config":null},"CgroupParent":""}`
|
||||||
|
if err = ioutil.WriteFile(filepath.Join(containerPath, "hostconfig.json"), []byte(hostConfig), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
daemon := &Daemon{
|
||||||
|
repository: tmp,
|
||||||
|
root: tmp,
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := daemon.load(containerId)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = daemon.verifyVolumesInfo(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.MountPoints) != 1 {
|
||||||
|
t.Fatalf("Expected 1 volume mounted, was 0\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
m := c.MountPoints["/vol1"]
|
||||||
|
if m.Name != "" {
|
||||||
|
t.Fatalf("Expected empty mount name, was %s\n", m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Source != "/vol1" {
|
||||||
|
t.Fatalf("Expected mount source /vol1, was %s\n", m.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Destination != "/vol1" {
|
||||||
|
t.Fatalf("Expected mount destination /vol1, was %s\n", m.Destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.RW {
|
||||||
|
t.Fatalf("Expected mount point to be RW but it was not\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi
|
|||||||
repos := daemon.Repositories().ByID()[img.ID]
|
repos := daemon.Repositories().ByID()[img.ID]
|
||||||
|
|
||||||
//If delete by id, see if the id belong only to one repository
|
//If delete by id, see if the id belong only to one repository
|
||||||
if repoName == "" {
|
deleteByID := repoName == ""
|
||||||
|
if deleteByID {
|
||||||
for _, repoAndTag := range repos {
|
for _, repoAndTag := range repos {
|
||||||
parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag)
|
parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag)
|
||||||
if repoName == "" || repoName == parsedRepo {
|
if repoName == "" || repoName == parsedRepo {
|
||||||
@@ -91,7 +92,7 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(repos) <= 1 {
|
if len(repos) <= 1 || (len(repoAndTags) <= 1 && deleteByID) {
|
||||||
if err := daemon.canDeleteImage(img.ID, force); err != nil {
|
if err := daemon.canDeleteImage(img.ID, force); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-3
@@ -25,6 +25,40 @@ func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error
|
|||||||
container.Lock()
|
container.Lock()
|
||||||
defer container.Unlock()
|
defer container.Unlock()
|
||||||
|
|
||||||
|
base, err := daemon.getInspectData(container)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.ContainerJSON{base, container.Config}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (daemon *Daemon) ContainerInspectRaw(name string) (*types.ContainerJSONRaw, error) {
|
||||||
|
container, err := daemon.Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
container.Lock()
|
||||||
|
defer container.Unlock()
|
||||||
|
|
||||||
|
base, err := daemon.getInspectData(container)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &types.ContainerConfig{
|
||||||
|
container.Config,
|
||||||
|
container.hostConfig.Memory,
|
||||||
|
container.hostConfig.MemorySwap,
|
||||||
|
container.hostConfig.CpuShares,
|
||||||
|
container.hostConfig.CpusetCpus,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.ContainerJSONRaw{base, config}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (daemon *Daemon) getInspectData(container *Container) (*types.ContainerJSONBase, error) {
|
||||||
// make a copy to play with
|
// make a copy to play with
|
||||||
hostConfig := *container.hostConfig
|
hostConfig := *container.hostConfig
|
||||||
|
|
||||||
@@ -60,12 +94,11 @@ func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error
|
|||||||
volumesRW[m.Destination] = m.RW
|
volumesRW[m.Destination] = m.RW
|
||||||
}
|
}
|
||||||
|
|
||||||
contJSON := &types.ContainerJSON{
|
contJSONBase := &types.ContainerJSONBase{
|
||||||
Id: container.ID,
|
Id: container.ID,
|
||||||
Created: container.Created,
|
Created: container.Created,
|
||||||
Path: container.Path,
|
Path: container.Path,
|
||||||
Args: container.Args,
|
Args: container.Args,
|
||||||
Config: container.Config,
|
|
||||||
State: containerState,
|
State: containerState,
|
||||||
Image: container.ImageID,
|
Image: container.ImageID,
|
||||||
NetworkSettings: container.NetworkSettings,
|
NetworkSettings: container.NetworkSettings,
|
||||||
@@ -86,7 +119,7 @@ func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error
|
|||||||
HostConfig: &hostConfig,
|
HostConfig: &hostConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
return contJSON, nil
|
return contJSONBase, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) ContainerExecInspect(id string) (*execConfig, error) {
|
func (daemon *Daemon) ContainerExecInspect(id string) (*execConfig, error) {
|
||||||
|
|||||||
+19
-5
@@ -2,6 +2,7 @@ package logger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -40,15 +41,28 @@ func (c *Copier) Run() {
|
|||||||
|
|
||||||
func (c *Copier) copySrc(name string, src io.Reader) {
|
func (c *Copier) copySrc(name string, src io.Reader) {
|
||||||
defer c.copyJobs.Done()
|
defer c.copyJobs.Done()
|
||||||
scanner := bufio.NewScanner(src)
|
reader := bufio.NewReader(src)
|
||||||
for scanner.Scan() {
|
|
||||||
if err := c.dst.Log(&Message{ContainerID: c.cid, Line: scanner.Bytes(), Source: name, Timestamp: time.Now().UTC()}); err != nil {
|
for {
|
||||||
logrus.Errorf("Failed to log msg %q for logger %s: %s", scanner.Bytes(), c.dst.Name(), err)
|
line, err := reader.ReadBytes('\n')
|
||||||
|
line = bytes.TrimSuffix(line, []byte{'\n'})
|
||||||
|
|
||||||
|
// ReadBytes can return full or partial output even when it failed.
|
||||||
|
// e.g. it can return a full entry and EOF.
|
||||||
|
if err == nil || len(line) > 0 {
|
||||||
|
if logErr := c.dst.Log(&Message{ContainerID: c.cid, Line: line, Source: name, Timestamp: time.Now().UTC()}); logErr != nil {
|
||||||
|
logrus.Errorf("Failed to log msg %q for logger %s: %s", line, c.dst.Name(), logErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
logrus.Errorf("Error scanning log stream: %s", err)
|
logrus.Errorf("Error scanning log stream: %s", err)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait waits until all copying is done
|
// Wait waits until all copying is done
|
||||||
|
|||||||
@@ -3,14 +3,17 @@
|
|||||||
package syslog
|
package syslog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/logger"
|
"github.com/docker/docker/daemon/logger"
|
||||||
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const name = "syslog"
|
const name = "syslog"
|
||||||
@@ -27,7 +30,18 @@ func init() {
|
|||||||
|
|
||||||
func New(ctx logger.Context) (logger.Logger, error) {
|
func New(ctx logger.Context) (logger.Logger, error) {
|
||||||
tag := ctx.ContainerID[:12]
|
tag := ctx.ContainerID[:12]
|
||||||
log, err := syslog.New(syslog.LOG_DAEMON, fmt.Sprintf("%s/%s", path.Base(os.Args[0]), tag))
|
|
||||||
|
proto, address, err := parseAddress(ctx.Config["syslog-address"])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log, err := syslog.Dial(
|
||||||
|
proto,
|
||||||
|
address,
|
||||||
|
syslog.LOG_DAEMON,
|
||||||
|
path.Base(os.Args[0])+"/"+tag,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -55,3 +69,33 @@ func (s *Syslog) Name() string {
|
|||||||
func (s *Syslog) GetReader() (io.Reader, error) {
|
func (s *Syslog) GetReader() (io.Reader, error) {
|
||||||
return nil, logger.ReadLogsNotSupported
|
return nil, logger.ReadLogsNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseAddress(address string) (string, string, error) {
|
||||||
|
if urlutil.IsTransportURL(address) {
|
||||||
|
url, err := url.Parse(address)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix socket validation
|
||||||
|
if url.Scheme == "unix" {
|
||||||
|
if _, err := os.Stat(url.Path); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return url.Scheme, url.Path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we process tcp|udp
|
||||||
|
host := url.Host
|
||||||
|
if _, _, err := net.SplitHostPort(host); err != nil {
|
||||||
|
if !strings.Contains(err.Error(), "missing port in address") {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
host = host + ":514"
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.Scheme, host, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,13 +15,23 @@ func (daemon *Daemon) ContainerStats(name string, stream bool, out io.Writer) er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var pre_cpu_stats types.CpuStats
|
||||||
|
for first_v := range updates {
|
||||||
|
first_update := first_v.(*execdriver.ResourceStats)
|
||||||
|
first_stats := convertToAPITypes(first_update.Stats)
|
||||||
|
pre_cpu_stats = first_stats.CpuStats
|
||||||
|
pre_cpu_stats.SystemUsage = first_update.SystemUsage
|
||||||
|
break
|
||||||
|
}
|
||||||
enc := json.NewEncoder(out)
|
enc := json.NewEncoder(out)
|
||||||
for v := range updates {
|
for v := range updates {
|
||||||
update := v.(*execdriver.ResourceStats)
|
update := v.(*execdriver.ResourceStats)
|
||||||
ss := convertToAPITypes(update.Stats)
|
ss := convertToAPITypes(update.Stats)
|
||||||
|
ss.PreCpuStats = pre_cpu_stats
|
||||||
ss.MemoryStats.Limit = uint64(update.MemoryLimit)
|
ss.MemoryStats.Limit = uint64(update.MemoryLimit)
|
||||||
ss.Read = update.Read
|
ss.Read = update.Read
|
||||||
ss.CpuStats.SystemUsage = update.SystemUsage
|
ss.CpuStats.SystemUsage = update.SystemUsage
|
||||||
|
pre_cpu_stats = ss.CpuStats
|
||||||
if err := enc.Encode(ss); err != nil {
|
if err := enc.Encode(ss); err != nil {
|
||||||
// TODO: handle the specific broken pipe
|
// TODO: handle the specific broken pipe
|
||||||
daemon.UnsubscribeToContainerStats(name, updates)
|
daemon.UnsubscribeToContainerStats(name, updates)
|
||||||
|
|||||||
+47
-5
@@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/pkg/chrootarchive"
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/volume"
|
"github.com/docker/docker/volume"
|
||||||
@@ -74,7 +75,7 @@ func parseBindMount(spec string, mountLabel string, config *runconfig.Config) (*
|
|||||||
return nil, fmt.Errorf("Invalid volume specification: %s", spec)
|
return nil, fmt.Errorf("Invalid volume specification: %s", spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
name, source, err := parseVolumeSource(arr[0], config)
|
name, source, err := parseVolumeSource(arr[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -238,9 +239,9 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyOldVolumesInfo ports volumes configured for the containers pre docker 1.7.
|
// verifyVolumesInfo ports volumes configured for the containers pre docker 1.7.
|
||||||
// It reads the container configuration and creates valid mount points for the old volumes.
|
// It reads the container configuration and creates valid mount points for the old volumes.
|
||||||
func (daemon *Daemon) verifyOldVolumesInfo(container *Container) error {
|
func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
|
||||||
jsonPath, err := container.jsonPath()
|
jsonPath, err := container.jsonPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -268,18 +269,59 @@ func (daemon *Daemon) verifyOldVolumesInfo(container *Container) error {
|
|||||||
|
|
||||||
for destination, hostPath := range vols.Volumes {
|
for destination, hostPath := range vols.Volumes {
|
||||||
vfsPath := filepath.Join(daemon.root, "vfs", "dir")
|
vfsPath := filepath.Join(daemon.root, "vfs", "dir")
|
||||||
|
rw := vols.VolumesRW != nil && vols.VolumesRW[destination]
|
||||||
|
|
||||||
if strings.HasPrefix(hostPath, vfsPath) {
|
if strings.HasPrefix(hostPath, vfsPath) {
|
||||||
id := filepath.Base(hostPath)
|
id := filepath.Base(hostPath)
|
||||||
|
if err := daemon.migrateVolume(id, hostPath); err != nil {
|
||||||
rw := vols.VolumesRW != nil && vols.VolumesRW[destination]
|
return err
|
||||||
|
}
|
||||||
container.addLocalMountPoint(id, destination, rw)
|
container.addLocalMountPoint(id, destination, rw)
|
||||||
|
} else { // Bind mount
|
||||||
|
id, source, err := parseVolumeSource(hostPath)
|
||||||
|
// We should not find an error here coming
|
||||||
|
// from the old configuration, but who knows.
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
container.addBindMountPoint(id, source, destination, rw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return container.ToDisk()
|
return container.ToDisk()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// migrateVolume moves the contents of a volume created pre Docker 1.7
|
||||||
|
// to the location expected by the local driver. Steps:
|
||||||
|
// 1. Save old directory that includes old volume's config json file.
|
||||||
|
// 2. Move virtual directory with content to where the local driver expects it to be.
|
||||||
|
// 3. Remove the backup of the old volume config.
|
||||||
|
func (daemon *Daemon) migrateVolume(id, vfs string) error {
|
||||||
|
volumeInfo := filepath.Join(daemon.root, defaultVolumesPathName, id)
|
||||||
|
backup := filepath.Join(daemon.root, defaultVolumesPathName, id+".back")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if err = os.Rename(volumeInfo, backup); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// Put old configuration back in place in case one of the next steps fails.
|
||||||
|
if err != nil {
|
||||||
|
os.Rename(backup, volumeInfo)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = os.Rename(vfs, volumeInfo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.RemoveAll(backup); err != nil {
|
||||||
|
logrus.Errorf("Unable to remove volume info backup directory %s: %v", backup, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func createVolume(name, driverName string) (volume.Volume, error) {
|
func createVolume(name, driverName string) (volume.Volume, error) {
|
||||||
vd, err := getVolumeDriver(driverName)
|
vd, err := getVolumeDriver(driverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package daemon
|
|||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
"github.com/docker/docker/volume"
|
"github.com/docker/docker/volume"
|
||||||
"github.com/docker/docker/volume/drivers"
|
"github.com/docker/docker/volume/drivers"
|
||||||
)
|
)
|
||||||
@@ -17,7 +16,7 @@ func getVolumeDriver(name string) (volume.Driver, error) {
|
|||||||
return volumedrivers.Lookup(name)
|
return volumedrivers.Lookup(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVolumeSource(spec string, config *runconfig.Config) (string, string, error) {
|
func parseVolumeSource(spec string) (string, string, error) {
|
||||||
if !filepath.IsAbs(spec) {
|
if !filepath.IsAbs(spec) {
|
||||||
return spec, "", nil
|
return spec, "", nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
"github.com/docker/docker/volume"
|
"github.com/docker/docker/volume"
|
||||||
"github.com/docker/docker/volume/drivers"
|
"github.com/docker/docker/volume/drivers"
|
||||||
)
|
)
|
||||||
@@ -15,7 +14,7 @@ func getVolumeDriver(_ string) (volume.Driver, error) {
|
|||||||
return volumedrivers.Lookup(volume.DefaultDriverName)
|
return volumedrivers.Lookup(volume.DefaultDriverName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVolumeSource(spec string, _ *runconfig.Config) (string, string, error) {
|
func parseVolumeSource(spec string) (string, string, error) {
|
||||||
if !filepath.IsAbs(spec) {
|
if !filepath.IsAbs(spec) {
|
||||||
return "", "", fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", spec)
|
return "", "", fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", spec)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/reexec"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -161,5 +162,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func showVersion() {
|
func showVersion() {
|
||||||
|
if utils.ExperimentalBuild() {
|
||||||
|
fmt.Printf("Docker version %s, build %s, experimental\n", dockerversion.VERSION, dockerversion.GITCOMMIT)
|
||||||
|
} else {
|
||||||
fmt.Printf("Docker version %s, build %s\n", dockerversion.VERSION, dockerversion.GITCOMMIT)
|
fmt.Printf("Docker version %s, build %s\n", dockerversion.VERSION, dockerversion.GITCOMMIT)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@ Docker has two primary branches for documentation:
|
|||||||
| Branch | Description | URL (published via commit-hook) |
|
| Branch | Description | URL (published via commit-hook) |
|
||||||
|----------|--------------------------------|------------------------------------------------------------------------------|
|
|----------|--------------------------------|------------------------------------------------------------------------------|
|
||||||
| `docs` | Official release documentation | [https://docs.docker.com](https://docs.docker.com) |
|
| `docs` | Official release documentation | [https://docs.docker.com](https://docs.docker.com) |
|
||||||
| `master` | Merged but unreleased development work | [http://docs.master.dockerproject.com](http://docs.master.dockerproject.com) |
|
| `master` | Merged but unreleased development work | [http://docs.master.dockerproject.org](http://docs.master.dockerproject.org) |
|
||||||
|
|
||||||
Additions and updates to upcoming releases are made in a feature branch off of
|
Additions and updates to upcoming releases are made in a feature branch off of
|
||||||
the `master` branch. The Docker maintainers also support a `docs` branch that
|
the `master` branch. The Docker maintainers also support a `docs` branch that
|
||||||
@@ -22,7 +22,7 @@ After a release, documentation updates are continually merged into `master` as
|
|||||||
they occur. This work includes new documentation for forthcoming features, bug
|
they occur. This work includes new documentation for forthcoming features, bug
|
||||||
fixes, and other updates. Docker's CI system automatically builds and updates
|
fixes, and other updates. Docker's CI system automatically builds and updates
|
||||||
the `master` documentation after each merge and posts it to
|
the `master` documentation after each merge and posts it to
|
||||||
[http://docs.master.dockerproject.com](http://docs.master.dockerproject.com).
|
[http://docs.master.dockerproject.org](http://docs.master.dockerproject.org).
|
||||||
|
|
||||||
Periodically, the Docker maintainers update `docs.docker.com` between official
|
Periodically, the Docker maintainers update `docs.docker.com` between official
|
||||||
releases of Docker. They do this by cherry-picking commits from `master`,
|
releases of Docker. They do this by cherry-picking commits from `master`,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ docker-create - Create a new container
|
|||||||
[**--link**[=*[]*]]
|
[**--link**[=*[]*]]
|
||||||
[**--lxc-conf**[=*[]*]]
|
[**--lxc-conf**[=*[]*]]
|
||||||
[**--log-driver**[=*[]*]]
|
[**--log-driver**[=*[]*]]
|
||||||
|
[**--log-opt**[=*[]*]]
|
||||||
[**-m**|**--memory**[=*MEMORY*]]
|
[**-m**|**--memory**[=*MEMORY*]]
|
||||||
[**--memory-swap**[=*MEMORY-SWAP*]]
|
[**--memory-swap**[=*MEMORY-SWAP*]]
|
||||||
[**--mac-address**[=*MAC-ADDRESS*]]
|
[**--mac-address**[=*MAC-ADDRESS*]]
|
||||||
@@ -148,6 +149,9 @@ two memory nodes.
|
|||||||
Logging driver for container. Default is defined by daemon `--log-driver` flag.
|
Logging driver for container. Default is defined by daemon `--log-driver` flag.
|
||||||
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
||||||
|
|
||||||
|
**--log-opt**=[]
|
||||||
|
Logging driver specific options.
|
||||||
|
|
||||||
**-m**, **--memory**=""
|
**-m**, **--memory**=""
|
||||||
Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
|
Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ docker-run - Run a command in a new container
|
|||||||
[**--link**[=*[]*]]
|
[**--link**[=*[]*]]
|
||||||
[**--lxc-conf**[=*[]*]]
|
[**--lxc-conf**[=*[]*]]
|
||||||
[**--log-driver**[=*[]*]]
|
[**--log-driver**[=*[]*]]
|
||||||
|
[**--log-opt**[=*[]*]]
|
||||||
[**-m**|**--memory**[=*MEMORY*]]
|
[**-m**|**--memory**[=*MEMORY*]]
|
||||||
[**--memory-swap**[=*MEMORY-SWAP*]]
|
[**--memory-swap**[=*MEMORY-SWAP*]]
|
||||||
[**--mac-address**[=*MAC-ADDRESS*]]
|
[**--mac-address**[=*MAC-ADDRESS*]]
|
||||||
@@ -255,6 +256,9 @@ which interface and port to use.
|
|||||||
Logging driver for container. Default is defined by daemon `--log-driver` flag.
|
Logging driver for container. Default is defined by daemon `--log-driver` flag.
|
||||||
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
||||||
|
|
||||||
|
**--log-opt**=[]
|
||||||
|
Logging driver specific options.
|
||||||
|
|
||||||
**-m**, **--memory**=""
|
**-m**, **--memory**=""
|
||||||
Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
|
Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,9 @@ unix://[/path/to/socket] to use.
|
|||||||
Default driver for container logs. Default is `json-file`.
|
Default driver for container logs. Default is `json-file`.
|
||||||
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
||||||
|
|
||||||
|
**--log-opt**=[]
|
||||||
|
Logging driver specific options.
|
||||||
|
|
||||||
**--mtu**=VALUE
|
**--mtu**=VALUE
|
||||||
Set the containers network mtu. Default is `0`.
|
Set the containers network mtu. Default is `0`.
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ program code and documentation code.
|
|||||||
|
|
||||||
## Merges after pull requests
|
## Merges after pull requests
|
||||||
|
|
||||||
* After a merge, [a master build](https://master.dockerproject.com/) is
|
* After a merge, [a master build](https://master.dockerproject.org/) is
|
||||||
available almost immediately.
|
available almost immediately.
|
||||||
|
|
||||||
* If you made a documentation change, you can see it at
|
* If you made a documentation change, you can see it at
|
||||||
[docs.master.dockerproject.com](http://docs.master.dockerproject.com/).
|
[docs.master.dockerproject.org](http://docs.master.dockerproject.org/).
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ It can take time to see a merged pull request in Docker's official release.
|
|||||||
A master build is available almost immediately though. Docker builds and
|
A master build is available almost immediately though. Docker builds and
|
||||||
updates its development binaries after each merge to `master`.
|
updates its development binaries after each merge to `master`.
|
||||||
|
|
||||||
1. Browse to <a href="https://master.dockerproject.com/" target="_blank">https://master.dockerproject.com/</a>.
|
1. Browse to <a href="https://master.dockerproject.org/" target="_blank">https://master.dockerproject.org/</a>.
|
||||||
|
|
||||||
2. Look for the binary appropriate to your system.
|
2. Look for the binary appropriate to your system.
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ updates its development binaries after each merge to `master`.
|
|||||||
You might want to run the binary in a container though. This
|
You might want to run the binary in a container though. This
|
||||||
will keep your local host environment clean.
|
will keep your local host environment clean.
|
||||||
|
|
||||||
4. View any documentation changes at <a href="http://docs.master.dockerproject.com/" target="_blank">docs.master.dockerproject.com</a>.
|
4. View any documentation changes at <a href="http://docs.master.dockerproject.org/" target="_blank">docs.master.dockerproject.org</a>.
|
||||||
|
|
||||||
Once you've verified everything merged, feel free to delete your feature branch
|
Once you've verified everything merged, feel free to delete your feature branch
|
||||||
from your fork. For information on how to do this,
|
from your fork. For information on how to do this,
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ page_keywords: API, Docker, rcli, REST, documentation
|
|||||||
- The daemon listens on `unix:///var/run/docker.sock` but you can
|
- The daemon listens on `unix:///var/run/docker.sock` but you can
|
||||||
[Bind Docker to another host/port or a Unix socket](
|
[Bind Docker to another host/port or a Unix socket](
|
||||||
/articles/basics/#bind-docker-to-another-hostport-or-a-unix-socket).
|
/articles/basics/#bind-docker-to-another-hostport-or-a-unix-socket).
|
||||||
- The API tends to be REST, but for some complex commands, like `attach`
|
- The API tends to be REST. However, for some complex commands, like `attach`
|
||||||
or `pull`, the HTTP connection is hijacked to transport `STDOUT`,
|
or `pull`, the HTTP connection is hijacked to transport `stdout`,
|
||||||
`STDIN` and `STDERR`.
|
`stdin` and `stderr`.
|
||||||
- When the client API version is newer than the daemon's an HTTP
|
- When the client API version is newer than the daemon's, these calls return an HTTP
|
||||||
`400 Bad Request` error message is returned.
|
`400 Bad Request` error message.
|
||||||
|
|
||||||
# 2. Endpoints
|
# 2. Endpoints
|
||||||
|
|
||||||
@@ -90,10 +90,10 @@ Query Parameters:
|
|||||||
non-running ones.
|
non-running ones.
|
||||||
- **size** – 1/True/true or 0/False/false, Show the containers
|
- **size** – 1/True/true or 0/False/false, Show the containers
|
||||||
sizes
|
sizes
|
||||||
- **filters** - a json encoded value of the filters (a map[string][]string) to process on the containers list. Available filters:
|
- **filters** - a JSON encoded value of the filters (a `map[string][]string`) to process on the containers list. Available filters:
|
||||||
- exited=<int> -- containers with exit code of <int>
|
- `exited=<int>`; -- containers with exit code of `<int>` ;
|
||||||
- status=(restarting|running|paused|exited)
|
- `status=`(`restarting`|`running`|`paused`|`exited`)
|
||||||
- label=`key` or `key=value` of a container label
|
- `label=key` or `key=value` of a container label
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
@@ -186,54 +186,53 @@ Create a container
|
|||||||
|
|
||||||
Json Parameters:
|
Json Parameters:
|
||||||
|
|
||||||
- **Hostname** - A string value containing the desired hostname to use for the
|
- **Hostname** - A string value containing the hostname to use for the
|
||||||
container.
|
container.
|
||||||
- **Domainname** - A string value containing the desired domain name to use
|
- **Domainname** - A string value containing the domain name to use
|
||||||
for the container.
|
for the container.
|
||||||
- **User** - A string value containing the user to use inside the container.
|
- **User** - A string value specifying the user inside the container.
|
||||||
- **Memory** - Memory limit in bytes.
|
- **Memory** - Memory limit in bytes.
|
||||||
- **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap,
|
- **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap
|
||||||
always use this with `memory`, and make the value larger than `memory`.
|
You must use this with `memory` and make the swap value larger than `memory`.
|
||||||
- **CpuShares** - An integer value containing the CPU Shares for container
|
- **CpuShares** - An integer value containing the container's CPU Shares
|
||||||
(ie. the relative weight vs other containers).
|
(ie. the relative weight vs other containers).
|
||||||
- **CpuPeriod** - The length of a CPU period (in microseconds).
|
- **CpuPeriod** - The length of a CPU period in microseconds.
|
||||||
- **Cpuset** - The same as CpusetCpus, but deprecated, please don't use.
|
- **Cpuset** - Deprecated please don't use. Use `CpusetCpus` instead.
|
||||||
- **CpusetCpus** - String value containing the cgroups CpusetCpus to use.
|
- **CpusetCpus** - String value containing the `cgroups CpusetCpus` to use.
|
||||||
- **CpusetMems** - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
|
- **CpusetMems** - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
|
||||||
- **BlkioWeight** - Block IO weight (relative weight) accepts a weight value between 10 and 1000.
|
- **BlkioWeight** - Block IO weight (relative weight) accepts a weight value between 10 and 1000.
|
||||||
- **OomKillDisable** - Boolean value, whether to disable OOM Killer for the container or not.
|
- **OomKillDisable** - Boolean value, whether to disable OOM Killer for the container or not.
|
||||||
- **AttachStdin** - Boolean value, attaches to stdin.
|
- **AttachStdin** - Boolean value, attaches to `stdin`.
|
||||||
- **AttachStdout** - Boolean value, attaches to stdout.
|
- **AttachStdout** - Boolean value, attaches to `stdout`.
|
||||||
- **AttachStderr** - Boolean value, attaches to stderr.
|
- **AttachStderr** - Boolean value, attaches to `stderr`.
|
||||||
- **Tty** - Boolean value, Attach standard streams to a tty, including stdin if it is not closed.
|
- **Tty** - Boolean value, Attach standard streams to a `tty`, including `stdin` if it is not closed.
|
||||||
- **OpenStdin** - Boolean value, opens stdin,
|
- **OpenStdin** - Boolean value, opens stdin,
|
||||||
- **StdinOnce** - Boolean value, close stdin after the 1 attached client disconnects.
|
- **StdinOnce** - Boolean value, close `stdin` after the 1 attached client disconnects.
|
||||||
- **Env** - A list of environment variables in the form of `VAR=value`
|
- **Env** - A list of environment variables in the form of `VAR=value`
|
||||||
- **Labels** - Adds a map of labels that to a container. To specify a map: `{"key":"value"[,"key2":"value2"]}`
|
- **Labels** - Adds a map of labels to a container. To specify a map: `{"key":"value"[,"key2":"value2"]}`
|
||||||
- **Cmd** - Command to run specified as a string or an array of strings.
|
- **Cmd** - Command to run specified as a string or an array of strings.
|
||||||
- **Entrypoint** - Set the entrypoint for the container a a string or an array
|
- **Entrypoint** - Set the entry point for the container as a string or an array
|
||||||
of strings
|
of strings.
|
||||||
- **Image** - String value containing the image name to use for the container
|
- **Image** - A string specifying the image name to use for the container.
|
||||||
- **Volumes** – An object mapping mount point paths (strings) inside the
|
- **Volumes** – An object mapping mount point paths (strings) inside the
|
||||||
container to empty objects.
|
container to empty objects.
|
||||||
- **WorkingDir** - A string value containing the working dir for commands to
|
- **WorkingDir** - A string specifying the working directory for commands to
|
||||||
run in.
|
run in.
|
||||||
- **NetworkDisabled** - Boolean value, when true disables networking for the
|
- **NetworkDisabled** - Boolean value, when true disables networking for the
|
||||||
container
|
container
|
||||||
- **ExposedPorts** - An object mapping ports to an empty object in the form of:
|
- **ExposedPorts** - An object mapping ports to an empty object in the form of:
|
||||||
`"ExposedPorts": { "<port>/<tcp|udp>: {}" }`
|
`"ExposedPorts": { "<port>/<tcp|udp>: {}" }`
|
||||||
- **HostConfig**
|
- **HostConfig**
|
||||||
- **Binds** – A list of volume bindings for this container. Each volume
|
- **Binds** – A list of volume bindings for this container. Each volume binding is a string in one of these forms:
|
||||||
binding is a string of the form `container_path` (to create a new
|
+ `container_path` to create a new volume for the container
|
||||||
volume for the container), `host_path:container_path` (to bind-mount
|
+ `host_path:container_path` to bind-mount a host path into the container
|
||||||
a host path into the container), or `host_path:container_path:ro`
|
+ `host_path:container_path:ro` to make the bind-mount read-only inside the container.
|
||||||
(to make the bind-mount read-only inside the container).
|
|
||||||
- **Links** - A list of links for the container. Each link entry should be
|
- **Links** - A list of links for the container. Each link entry should be
|
||||||
in the form of `container_name:alias`.
|
in the form of `container_name:alias`.
|
||||||
- **LxcConf** - LXC specific configurations. These configurations will only
|
- **LxcConf** - LXC specific configurations. These configurations only
|
||||||
work when using the `lxc` execution driver.
|
work when using the `lxc` execution driver.
|
||||||
- **PortBindings** - A map of exposed container ports and the host port they
|
- **PortBindings** - A map of exposed container ports and the host port they
|
||||||
should map to. It should be specified in the form
|
should map to. A JSON object in the form
|
||||||
`{ <port>/<protocol>: [{ "HostPort": "<port>" }] }`
|
`{ <port>/<protocol>: [{ "HostPort": "<port>" }] }`
|
||||||
Take note that `port` is specified as a string and not an integer value.
|
Take note that `port` is specified as a string and not an integer value.
|
||||||
- **PublishAllPorts** - Allocates a random host port for all of a container's
|
- **PublishAllPorts** - Allocates a random host port for all of a container's
|
||||||
@@ -242,9 +241,9 @@ Json Parameters:
|
|||||||
a boolean value.
|
a boolean value.
|
||||||
- **ReadonlyRootfs** - Mount the container's root filesystem as read only.
|
- **ReadonlyRootfs** - Mount the container's root filesystem as read only.
|
||||||
Specified as a boolean value.
|
Specified as a boolean value.
|
||||||
- **Dns** - A list of dns servers for the container to use.
|
- **Dns** - A list of DNS servers for the container to use.
|
||||||
- **DnsSearch** - A list of DNS search domains
|
- **DnsSearch** - A list of DNS search domains
|
||||||
- **ExtraHosts** - A list of hostnames/IP mappings to be added to the
|
- **ExtraHosts** - A list of hostnames/IP mappings to add to the
|
||||||
container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
|
container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
|
||||||
- **VolumesFrom** - A list of volumes to inherit from another container.
|
- **VolumesFrom** - A list of volumes to inherit from another container.
|
||||||
Specified in the form `<container name>[:<ro|rw>]`
|
Specified in the form `<container name>[:<ro|rw>]`
|
||||||
@@ -260,18 +259,18 @@ Json Parameters:
|
|||||||
is added before each restart to prevent flooding the server.
|
is added before each restart to prevent flooding the server.
|
||||||
- **NetworkMode** - Sets the networking mode for the container. Supported
|
- **NetworkMode** - Sets the networking mode for the container. Supported
|
||||||
values are: `bridge`, `host`, and `container:<name|id>`
|
values are: `bridge`, `host`, and `container:<name|id>`
|
||||||
- **Devices** - A list of devices to add to the container specified in the
|
- **Devices** - A list of devices to add to the container specified as a JSON object in the
|
||||||
form
|
form
|
||||||
`{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
|
`{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
|
||||||
- **Ulimits** - A list of ulimits to be set in the container, specified as
|
- **Ulimits** - A list of ulimits to set in the container, specified as
|
||||||
`{ "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }`, for example:
|
`{ "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }`, for example:
|
||||||
`Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
|
`Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
|
||||||
- **SecurityOpt**: A list of string values to customize labels for MLS
|
- **SecurityOpt**: A list of string values to customize labels for MLS
|
||||||
systems, such as SELinux.
|
systems, such as SELinux.
|
||||||
- **LogConfig** - Log configuration for the container, specified as
|
- **LogConfig** - Log configuration for the container, specified as a JSON object in the form
|
||||||
`{ "Type": "<driver_name>", "Config": {"key1": "val1"}}`.
|
`{ "Type": "<driver_name>", "Config": {"key1": "val1"}}`.
|
||||||
Available types: `json-file`, `syslog`, `journald`, `none`.
|
Available types: `json-file`, `syslog`, `journald`, `none`.
|
||||||
`json-file` logging driver.
|
`syslog` available options are: `address`.
|
||||||
- **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
|
- **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
|
||||||
|
|
||||||
Query Parameters:
|
Query Parameters:
|
||||||
@@ -472,7 +471,7 @@ Status Codes:
|
|||||||
|
|
||||||
`GET /containers/(id)/logs`
|
`GET /containers/(id)/logs`
|
||||||
|
|
||||||
Get stdout and stderr logs from the container ``id``
|
Get `stdout` and `stderr` logs from the container ``id``
|
||||||
|
|
||||||
> **Note**:
|
> **Note**:
|
||||||
> This endpoint works only for containers with `json-file` logging driver.
|
> This endpoint works only for containers with `json-file` logging driver.
|
||||||
@@ -492,14 +491,14 @@ Get stdout and stderr logs from the container ``id``
|
|||||||
|
|
||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **follow** – 1/True/true or 0/False/false, return stream. Default false
|
- **follow** – 1/True/true or 0/False/false, return stream. Default `false`.
|
||||||
- **stdout** – 1/True/true or 0/False/false, show stdout log. Default false
|
- **stdout** – 1/True/true or 0/False/false, show `stdout` log. Default `false`.
|
||||||
- **stderr** – 1/True/true or 0/False/false, show stderr log. Default false
|
- **stderr** – 1/True/true or 0/False/false, show `stderr` log. Default `false`.
|
||||||
- **since** – UNIX timestamp (integer) to filter logs. Specifying a timestamp
|
- **since** – UNIX timestamp (integer) to filter logs. Specifying a timestamp
|
||||||
will only output log-entries since that timestamp. Default: 0 (unfiltered)
|
will only output log-entries since that timestamp. Default: 0 (unfiltered)
|
||||||
- **timestamps** – 1/True/true or 0/False/false, print timestamps for
|
- **timestamps** – 1/True/true or 0/False/false, print timestamps for
|
||||||
every log line. Default false
|
every log line. Default `false`.
|
||||||
- **tail** – Output specified number of lines at the end of logs: `all` or `<number>`. Default all
|
- **tail** – Output specified number of lines at the end of logs: `all` or `<number>`. Default all.
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
@@ -659,7 +658,7 @@ This endpoint returns a live stream of a container's resource usage statistics.
|
|||||||
|
|
||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **stream** – 1/True/true or 0/False/false, pull stats once then disconnect. Default true
|
- **stream** – 1/True/true or 0/False/false, pull stats once then disconnect. Default `true`.
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
@@ -671,7 +670,7 @@ Status Codes:
|
|||||||
|
|
||||||
`POST /containers/(id)/resize?h=<height>&w=<width>`
|
`POST /containers/(id)/resize?h=<height>&w=<width>`
|
||||||
|
|
||||||
Resize the TTY for container with `id`. The container must be restarted for the resize to take effect.
|
Resize the TTY for container with `id`. You must restart the container for the resize to take effect.
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
@@ -733,17 +732,16 @@ Start the container `id`
|
|||||||
|
|
||||||
Json Parameters:
|
Json Parameters:
|
||||||
|
|
||||||
- **Binds** – A list of volume bindings for this container. Each volume
|
- **Binds** – A list of volume bindings for this container. Each volume binding is a string in one of these forms:
|
||||||
binding is a string of the form `container_path` (to create a new
|
+ `container_path` to create a new volume for the container
|
||||||
volume for the container), `host_path:container_path` (to bind-mount
|
+ `host_path:container_path` to bind-mount a host path into the container
|
||||||
a host path into the container), or `host_path:container_path:ro`
|
+ `host_path:container_path:ro` to make the bind-mount read-only inside the container.
|
||||||
(to make the bind-mount read-only inside the container).
|
|
||||||
- **Links** - A list of links for the container. Each link entry should be of
|
- **Links** - A list of links for the container. Each link entry should be of
|
||||||
of the form `container_name:alias`.
|
of the form `container_name:alias`.
|
||||||
- **LxcConf** - LXC specific configurations. These configurations will only
|
- **LxcConf** - LXC specific configurations. These configurations only
|
||||||
work when using the `lxc` execution driver.
|
work when using the `lxc` execution driver.
|
||||||
- **PortBindings** - A map of exposed container ports and the host port they
|
- **PortBindings** - A map of exposed container ports and the host port they
|
||||||
should map to. It should be specified in the form
|
should map to. A JSON object in the form
|
||||||
`{ <port>/<protocol>: [{ "HostPort": "<port>" }] }`
|
`{ <port>/<protocol>: [{ "HostPort": "<port>" }] }`
|
||||||
Take note that `port` is specified as a string and not an integer value.
|
Take note that `port` is specified as a string and not an integer value.
|
||||||
- **PublishAllPorts** - Allocates a random host port for all of a container's
|
- **PublishAllPorts** - Allocates a random host port for all of a container's
|
||||||
@@ -753,8 +751,8 @@ Json Parameters:
|
|||||||
- **ReadonlyRootfs** - Mount the container's root filesystem as read only.
|
- **ReadonlyRootfs** - Mount the container's root filesystem as read only.
|
||||||
Specified as a boolean value.
|
Specified as a boolean value.
|
||||||
- **Dns** - A list of dns servers for the container to use.
|
- **Dns** - A list of dns servers for the container to use.
|
||||||
- **DnsSearch** - A list of DNS search domains
|
- **DnsSearch** - A list of DNS search domains.
|
||||||
- **ExtraHosts** - A list of hostnames/IP mappings to be added to the
|
- **ExtraHosts** - A list of hostnames/IP mappings to add to the
|
||||||
container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
|
container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`.
|
||||||
- **VolumesFrom** - A list of volumes to inherit from another container.
|
- **VolumesFrom** - A list of volumes to inherit from another container.
|
||||||
Specified in the form `<container name>[:<ro|rw>]`
|
Specified in the form `<container name>[:<ro|rw>]`
|
||||||
@@ -770,10 +768,10 @@ Json Parameters:
|
|||||||
is added before each restart to prevent flooding the server.
|
is added before each restart to prevent flooding the server.
|
||||||
- **NetworkMode** - Sets the networking mode for the container. Supported
|
- **NetworkMode** - Sets the networking mode for the container. Supported
|
||||||
values are: `bridge`, `host`, and `container:<name|id>`
|
values are: `bridge`, `host`, and `container:<name|id>`
|
||||||
- **Devices** - A list of devices to add to the container specified in the
|
- **Devices** - A list of devices to add to the container specified as a JSON object in the
|
||||||
form
|
form
|
||||||
`{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
|
`{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}`
|
||||||
- **Ulimits** - A list of ulimits to be set in the container, specified as
|
- **Ulimits** - A list of ulimits to set in the container, specified as
|
||||||
`{ "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }`, for example:
|
`{ "Name": <name>, "Soft": <soft limit>, "Hard": <hard limit> }`, for example:
|
||||||
`Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
|
`Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
|
||||||
- **SecurityOpt**: A list of string values to customize labels for MLS
|
- **SecurityOpt**: A list of string values to customize labels for MLS
|
||||||
@@ -782,7 +780,7 @@ Json Parameters:
|
|||||||
`{ "Type": "<driver_name>", "Config": {"key1": "val1"}}`.
|
`{ "Type": "<driver_name>", "Config": {"key1": "val1"}}`.
|
||||||
Available types: `json-file`, `syslog`, `journald`, `none`.
|
Available types: `json-file`, `syslog`, `journald`, `none`.
|
||||||
`json-file` logging driver.
|
`json-file` logging driver.
|
||||||
- **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
|
- **CgroupParent** - Path to `cgroups` under which the container's `cgroup` is created. If the path is not absolute, the path is considered to be relative to the `cgroups` path of the init process. Cgroups are created if they do not already exist.
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
@@ -856,8 +854,8 @@ Kill the container `id`
|
|||||||
|
|
||||||
Query Parameters
|
Query Parameters
|
||||||
|
|
||||||
- **signal** - Signal to send to the container: integer or string like "SIGINT".
|
- **signal** - Signal to send to the container: integer or string like `SIGINT`.
|
||||||
When not set, SIGKILL is assumed and the call will waits for the container to exit.
|
When not set, `SIGKILL` is assumed and the call waits for the container to exit.
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
@@ -951,15 +949,15 @@ Attach to the container `id`
|
|||||||
|
|
||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **logs** – 1/True/true or 0/False/false, return logs. Default false
|
- **logs** – 1/True/true or 0/False/false, return logs. Default `false`.
|
||||||
- **stream** – 1/True/true or 0/False/false, return stream.
|
- **stream** – 1/True/true or 0/False/false, return stream.
|
||||||
Default false
|
Default `false`.
|
||||||
- **stdin** – 1/True/true or 0/False/false, if stream=true, attach
|
- **stdin** – 1/True/true or 0/False/false, if `stream=true`, attach
|
||||||
to stdin. Default false
|
to `stdin`. Default `false`.
|
||||||
- **stdout** – 1/True/true or 0/False/false, if logs=true, return
|
- **stdout** – 1/True/true or 0/False/false, if `logs=true`, return
|
||||||
stdout log, if stream=true, attach to stdout. Default false
|
`stdout` log, if `stream=true`, attach to `stdout`. Default `false`.
|
||||||
- **stderr** – 1/True/true or 0/False/false, if logs=true, return
|
- **stderr** – 1/True/true or 0/False/false, if `logs=true`, return
|
||||||
stderr log, if stream=true, attach to stderr. Default false
|
`stderr` log, if `stream=true`, attach to `stderr`. Default `false`.
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
@@ -974,30 +972,30 @@ Status Codes:
|
|||||||
When using the TTY setting is enabled in
|
When using the TTY setting is enabled in
|
||||||
[`POST /containers/create`
|
[`POST /containers/create`
|
||||||
](/reference/api/docker_remote_api_v1.9/#create-a-container "POST /containers/create"),
|
](/reference/api/docker_remote_api_v1.9/#create-a-container "POST /containers/create"),
|
||||||
the stream is the raw data from the process PTY and client's stdin.
|
the stream is the raw data from the process PTY and client's `stdin`.
|
||||||
When the TTY is disabled, then the stream is multiplexed to separate
|
When the TTY is disabled, then the stream is multiplexed to separate
|
||||||
stdout and stderr.
|
`stdout` and `stderr`.
|
||||||
|
|
||||||
The format is a **Header** and a **Payload** (frame).
|
The format is a **Header** and a **Payload** (frame).
|
||||||
|
|
||||||
**HEADER**
|
**HEADER**
|
||||||
|
|
||||||
The header will contain the information on which stream write the
|
The header contains the information which the stream writes (`stdout` or
|
||||||
stream (stdout or stderr). It also contain the size of the
|
`stderr`). It also contains the size of the associated frame encoded in the
|
||||||
associated frame encoded on the last 4 bytes (uint32).
|
last four bytes (`uint32`).
|
||||||
|
|
||||||
It is encoded on the first 8 bytes like this:
|
It is encoded on the first eight bytes like this:
|
||||||
|
|
||||||
header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
|
header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
|
||||||
|
|
||||||
`STREAM_TYPE` can be:
|
`STREAM_TYPE` can be:
|
||||||
|
|
||||||
- 0: stdin (will be written on stdout)
|
- 0: `stdin` (is written on `stdout`)
|
||||||
- 1: stdout
|
- 1: `stdout`
|
||||||
- 2: stderr
|
- 2: `stderr`
|
||||||
|
|
||||||
`SIZE1, SIZE2, SIZE3, SIZE4` are the 4 bytes of
|
`SIZE1, SIZE2, SIZE3, SIZE4` are the four bytes of
|
||||||
the uint32 size encoded as big endian.
|
the `uint32` size encoded as big endian.
|
||||||
|
|
||||||
**PAYLOAD**
|
**PAYLOAD**
|
||||||
|
|
||||||
@@ -1007,11 +1005,11 @@ Status Codes:
|
|||||||
|
|
||||||
The simplest way to implement the Attach protocol is the following:
|
The simplest way to implement the Attach protocol is the following:
|
||||||
|
|
||||||
1. Read 8 bytes
|
1. Read eight bytes.
|
||||||
2. chose stdout or stderr depending on the first byte
|
2. Choose `stdout` or `stderr` depending on the first byte.
|
||||||
3. Extract the frame size from the last 4 bytes
|
3. Extract the frame size from the last four bytes.
|
||||||
4. Read the extracted size and output it on the correct output
|
4. Read the extracted size and output it on the correct output.
|
||||||
5. Goto 1
|
5. Goto 1.
|
||||||
|
|
||||||
### Attach to a container (websocket)
|
### Attach to a container (websocket)
|
||||||
|
|
||||||
@@ -1031,15 +1029,15 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
|
|||||||
|
|
||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **logs** – 1/True/true or 0/False/false, return logs. Default false
|
- **logs** – 1/True/true or 0/False/false, return logs. Default `false`.
|
||||||
- **stream** – 1/True/true or 0/False/false, return stream.
|
- **stream** – 1/True/true or 0/False/false, return stream.
|
||||||
Default false
|
Default `false`.
|
||||||
- **stdin** – 1/True/true or 0/False/false, if stream=true, attach
|
- **stdin** – 1/True/true or 0/False/false, if `stream=true`, attach
|
||||||
to stdin. Default false
|
to `stdin`. Default `false`.
|
||||||
- **stdout** – 1/True/true or 0/False/false, if logs=true, return
|
- **stdout** – 1/True/true or 0/False/false, if `logs=true`, return
|
||||||
stdout log, if stream=true, attach to stdout. Default false
|
`stdout` log, if `stream=true`, attach to `stdout`. Default `false`.
|
||||||
- **stderr** – 1/True/true or 0/False/false, if logs=true, return
|
- **stderr** – 1/True/true or 0/False/false, if `logs=true`, return
|
||||||
stderr log, if stream=true, attach to stderr. Default false
|
`stderr` log, if `stream=true`, attach to `stderr`. Default `false`.
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
@@ -1088,9 +1086,9 @@ Remove the container `id` from the filesystem
|
|||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **v** – 1/True/true or 0/False/false, Remove the volumes
|
- **v** – 1/True/true or 0/False/false, Remove the volumes
|
||||||
associated to the container. Default false
|
associated to the container. Default `false`.
|
||||||
- **force** - 1/True/true or 0/False/false, Kill then remove the container.
|
- **force** - 1/True/true or 0/False/false, Kill then remove the container.
|
||||||
Default false
|
Default `false`.
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
@@ -1210,9 +1208,9 @@ references on the command line.
|
|||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **all** – 1/True/true or 0/False/false, default false
|
- **all** – 1/True/true or 0/False/false, default false
|
||||||
- **filters** – a json encoded value of the filters (a map[string][]string) to process on the images list. Available filters:
|
- **filters** – a JSON encoded value of the filters (a map[string][]string) to process on the images list. Available filters:
|
||||||
- dangling=true
|
- `dangling=true`
|
||||||
- label=`key` or `key=value` of an image label
|
- `label=key` or `key=value` of an image label
|
||||||
|
|
||||||
### Build image from a Dockerfile
|
### Build image from a Dockerfile
|
||||||
|
|
||||||
@@ -1235,43 +1233,43 @@ Build an image from a Dockerfile
|
|||||||
{"stream": "..."}
|
{"stream": "..."}
|
||||||
{"error": "Error...", "errorDetail": {"code": 123, "message": "Error..."}}
|
{"error": "Error...", "errorDetail": {"code": 123, "message": "Error..."}}
|
||||||
|
|
||||||
The input stream must be a tar archive compressed with one of the
|
The input stream must be a `tar` archive compressed with one of the
|
||||||
following algorithms: identity (no compression), gzip, bzip2, xz.
|
following algorithms: `identity` (no compression), `gzip`, `bzip2`, `xz`.
|
||||||
|
|
||||||
The archive must include a build instructions file, typically called
|
The archive must include a build instructions file, typically called
|
||||||
`Dockerfile` at the root of the archive. The `dockerfile` parameter may be
|
`Dockerfile` at the archive's root. The `dockerfile` parameter may be
|
||||||
used to specify a different build instructions file by having its value be
|
used to specify a different build instructions file. To do this, its value must be
|
||||||
the path to the alternate build instructions file to use.
|
the path to the alternate build instructions file to use.
|
||||||
|
|
||||||
The archive may include any number of other files,
|
The archive may include any number of other files,
|
||||||
which will be accessible in the build context (See the [*ADD build
|
which are accessible in the build context (See the [*ADD build
|
||||||
command*](/reference/builder/#dockerbuilder)).
|
command*](/reference/builder/#dockerbuilder)).
|
||||||
|
|
||||||
The build will also be canceled if the client drops the connection by quitting
|
The build is canceled if the client drops the connection by quitting
|
||||||
or being killed.
|
or being killed.
|
||||||
|
|
||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **dockerfile** - path within the build context to the Dockerfile. This is
|
- **dockerfile** - Path within the build context to the Dockerfile. This is
|
||||||
ignored if `remote` is specified and points to an individual filename.
|
ignored if `remote` is specified and points to an individual filename.
|
||||||
- **t** – repository name (and optionally a tag) to be applied to
|
- **t** – A repository name (and optionally a tag) to apply to
|
||||||
the resulting image in case of success
|
the resulting image in case of success.
|
||||||
- **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the
|
- **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the
|
||||||
URI specifies a filename, the file's contents are placed into a file
|
URI specifies a filename, the file's contents are placed into a file
|
||||||
called `Dockerfile`.
|
called `Dockerfile`.
|
||||||
- **q** – suppress verbose build output
|
- **q** – Suppress verbose build output.
|
||||||
- **nocache** – do not use the cache when building the image
|
- **nocache** – Do not use the cache when building the image.
|
||||||
- **pull** - attempt to pull the image even if an older image exists locally
|
- **pull** - Attempt to pull the image even if an older image exists locally.
|
||||||
- **rm** - remove intermediate containers after a successful build (default behavior)
|
- **rm** - Remove intermediate containers after a successful build (default behavior).
|
||||||
- **forcerm** - always remove intermediate containers (includes rm)
|
- **forcerm** - Always remove intermediate containers (includes `rm`).
|
||||||
- **memory** - set memory limit for build
|
- **memory** - Set memory limit for build.
|
||||||
- **memswap** - Total memory (memory + swap), `-1` to disable swap
|
- **memswap** - Total memory (memory + swap), `-1` to disable swap.
|
||||||
- **cpushares** - CPU shares (relative weight)
|
- **cpushares** - CPU shares (relative weight).
|
||||||
- **cpusetcpus** - CPUs in which to allow execution, e.g., `0-3`, `0,1`
|
- **cpusetcpus** - CPUs in which to allow execution (e.g., `0-3`, `0,1`).
|
||||||
|
|
||||||
Request Headers:
|
Request Headers:
|
||||||
|
|
||||||
- **Content-type** – should be set to `"application/tar"`.
|
- **Content-type** – Set to `"application/tar"`.
|
||||||
- **X-Registry-Config** – base64-encoded ConfigFile object
|
- **X-Registry-Config** – base64-encoded ConfigFile object
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
@@ -1283,7 +1281,7 @@ Status Codes:
|
|||||||
|
|
||||||
`POST /images/create`
|
`POST /images/create`
|
||||||
|
|
||||||
Create an image, either by pulling it from the registry or by importing it
|
Create an image either by pulling it from the registry or by importing it
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
@@ -1305,12 +1303,12 @@ Create an image, either by pulling it from the registry or by importing it
|
|||||||
|
|
||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **fromImage** – name of the image to pull
|
- **fromImage** – Name of the image to pull.
|
||||||
- **fromSrc** – source to import. The value may be a URL from which the image
|
- **fromSrc** – Source to import. The value may be a URL from which the image
|
||||||
can be retrieved or `-` to read the image from the request body.
|
can be retrieved or `-` to read the image from the request body.
|
||||||
- **repo** – repository
|
- **repo** – Repository name.
|
||||||
- **tag** – tag
|
- **tag** – Tag.
|
||||||
- **registry** – the registry to pull from
|
- **registry** – The registry to pull from.
|
||||||
|
|
||||||
Request Headers:
|
Request Headers:
|
||||||
|
|
||||||
@@ -1430,9 +1428,9 @@ Push the image `name` on the registry
|
|||||||
{"error": "Invalid..."}
|
{"error": "Invalid..."}
|
||||||
...
|
...
|
||||||
|
|
||||||
If you wish to push an image on to a private registry, that image must already have been tagged
|
If you wish to push an image on to a private registry, that image must already have a tag
|
||||||
into a repository which references that registry host name and port. This repository name should
|
into a repository which references that registry `hostname` and `port`. This repository name should
|
||||||
then be used in the URL. This mirrors the flow of the CLI.
|
then be used in the URL. This duplicates the command line's flow.
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
@@ -1441,11 +1439,11 @@ Push the image `name` on the registry
|
|||||||
|
|
||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **tag** – the tag to associate with the image on the registry, optional
|
- **tag** – The tag to associate with the image on the registry. This is optional.
|
||||||
|
|
||||||
Request Headers:
|
Request Headers:
|
||||||
|
|
||||||
- **X-Registry-Auth** – include a base64-encoded AuthConfig
|
- **X-Registry-Auth** – Include a base64-encoded AuthConfig.
|
||||||
object.
|
object.
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
@@ -1788,11 +1786,11 @@ Status Codes:
|
|||||||
Get container events from docker, either in real time via streaming, or via
|
Get container events from docker, either in real time via streaming, or via
|
||||||
polling (using since).
|
polling (using since).
|
||||||
|
|
||||||
Docker containers will report the following events:
|
Docker containers report the following events:
|
||||||
|
|
||||||
create, destroy, die, exec_create, exec_start, export, kill, oom, pause, restart, start, stop, unpause
|
create, destroy, die, exec_create, exec_start, export, kill, oom, pause, restart, start, stop, unpause
|
||||||
|
|
||||||
and Docker images will report:
|
and Docker images report:
|
||||||
|
|
||||||
untag, delete
|
untag, delete
|
||||||
|
|
||||||
@@ -1812,12 +1810,12 @@ and Docker images will report:
|
|||||||
|
|
||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **since** – timestamp used for polling
|
- **since** – Timestamp used for polling
|
||||||
- **until** – timestamp used for polling
|
- **until** – Timestamp used for polling
|
||||||
- **filters** – a json encoded value of the filters (a map[string][]string) to process on the event list. Available filters:
|
- **filters** – A json encoded value of the filters (a map[string][]string) to process on the event list. Available filters:
|
||||||
- event=<string> -- event to filter
|
- `event=<string>`; -- event to filter
|
||||||
- image=<string> -- image to filter
|
- `image=<string>`; -- image to filter
|
||||||
- container=<string> -- container to filter
|
- `container=<string>`; -- container to filter
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
@@ -1861,7 +1859,7 @@ Status Codes:
|
|||||||
Get a tarball containing all images and metadata for one or more repositories.
|
Get a tarball containing all images and metadata for one or more repositories.
|
||||||
|
|
||||||
For each value of the `names` parameter: if it is a specific name and tag (e.g.
|
For each value of the `names` parameter: if it is a specific name and tag (e.g.
|
||||||
ubuntu:latest), then only that image (and its parents) are returned; if it is
|
`ubuntu:latest`), then only that image (and its parents) are returned; if it is
|
||||||
an image ID, similarly only that image (and its parents) are returned and there
|
an image ID, similarly only that image (and its parents) are returned and there
|
||||||
would be no names referenced in the 'repositories' file for this image ID.
|
would be no names referenced in the 'repositories' file for this image ID.
|
||||||
|
|
||||||
@@ -1887,7 +1885,7 @@ Status Codes:
|
|||||||
|
|
||||||
`POST /images/load`
|
`POST /images/load`
|
||||||
|
|
||||||
Load a set of images and tags into the docker repository.
|
Load a set of images and tags into a Docker repository.
|
||||||
See the [image tarball format](#image-tarball-format) for more details.
|
See the [image tarball format](#image-tarball-format) for more details.
|
||||||
|
|
||||||
**Example request**
|
**Example request**
|
||||||
@@ -1908,16 +1906,16 @@ Status Codes:
|
|||||||
### Image tarball format
|
### Image tarball format
|
||||||
|
|
||||||
An image tarball contains one directory per image layer (named using its long ID),
|
An image tarball contains one directory per image layer (named using its long ID),
|
||||||
each containing three files:
|
each containing these files:
|
||||||
|
|
||||||
1. `VERSION`: currently `1.0` - the file format version
|
- `VERSION`: currently `1.0` - the file format version
|
||||||
2. `json`: detailed layer information, similar to `docker inspect layer_id`
|
- `json`: detailed layer information, similar to `docker inspect layer_id`
|
||||||
3. `layer.tar`: A tarfile containing the filesystem changes in this layer
|
- `layer.tar`: A tarfile containing the filesystem changes in this layer
|
||||||
|
|
||||||
The `layer.tar` file will contain `aufs` style `.wh..wh.aufs` files and directories
|
The `layer.tar` file contains `aufs` style `.wh..wh.aufs` files and directories
|
||||||
for storing attribute changes and deletions.
|
for storing attribute changes and deletions.
|
||||||
|
|
||||||
If the tarball defines a repository, there will also be a `repositories` file at
|
If the tarball defines a repository, the tarball should also include a `repositories` file at
|
||||||
the root that contains a list of repository and tag names mapped to layer IDs.
|
the root that contains a list of repository and tag names mapped to layer IDs.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -1959,10 +1957,10 @@ Sets up an exec instance in a running container `id`
|
|||||||
|
|
||||||
Json Parameters:
|
Json Parameters:
|
||||||
|
|
||||||
- **AttachStdin** - Boolean value, attaches to stdin of the exec command.
|
- **AttachStdin** - Boolean value, attaches to `stdin` of the `exec` command.
|
||||||
- **AttachStdout** - Boolean value, attaches to stdout of the exec command.
|
- **AttachStdout** - Boolean value, attaches to `stdout` of the `exec` command.
|
||||||
- **AttachStderr** - Boolean value, attaches to stderr of the exec command.
|
- **AttachStderr** - Boolean value, attaches to `stderr` of the `exec` command.
|
||||||
- **Tty** - Boolean value to allocate a pseudo-TTY
|
- **Tty** - Boolean value to allocate a pseudo-TTY.
|
||||||
- **Cmd** - Command to run specified as a string or an array of strings.
|
- **Cmd** - Command to run specified as a string or an array of strings.
|
||||||
|
|
||||||
|
|
||||||
@@ -1975,7 +1973,7 @@ Status Codes:
|
|||||||
|
|
||||||
`POST /exec/(id)/start`
|
`POST /exec/(id)/start`
|
||||||
|
|
||||||
Starts a previously set up exec instance `id`. If `detach` is true, this API
|
Starts a previously set up `exec` instance `id`. If `detach` is true, this API
|
||||||
returns after starting the `exec` command. Otherwise, this API sets up an
|
returns after starting the `exec` command. Otherwise, this API sets up an
|
||||||
interactive session with the `exec` command.
|
interactive session with the `exec` command.
|
||||||
|
|
||||||
@@ -1998,8 +1996,8 @@ interactive session with the `exec` command.
|
|||||||
|
|
||||||
Json Parameters:
|
Json Parameters:
|
||||||
|
|
||||||
- **Detach** - Detach from the exec command
|
- **Detach** - Detach from the `exec` command.
|
||||||
- **Tty** - Boolean value to allocate a pseudo-TTY
|
- **Tty** - Boolean value to allocate a pseudo-TTY.
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
|
|
||||||
@@ -2013,8 +2011,8 @@ Status Codes:
|
|||||||
|
|
||||||
`POST /exec/(id)/resize`
|
`POST /exec/(id)/resize`
|
||||||
|
|
||||||
Resizes the tty session used by the exec command `id`.
|
Resizes the `tty` session used by the `exec` command `id`.
|
||||||
This API is valid only if `tty` was specified as part of creating and starting the exec command.
|
This API is valid only if `tty` was specified as part of creating and starting the `exec` command.
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
@@ -2028,7 +2026,7 @@ This API is valid only if `tty` was specified as part of creating and starting t
|
|||||||
|
|
||||||
Query Parameters:
|
Query Parameters:
|
||||||
|
|
||||||
- **h** – height of tty session
|
- **h** – height of `tty` session
|
||||||
- **w** – width
|
- **w** – width
|
||||||
|
|
||||||
Status Codes:
|
Status Codes:
|
||||||
@@ -2040,7 +2038,7 @@ Status Codes:
|
|||||||
|
|
||||||
`GET /exec/(id)/json`
|
`GET /exec/(id)/json`
|
||||||
|
|
||||||
Return low-level information about the exec command `id`.
|
Return low-level information about the `exec` command `id`.
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
@@ -2150,22 +2148,21 @@ As an example, the `docker run` command line makes the following API calls:
|
|||||||
- Create the container
|
- Create the container
|
||||||
|
|
||||||
- If the status code is 404, it means the image doesn't exist:
|
- If the status code is 404, it means the image doesn't exist:
|
||||||
- Try to pull it
|
- Try to pull it.
|
||||||
- Then retry to create the container
|
- Then, retry to create the container.
|
||||||
|
|
||||||
- Start the container
|
- Start the container.
|
||||||
|
|
||||||
- If you are not in detached mode:
|
- If you are not in detached mode:
|
||||||
- Attach to the container, using logs=1 (to have stdout and
|
- Attach to the container, using `logs=1` (to have `stdout` and
|
||||||
stderr from the container's start) and stream=1
|
`stderr` from the container's start) and `stream=1`
|
||||||
|
|
||||||
- If in detached mode or only stdin is attached:
|
- If in detached mode or only `stdin` is attached, display the container's id.
|
||||||
- Display the container's id
|
|
||||||
|
|
||||||
## 3.2 Hijacking
|
## 3.2 Hijacking
|
||||||
|
|
||||||
In this version of the API, /attach, uses hijacking to transport stdin,
|
In this version of the API, `/attach`, uses hijacking to transport `stdin`,
|
||||||
stdout and stderr on the same socket.
|
`stdout`, and `stderr` on the same socket.
|
||||||
|
|
||||||
To hint potential proxies about connection hijacking, Docker client sends
|
To hint potential proxies about connection hijacking, Docker client sends
|
||||||
connection upgrade headers similarly to websocket.
|
connection upgrade headers similarly to websocket.
|
||||||
@@ -2173,15 +2170,14 @@ connection upgrade headers similarly to websocket.
|
|||||||
Upgrade: tcp
|
Upgrade: tcp
|
||||||
Connection: Upgrade
|
Connection: Upgrade
|
||||||
|
|
||||||
When Docker daemon detects the `Upgrade` header, it will switch its status code
|
When Docker daemon detects the `Upgrade` header, it switches its status code
|
||||||
from **200 OK** to **101 UPGRADED** and resend the same headers.
|
from **200 OK** to **101 UPGRADED** and resends the same headers.
|
||||||
|
|
||||||
This might change in the future.
|
|
||||||
|
|
||||||
## 3.3 CORS Requests
|
## 3.3 CORS Requests
|
||||||
|
|
||||||
To set cross origin requests to the remote api please give values to
|
To set cross origin requests to the remote api please give values to
|
||||||
"--api-cors-header" when running docker in daemon mode. Set * will allow all,
|
`--api-cors-header` when running Docker in daemon mode. Set * (asterisk) allows all,
|
||||||
default or blank means CORS disabled
|
default or blank means CORS disabled
|
||||||
|
|
||||||
$ docker -d -H="192.168.1.9:2375" --api-cors-header="http://foo.bar"
|
$ docker -d -H="192.168.1.9:2375" --api-cors-header="http://foo.bar"
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ expect an integer, and they can only be specified once.
|
|||||||
-l, --log-level="info" Set the logging level
|
-l, --log-level="info" Set the logging level
|
||||||
--label=[] Set key=value labels to the daemon
|
--label=[] Set key=value labels to the daemon
|
||||||
--log-driver="json-file" Default driver for container logs
|
--log-driver="json-file" Default driver for container logs
|
||||||
|
--log-opt=[] Log driver specific options
|
||||||
--mtu=0 Set the containers network MTU
|
--mtu=0 Set the containers network MTU
|
||||||
-p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file
|
-p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file
|
||||||
--registry-mirror=[] Preferred Docker registry mirror
|
--registry-mirror=[] Preferred Docker registry mirror
|
||||||
@@ -282,6 +283,18 @@ set `zfs.fsname` option as described in [Storage driver options](#storage-driver
|
|||||||
The `overlay` is a very fast union filesystem. It is now merged in the main
|
The `overlay` is a very fast union filesystem. It is now merged in the main
|
||||||
Linux kernel as of [3.18.0](https://lkml.org/lkml/2014/10/26/137).
|
Linux kernel as of [3.18.0](https://lkml.org/lkml/2014/10/26/137).
|
||||||
Call `docker -d -s overlay` to use it.
|
Call `docker -d -s overlay` to use it.
|
||||||
|
> **Note:**
|
||||||
|
> As promising as `overlay` is, the feature is still quite young and should not
|
||||||
|
> be used in production. Most notably, using `overlay` can cause excessive
|
||||||
|
> inode consumption (especially as the number of images grows), as well as
|
||||||
|
> being incompatible with the use of RPMs.
|
||||||
|
|
||||||
|
> **Note:**
|
||||||
|
> As promising as `overlay` is, the feature is still quite young and should not
|
||||||
|
> be used in production. Most notably, using `overlay` can cause excessive
|
||||||
|
> inode consumption (especially as the number of images grows), as well as
|
||||||
|
> being incompatible with the use of RPMs.
|
||||||
|
|
||||||
> **Note:**
|
> **Note:**
|
||||||
> It is currently unsupported on `btrfs` or any Copy on Write filesystem
|
> It is currently unsupported on `btrfs` or any Copy on Write filesystem
|
||||||
> and should only be used over `ext4` partitions.
|
> and should only be used over `ext4` partitions.
|
||||||
@@ -984,6 +997,7 @@ Creates a new container.
|
|||||||
--label-file=[] Read in a line delimited file of labels
|
--label-file=[] Read in a line delimited file of labels
|
||||||
--link=[] Add link to another container
|
--link=[] Add link to another container
|
||||||
--log-driver="" Logging driver for container
|
--log-driver="" Logging driver for container
|
||||||
|
--log-opt=[] Log driver specific options
|
||||||
--lxc-conf=[] Add custom lxc options
|
--lxc-conf=[] Add custom lxc options
|
||||||
-m, --memory="" Memory limit
|
-m, --memory="" Memory limit
|
||||||
--mac-address="" Container MAC address (e.g. 92:d0:c6:0a:29:33)
|
--mac-address="" Container MAC address (e.g. 92:d0:c6:0a:29:33)
|
||||||
@@ -1948,6 +1962,7 @@ To remove an image using its digest:
|
|||||||
--ipc="" IPC namespace to use
|
--ipc="" IPC namespace to use
|
||||||
--link=[] Add link to another container
|
--link=[] Add link to another container
|
||||||
--log-driver="" Logging driver for container
|
--log-driver="" Logging driver for container
|
||||||
|
--log-opt=[] Log driver specific options
|
||||||
--lxc-conf=[] Add custom lxc options
|
--lxc-conf=[] Add custom lxc options
|
||||||
-m, --memory="" Memory limit
|
-m, --memory="" Memory limit
|
||||||
-l, --label=[] Set metadata on the container (e.g., --label=com.example.key=value)
|
-l, --label=[] Set metadata on the container (e.g., --label=com.example.key=value)
|
||||||
|
|||||||
@@ -873,18 +873,31 @@ this driver.
|
|||||||
Default logging driver for Docker. Writes JSON messages to file. `docker logs`
|
Default logging driver for Docker. Writes JSON messages to file. `docker logs`
|
||||||
command is available only for this logging driver
|
command is available only for this logging driver
|
||||||
|
|
||||||
|
The following logging options are supported for this logging driver: [none]
|
||||||
|
|
||||||
#### Logging driver: syslog
|
#### Logging driver: syslog
|
||||||
|
|
||||||
Syslog logging driver for Docker. Writes log messages to syslog. `docker logs`
|
Syslog logging driver for Docker. Writes log messages to syslog. `docker logs`
|
||||||
command is not available for this logging driver
|
command is not available for this logging driver
|
||||||
|
|
||||||
|
The following logging options are supported for this logging driver:
|
||||||
|
|
||||||
|
--log-opt address=[tcp|udp]://host:port
|
||||||
|
--log-opt address=unix://path
|
||||||
|
|
||||||
|
`address` specifies the remote syslog server address where the driver connects to.
|
||||||
|
If not specified it defaults to the local unix socket of the running system.
|
||||||
|
If transport is either `tcp` or `udp` and `port` is not specified it defaults to `514`
|
||||||
|
The following example shows how to have the `syslog` driver connect to a `syslog`
|
||||||
|
remote server at `192.168.0.42` on port `123`
|
||||||
|
|
||||||
|
$ docker run --log-driver=syslog --log-opt address=tcp://192.168.0.42:123
|
||||||
|
|
||||||
#### Logging driver: journald
|
#### Logging driver: journald
|
||||||
|
|
||||||
Journald logging driver for Docker. Writes log messages to journald; the container id will be stored in the journal's `CONTAINER_ID` field. `docker logs` command is not available for this logging driver. For detailed information on working with this logging driver, see [the journald logging driver](reference/logging/journald) reference documentation.
|
Journald logging driver for Docker. Writes log messages to journald; the container id will be stored in the journal's `CONTAINER_ID` field. `docker logs` command is not available for this logging driver. For detailed information on working with this logging driver, see [the journald logging driver](reference/logging/journald) reference documentation.
|
||||||
|
|
||||||
#### Log Opts :
|
The following logging options are supported for this logging driver: [none]
|
||||||
|
|
||||||
Logging options for configuring a log driver. The following log options are supported: [none]
|
|
||||||
|
|
||||||
## Overriding Dockerfile image defaults
|
## Overriding Dockerfile image defaults
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ notation. Use the following guidelines to name your keys:
|
|||||||
reverse DNS notation of a domain controlled by the author. For
|
reverse DNS notation of a domain controlled by the author. For
|
||||||
example, `com.example.some-label`.
|
example, `com.example.some-label`.
|
||||||
|
|
||||||
- The `com.docker.*`, `io.docker.*` and `com.dockerproject.*` namespaces are
|
- The `com.docker.*`, `io.docker.*` and `org.dockerproject.*` namespaces are
|
||||||
reserved for Docker's internal use.
|
reserved for Docker's internal use.
|
||||||
|
|
||||||
- Keys should only consist of lower-cased alphanumeric characters,
|
- Keys should only consist of lower-cased alphanumeric characters,
|
||||||
|
|||||||
+119
-47
@@ -8,92 +8,164 @@ import (
|
|||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/trust"
|
"github.com/docker/docker/trust"
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadManifest loads a manifest from a byte array and verifies its content.
|
// loadManifest loads a manifest from a byte array and verifies its content,
|
||||||
// The signature must be verified or an error is returned. If the manifest
|
// returning the local digest, the manifest itself, whether or not it was
|
||||||
// contains no signatures by a trusted key for the name in the manifest, the
|
// verified. If ref is a digest, rather than a tag, this will be treated as
|
||||||
// image is not considered verified. The parsed manifest object and a boolean
|
// the local digest. An error will be returned if the signature verification
|
||||||
// for whether the manifest is verified is returned.
|
// fails, local digest verification fails and, if provided, the remote digest
|
||||||
func (s *TagStore) loadManifest(manifestBytes []byte, dgst, ref string) (*registry.ManifestData, bool, error) {
|
// verification fails. The boolean return will only be false without error on
|
||||||
sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures")
|
// the failure of signatures trust check.
|
||||||
|
func (s *TagStore) loadManifest(manifestBytes []byte, ref string, remoteDigest digest.Digest) (digest.Digest, *registry.ManifestData, bool, error) {
|
||||||
|
payload, keys, err := unpackSignedManifest(manifestBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("error parsing payload: %s", err)
|
return "", nil, false, fmt.Errorf("error unpacking manifest: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
keys, err := sig.Verify()
|
// TODO(stevvooe): It would be a lot better here to build up a stack of
|
||||||
|
// verifiers, then push the bytes one time for signatures and digests, but
|
||||||
|
// the manifests are typically small, so this optimization is not worth
|
||||||
|
// hacking this code without further refactoring.
|
||||||
|
|
||||||
|
var localDigest digest.Digest
|
||||||
|
|
||||||
|
// Verify the local digest, if present in ref. ParseDigest will validate
|
||||||
|
// that the ref is a digest and verify against that if present. Otherwize
|
||||||
|
// (on error), we simply compute the localDigest and proceed.
|
||||||
|
if dgst, err := digest.ParseDigest(ref); err == nil {
|
||||||
|
// verify the manifest against local ref
|
||||||
|
if err := verifyDigest(dgst, payload); err != nil {
|
||||||
|
return "", nil, false, fmt.Errorf("verifying local digest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
localDigest = dgst
|
||||||
|
} else {
|
||||||
|
// We don't have a local digest, since we are working from a tag.
|
||||||
|
// Compute the digest of the payload and return that.
|
||||||
|
logrus.Debugf("provided manifest reference %q is not a digest: %v", ref, err)
|
||||||
|
localDigest, err = digest.FromBytes(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("error verifying payload: %s", err)
|
// near impossible
|
||||||
}
|
logrus.Errorf("error calculating local digest during tag pull: %v", err)
|
||||||
|
return "", nil, false, err
|
||||||
payload, err := sig.Payload()
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, fmt.Errorf("error retrieving payload: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var manifestDigest digest.Digest
|
|
||||||
|
|
||||||
if dgst != "" {
|
|
||||||
manifestDigest, err = digest.ParseDigest(dgst)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, fmt.Errorf("invalid manifest digest from registry: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dgstVerifier, err := digest.NewDigestVerifier(manifestDigest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, fmt.Errorf("unable to verify manifest digest from registry: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dgstVerifier.Write(payload)
|
|
||||||
|
|
||||||
if !dgstVerifier.Verified() {
|
|
||||||
computedDigest, _ := digest.FromBytes(payload)
|
|
||||||
return nil, false, fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", manifestDigest, computedDigest)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.DigestReference(ref) && ref != manifestDigest.String() {
|
// verify against the remote digest, if available
|
||||||
return nil, false, fmt.Errorf("mismatching image manifest digest: got %q, expected %q", manifestDigest, ref)
|
if remoteDigest != "" {
|
||||||
|
if err := verifyDigest(remoteDigest, payload); err != nil {
|
||||||
|
return "", nil, false, fmt.Errorf("verifying remote digest: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var manifest registry.ManifestData
|
var manifest registry.ManifestData
|
||||||
if err := json.Unmarshal(payload, &manifest); err != nil {
|
if err := json.Unmarshal(payload, &manifest); err != nil {
|
||||||
return nil, false, fmt.Errorf("error unmarshalling manifest: %s", err)
|
return "", nil, false, fmt.Errorf("error unmarshalling manifest: %s", err)
|
||||||
}
|
}
|
||||||
if manifest.SchemaVersion != 1 {
|
|
||||||
return nil, false, fmt.Errorf("unsupported schema version: %d", manifest.SchemaVersion)
|
// validate the contents of the manifest
|
||||||
|
if err := validateManifest(&manifest); err != nil {
|
||||||
|
return "", nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var verified bool
|
var verified bool
|
||||||
for _, key := range keys {
|
verified, err = s.verifyTrustedKeys(manifest.Name, keys)
|
||||||
namespace := manifest.Name
|
if err != nil {
|
||||||
|
return "", nil, false, fmt.Errorf("error verifying trusted keys: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return localDigest, &manifest, verified, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackSignedManifest takes the raw, signed manifest bytes, unpacks the jws
|
||||||
|
// and returns the payload and public keys used to signed the manifest.
|
||||||
|
// Signatures are verified for authenticity but not against the trust store.
|
||||||
|
func unpackSignedManifest(p []byte) ([]byte, []libtrust.PublicKey, error) {
|
||||||
|
sig, err := libtrust.ParsePrettySignature(p, "signatures")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error parsing payload: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, err := sig.Verify()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error verifying payload: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := sig.Payload()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error retrieving payload: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload, keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyTrustedKeys checks the keys provided against the trust store,
|
||||||
|
// ensuring that the provided keys are trusted for the namespace. The keys
|
||||||
|
// provided from this method must come from the signatures provided as part of
|
||||||
|
// the manifest JWS package, obtained from unpackSignedManifest or libtrust.
|
||||||
|
func (s *TagStore) verifyTrustedKeys(namespace string, keys []libtrust.PublicKey) (verified bool, err error) {
|
||||||
if namespace[0] != '/' {
|
if namespace[0] != '/' {
|
||||||
namespace = "/" + namespace
|
namespace = "/" + namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
b, err := key.MarshalJSON()
|
b, err := key.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("error marshalling public key: %s", err)
|
return false, fmt.Errorf("error marshalling public key: %s", err)
|
||||||
}
|
}
|
||||||
// Check key has read/write permission (0x03)
|
// Check key has read/write permission (0x03)
|
||||||
v, err := s.trustService.CheckKey(namespace, b, 0x03)
|
v, err := s.trustService.CheckKey(namespace, b, 0x03)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vErr, ok := err.(trust.NotVerifiedError)
|
vErr, ok := err.(trust.NotVerifiedError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false, fmt.Errorf("error running key check: %s", err)
|
return false, fmt.Errorf("error running key check: %s", err)
|
||||||
}
|
}
|
||||||
logrus.Debugf("Key check result: %v", vErr)
|
logrus.Debugf("Key check result: %v", vErr)
|
||||||
}
|
}
|
||||||
verified = v
|
verified = v
|
||||||
|
}
|
||||||
|
|
||||||
if verified {
|
if verified {
|
||||||
logrus.Debug("Key check result: verified")
|
logrus.Debug("Key check result: verified")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return &manifest, verified, nil
|
|
||||||
|
// verifyDigest checks the contents of p against the provided digest. Note
|
||||||
|
// that for manifests, this is the signed payload and not the raw bytes with
|
||||||
|
// signatures.
|
||||||
|
func verifyDigest(dgst digest.Digest, p []byte) error {
|
||||||
|
if err := dgst.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("error validating digest %q: %v", dgst, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
verifier, err := digest.NewDigestVerifier(dgst)
|
||||||
|
if err != nil {
|
||||||
|
// There are not many ways this can go wrong: if it does, its
|
||||||
|
// fatal. Likley, the cause would be poor validation of the
|
||||||
|
// incoming reference.
|
||||||
|
return fmt.Errorf("error creating verifier for digest %q: %v", dgst, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := verifier.Write(p); err != nil {
|
||||||
|
return fmt.Errorf("error writing payload to digest verifier (verifier target %q): %v", dgst, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !verifier.Verified() {
|
||||||
|
return fmt.Errorf("verification against digest %q failed", dgst)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateManifest(manifest *registry.ManifestData) error {
|
||||||
|
if manifest.SchemaVersion != 1 {
|
||||||
|
return fmt.Errorf("unsupported schema version: %d", manifest.SchemaVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkValidManifest(manifest *registry.ManifestData) error {
|
|
||||||
if len(manifest.FSLayers) != len(manifest.History) {
|
if len(manifest.FSLayers) != len(manifest.History) {
|
||||||
return fmt.Errorf("length of history not equal to number of layers")
|
return fmt.Errorf("length of history not equal to number of layers")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/tarsum"
|
"github.com/docker/docker/pkg/tarsum"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
|
"github.com/docker/libtrust"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -181,3 +183,121 @@ func TestManifestTarsumCache(t *testing.T) {
|
|||||||
t.Fatalf("Unexpected json value\nExpected:\n%s\nActual:\n%s", v1compat, manifest.History[0].V1Compatibility)
|
t.Fatalf("Unexpected json value\nExpected:\n%s\nActual:\n%s", v1compat, manifest.History[0].V1Compatibility)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestManifestDigestCheck ensures that loadManifest properly verifies the
|
||||||
|
// remote and local digest.
|
||||||
|
func TestManifestDigestCheck(t *testing.T) {
|
||||||
|
tmp, err := utils.TestDirectory("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmp)
|
||||||
|
store := mkTestTagStore(tmp, t)
|
||||||
|
defer store.graph.driver.Cleanup()
|
||||||
|
|
||||||
|
archive, err := fakeTar()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
img := &image.Image{ID: testManifestImageID}
|
||||||
|
if err := store.graph.Register(img, archive); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := store.Tag(testManifestImageName, testManifestTag, testManifestImageID, false); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cs, err := img.GetCheckSum(store.graph.ImageRoot(testManifestImageID)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if cs != "" {
|
||||||
|
t.Fatalf("Non-empty checksum file after register")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate manifest
|
||||||
|
payload, err := store.newManifest(testManifestImageName, testManifestImageName, testManifestTag)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error generating test manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pk, err := libtrust.GenerateECP256PrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error generating private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := libtrust.NewJSONSignature(payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating signature: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sig.Sign(pk); err != nil {
|
||||||
|
t.Fatalf("error signing manifest bytes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedBytes, err := sig.PrettySignature("signatures")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting signed bytes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dgst, err := digest.FromBytes(payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting digest of manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// use this as the "bad" digest
|
||||||
|
zeroDigest, err := digest.FromBytes([]byte{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error making zero digest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote and local match, everything should look good
|
||||||
|
local, _, _, err := store.loadManifest(signedBytes, dgst.String(), dgst)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error verifying local and remote digest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if local != dgst {
|
||||||
|
t.Fatalf("local digest not correctly calculated: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remote and no local, since pulling by tag
|
||||||
|
local, _, _, err = store.loadManifest(signedBytes, "tag", dgst)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error verifying tag pull and remote digest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if local != dgst {
|
||||||
|
t.Fatalf("local digest not correctly calculated: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remote and differing local, this is the most important to fail
|
||||||
|
local, _, _, err = store.loadManifest(signedBytes, zeroDigest.String(), dgst)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("error expected when verifying with differing local digest")
|
||||||
|
}
|
||||||
|
|
||||||
|
// no remote, no local (by tag)
|
||||||
|
local, _, _, err = store.loadManifest(signedBytes, "tag", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error verifying manifest without remote digest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if local != dgst {
|
||||||
|
t.Fatalf("local digest not correctly calculated: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// no remote, with local
|
||||||
|
local, _, _, err = store.loadManifest(signedBytes, dgst.String(), "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error verifying manifest without remote digest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if local != dgst {
|
||||||
|
t.Fatalf("local digest not correctly calculated: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad remote, we fail the check.
|
||||||
|
local, _, _, err = store.loadManifest(signedBytes, dgst.String(), zeroDigest)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("error expected when verifying with differing remote digest")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+41
-29
@@ -142,7 +142,6 @@ func makeMirrorRepoInfo(repoInfo *registry.RepositoryInfo, mirror string) *regis
|
|||||||
|
|
||||||
func configureV2Mirror(repoInfo *registry.RepositoryInfo, s *registry.Service) (*registry.Endpoint, *registry.RepositoryInfo, error) {
|
func configureV2Mirror(repoInfo *registry.RepositoryInfo, s *registry.Service) (*registry.Endpoint, *registry.RepositoryInfo, error) {
|
||||||
mirrors := repoInfo.Index.Mirrors
|
mirrors := repoInfo.Index.Mirrors
|
||||||
|
|
||||||
if len(mirrors) == 0 {
|
if len(mirrors) == 0 {
|
||||||
// no mirrors configured
|
// no mirrors configured
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
@@ -151,13 +150,11 @@ func configureV2Mirror(repoInfo *registry.RepositoryInfo, s *registry.Service) (
|
|||||||
v1MirrorCount := 0
|
v1MirrorCount := 0
|
||||||
var v2MirrorEndpoint *registry.Endpoint
|
var v2MirrorEndpoint *registry.Endpoint
|
||||||
var v2MirrorRepoInfo *registry.RepositoryInfo
|
var v2MirrorRepoInfo *registry.RepositoryInfo
|
||||||
var lastErr error
|
|
||||||
for _, mirror := range mirrors {
|
for _, mirror := range mirrors {
|
||||||
mirrorRepoInfo := makeMirrorRepoInfo(repoInfo, mirror)
|
mirrorRepoInfo := makeMirrorRepoInfo(repoInfo, mirror)
|
||||||
endpoint, err := registry.NewEndpoint(mirrorRepoInfo.Index, nil)
|
endpoint, err := registry.NewEndpoint(mirrorRepoInfo.Index, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Unable to create endpoint for %s: %s", mirror, err)
|
logrus.Errorf("Unable to create endpoint for %s: %s", mirror, err)
|
||||||
lastErr = err
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if endpoint.Version == 2 {
|
if endpoint.Version == 2 {
|
||||||
@@ -182,9 +179,11 @@ func configureV2Mirror(repoInfo *registry.RepositoryInfo, s *registry.Service) (
|
|||||||
return v2MirrorEndpoint, v2MirrorRepoInfo, nil
|
return v2MirrorEndpoint, v2MirrorRepoInfo, nil
|
||||||
}
|
}
|
||||||
if v2MirrorEndpoint != nil && v1MirrorCount > 0 {
|
if v2MirrorEndpoint != nil && v1MirrorCount > 0 {
|
||||||
lastErr = fmt.Errorf("v1 and v2 mirrors configured")
|
return nil, nil, fmt.Errorf("v1 and v2 mirrors configured")
|
||||||
}
|
}
|
||||||
return nil, nil, lastErr
|
// No endpoint could be established with the given mirror configurations
|
||||||
|
// Fallback to pulling from the hub as per v1 behavior.
|
||||||
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TagStore) pullFromV2Mirror(mirrorEndpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo,
|
func (s *TagStore) pullFromV2Mirror(mirrorEndpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo,
|
||||||
@@ -458,17 +457,6 @@ func WriteStatus(requestedTag string, out io.Writer, sf *streamformatter.StreamF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadInfo is used to pass information from download to extractor
|
|
||||||
type downloadInfo struct {
|
|
||||||
imgJSON []byte
|
|
||||||
img *image.Image
|
|
||||||
digest digest.Digest
|
|
||||||
tmpFile *os.File
|
|
||||||
length int64
|
|
||||||
downloaded bool
|
|
||||||
err chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TagStore) pullV2Repository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter) error {
|
func (s *TagStore) pullV2Repository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter) error {
|
||||||
endpoint, err := r.V2RegistryEndpoint(repoInfo.Index)
|
endpoint, err := r.V2RegistryEndpoint(repoInfo.Index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -518,27 +506,34 @@ func (s *TagStore) pullV2Repository(r *registry.Session, out io.Writer, repoInfo
|
|||||||
func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, auth *registry.RequestAuthorization) (bool, error) {
|
func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, auth *registry.RequestAuthorization) (bool, error) {
|
||||||
logrus.Debugf("Pulling tag from V2 registry: %q", tag)
|
logrus.Debugf("Pulling tag from V2 registry: %q", tag)
|
||||||
|
|
||||||
manifestBytes, manifestDigest, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth)
|
remoteDigest, manifestBytes, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadManifest ensures that the manifest payload has the expected digest
|
// loadManifest ensures that the manifest payload has the expected digest
|
||||||
// if the tag is a digest reference.
|
// if the tag is a digest reference.
|
||||||
manifest, verified, err := s.loadManifest(manifestBytes, manifestDigest, tag)
|
localDigest, manifest, verified, err := s.loadManifest(manifestBytes, tag, remoteDigest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("error verifying manifest: %s", err)
|
return false, fmt.Errorf("error verifying manifest: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkValidManifest(manifest); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if verified {
|
if verified {
|
||||||
logrus.Printf("Image manifest for %s has been verified", utils.ImageReference(repoInfo.CanonicalName, tag))
|
logrus.Printf("Image manifest for %s has been verified", utils.ImageReference(repoInfo.CanonicalName, tag))
|
||||||
}
|
}
|
||||||
out.Write(sf.FormatStatus(tag, "Pulling from %s", repoInfo.CanonicalName))
|
out.Write(sf.FormatStatus(tag, "Pulling from %s", repoInfo.CanonicalName))
|
||||||
|
|
||||||
|
// downloadInfo is used to pass information from download to extractor
|
||||||
|
type downloadInfo struct {
|
||||||
|
imgJSON []byte
|
||||||
|
img *image.Image
|
||||||
|
digest digest.Digest
|
||||||
|
tmpFile *os.File
|
||||||
|
length int64
|
||||||
|
downloaded bool
|
||||||
|
err chan error
|
||||||
|
}
|
||||||
|
|
||||||
downloads := make([]downloadInfo, len(manifest.FSLayers))
|
downloads := make([]downloadInfo, len(manifest.FSLayers))
|
||||||
|
|
||||||
for i := len(manifest.FSLayers) - 1; i >= 0; i-- {
|
for i := len(manifest.FSLayers) - 1; i >= 0; i-- {
|
||||||
@@ -611,8 +606,7 @@ func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *regis
|
|||||||
out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Verifying Checksum", nil))
|
out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Verifying Checksum", nil))
|
||||||
|
|
||||||
if !verifier.Verified() {
|
if !verifier.Verified() {
|
||||||
logrus.Infof("Image verification failed: checksum mismatch for %q", di.digest.String())
|
return fmt.Errorf("image layer digest verification failed for %q", di.digest)
|
||||||
verified = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil))
|
out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil))
|
||||||
@@ -689,15 +683,33 @@ func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *regis
|
|||||||
out.Write(sf.FormatStatus(utils.ImageReference(repoInfo.CanonicalName, tag), "The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security."))
|
out.Write(sf.FormatStatus(utils.ImageReference(repoInfo.CanonicalName, tag), "The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security."))
|
||||||
}
|
}
|
||||||
|
|
||||||
if manifestDigest != "" {
|
if localDigest != remoteDigest { // this is not a verification check.
|
||||||
out.Write(sf.FormatStatus("", "Digest: %s", manifestDigest))
|
// NOTE(stevvooe): This is a very defensive branch and should never
|
||||||
|
// happen, since all manifest digest implementations use the same
|
||||||
|
// algorithm.
|
||||||
|
logrus.WithFields(
|
||||||
|
logrus.Fields{
|
||||||
|
"local": localDigest,
|
||||||
|
"remote": remoteDigest,
|
||||||
|
}).Debugf("local digest does not match remote")
|
||||||
|
|
||||||
|
out.Write(sf.FormatStatus("", "Remote Digest: %s", remoteDigest))
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.DigestReference(tag) {
|
out.Write(sf.FormatStatus("", "Digest: %s", localDigest))
|
||||||
if err = s.SetDigest(repoInfo.LocalName, tag, downloads[0].img.ID); err != nil {
|
|
||||||
|
if tag == localDigest.String() {
|
||||||
|
// TODO(stevvooe): Ideally, we should always set the digest so we can
|
||||||
|
// use the digest whether we pull by it or not. Unfortunately, the tag
|
||||||
|
// store treats the digest as a separate tag, meaning there may be an
|
||||||
|
// untagged digest image that would seem to be dangling by a user.
|
||||||
|
|
||||||
|
if err = s.SetDigest(repoInfo.LocalName, localDigest.String(), downloads[0].img.ID); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if !utils.DigestReference(tag) {
|
||||||
// only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest)
|
// only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest)
|
||||||
if err = s.Tag(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil {
|
if err = s.Tag(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
+1
-1
@@ -413,7 +413,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o
|
|||||||
m.History[i] = ®istry.ManifestHistory{V1Compatibility: string(jsonData)}
|
m.History[i] = ®istry.ManifestHistory{V1Compatibility: string(jsonData)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkValidManifest(m); err != nil {
|
if err := validateManifest(m); err != nil {
|
||||||
return fmt.Errorf("invalid manifest: %s", err)
|
return fmt.Errorf("invalid manifest: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/docker/docker/daemon/graphdriver"
|
"github.com/docker/docker/daemon/graphdriver"
|
||||||
_ "github.com/docker/docker/daemon/graphdriver/vfs" // import the vfs driver so it is used in the tests
|
_ "github.com/docker/docker/daemon/graphdriver/vfs" // import the vfs driver so it is used in the tests
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
|
"github.com/docker/docker/trust"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,9 +61,16 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trust, err := trust.NewTrustStore(root + "/trust")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
tagCfg := &TagStoreConfig{
|
tagCfg := &TagStoreConfig{
|
||||||
Graph: graph,
|
Graph: graph,
|
||||||
Events: events.New(),
|
Events: events.New(),
|
||||||
|
Trust: trust,
|
||||||
}
|
}
|
||||||
store, err := NewTagStore(path.Join(root, "tags"), tagCfg)
|
store, err := NewTagStore(path.Join(root, "tags"), tagCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+14
-10
@@ -96,7 +96,6 @@ fi
|
|||||||
if [ "$DOCKER_EXPERIMENTAL" ]; then
|
if [ "$DOCKER_EXPERIMENTAL" ]; then
|
||||||
echo >&2 '# WARNING! DOCKER_EXPERIMENTAL is set: building experimental features'
|
echo >&2 '# WARNING! DOCKER_EXPERIMENTAL is set: building experimental features'
|
||||||
echo >&2
|
echo >&2
|
||||||
VERSION+="-experimental"
|
|
||||||
DOCKER_BUILDTAGS+=" experimental"
|
DOCKER_BUILDTAGS+=" experimental"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -198,13 +197,13 @@ go_test_dir() {
|
|||||||
# if our current go install has -cover, we want to use it :)
|
# if our current go install has -cover, we want to use it :)
|
||||||
mkdir -p "$DEST/coverprofiles"
|
mkdir -p "$DEST/coverprofiles"
|
||||||
coverprofile="docker${dir#.}"
|
coverprofile="docker${dir#.}"
|
||||||
coverprofile="$DEST/coverprofiles/${coverprofile//\//-}"
|
coverprofile="$ABS_DEST/coverprofiles/${coverprofile//\//-}"
|
||||||
testcover=( -cover -coverprofile "$coverprofile" $coverpkg )
|
testcover=( -cover -coverprofile "$coverprofile" $coverpkg )
|
||||||
fi
|
fi
|
||||||
(
|
(
|
||||||
export DEST
|
|
||||||
echo '+ go test' $TESTFLAGS "${DOCKER_PKG}${dir#.}"
|
echo '+ go test' $TESTFLAGS "${DOCKER_PKG}${dir#.}"
|
||||||
cd "$dir"
|
cd "$dir"
|
||||||
|
export DEST="$ABS_DEST" # we're in a subshell, so this is safe -- our integration-cli tests need DEST, and "cd" screws it up
|
||||||
test_env go test ${testcover[@]} -ldflags "$LDFLAGS" "${BUILDFLAGS[@]}" $TESTFLAGS
|
test_env go test ${testcover[@]} -ldflags "$LDFLAGS" "${BUILDFLAGS[@]}" $TESTFLAGS
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -217,7 +216,7 @@ test_env() {
|
|||||||
DOCKER_USERLANDPROXY="$DOCKER_USERLANDPROXY" \
|
DOCKER_USERLANDPROXY="$DOCKER_USERLANDPROXY" \
|
||||||
DOCKER_HOST="$DOCKER_HOST" \
|
DOCKER_HOST="$DOCKER_HOST" \
|
||||||
GOPATH="$GOPATH" \
|
GOPATH="$GOPATH" \
|
||||||
HOME="$DEST/fake-HOME" \
|
HOME="$ABS_DEST/fake-HOME" \
|
||||||
PATH="$PATH" \
|
PATH="$PATH" \
|
||||||
TEST_DOCKERINIT_PATH="$TEST_DOCKERINIT_PATH" \
|
TEST_DOCKERINIT_PATH="$TEST_DOCKERINIT_PATH" \
|
||||||
"$@"
|
"$@"
|
||||||
@@ -271,11 +270,9 @@ hash_files() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bundle() {
|
bundle() {
|
||||||
bundlescript=$1
|
local bundle="$1"; shift
|
||||||
bundle=$(basename $bundlescript)
|
echo "---> Making bundle: $(basename "$bundle") (in $DEST)"
|
||||||
echo "---> Making bundle: $bundle (in bundles/$VERSION/$bundle)"
|
source "$SCRIPTDIR/make/$bundle" "$@"
|
||||||
mkdir -p "bundles/$VERSION/$bundle"
|
|
||||||
source "$bundlescript" "$(pwd)/bundles/$VERSION/$bundle"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
@@ -301,7 +298,14 @@ main() {
|
|||||||
bundles=($@)
|
bundles=($@)
|
||||||
fi
|
fi
|
||||||
for bundle in ${bundles[@]}; do
|
for bundle in ${bundles[@]}; do
|
||||||
bundle "$SCRIPTDIR/make/$bundle"
|
export DEST="bundles/$VERSION/$(basename "$bundle")"
|
||||||
|
# Cygdrive paths don't play well with go build -o.
|
||||||
|
if [[ "$(uname -s)" == CYGWIN* ]]; then
|
||||||
|
export DEST="$(cygpath -mw "$DEST")"
|
||||||
|
fi
|
||||||
|
mkdir -p "$DEST"
|
||||||
|
ABS_DEST="$(cd "$DEST" && pwd -P)"
|
||||||
|
bundle "$bundle"
|
||||||
echo
|
echo
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Source: docker-engine
|
Source: docker-engine
|
||||||
Maintainer: Docker <support@docker.com>
|
Maintainer: Docker <support@docker.com>
|
||||||
Homepage: https://dockerproject.com
|
Homepage: https://dockerproject.org
|
||||||
Vcs-Browser: https://github.com/docker/docker
|
Vcs-Browser: https://github.com/docker/docker
|
||||||
Vcs-Git: git://github.com/docker/docker.git
|
Vcs-Git: git://github.com/docker/docker.git
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Summary: The open-source application container engine
|
|||||||
License: ASL 2.0
|
License: ASL 2.0
|
||||||
Source: %{name}.tar.gz
|
Source: %{name}.tar.gz
|
||||||
|
|
||||||
URL: https://dockerproject.com
|
URL: https://dockerproject.org
|
||||||
Vendor: Docker
|
Vendor: Docker
|
||||||
Packager: Docker <support@docker.com>
|
Packager: Docker <support@docker.com>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# see test-integration-cli for example usage of this script
|
# see test-integration-cli for example usage of this script
|
||||||
|
|
||||||
export PATH="$DEST/../binary:$DEST/../dynbinary:$DEST/../gccgo:$DEST/../dyngccgo:$PATH"
|
export PATH="$ABS_DEST/../binary:$ABS_DEST/../dynbinary:$ABS_DEST/../gccgo:$ABS_DEST/../dyngccgo:$PATH"
|
||||||
|
|
||||||
if ! command -v docker &> /dev/null; then
|
if ! command -v docker &> /dev/null; then
|
||||||
echo >&2 'error: binary or dynbinary must be run before .integration-daemon-start'
|
echo >&2 'error: binary or dynbinary must be run before .integration-daemon-start'
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST=$1
|
|
||||||
BINARY_NAME="docker-$VERSION"
|
BINARY_NAME="docker-$VERSION"
|
||||||
BINARY_EXTENSION="$(binary_extension)"
|
BINARY_EXTENSION="$(binary_extension)"
|
||||||
BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
|
BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
|
||||||
|
|
||||||
# Cygdrive paths don't play well with go build -o.
|
|
||||||
if [[ "$(uname -s)" == CYGWIN* ]]; then
|
|
||||||
DEST=$(cygpath -mw $DEST)
|
|
||||||
fi
|
|
||||||
|
|
||||||
source "${MAKEDIR}/.go-autogen"
|
source "${MAKEDIR}/.go-autogen"
|
||||||
|
|
||||||
echo "Building: $DEST/$BINARY_FULLNAME"
|
echo "Building: $DEST/$BINARY_FULLNAME"
|
||||||
|
|||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST=$1
|
# subshell so that we can export PATH and TZ without breaking other things
|
||||||
|
|
||||||
# subshell so that we can export PATH without breaking other things
|
|
||||||
(
|
(
|
||||||
|
export TZ=UTC # make sure our "date" variables are UTC-based
|
||||||
|
|
||||||
source "${MAKEDIR}/.integration-daemon-start"
|
source "${MAKEDIR}/.integration-daemon-start"
|
||||||
|
|
||||||
# TODO consider using frozen images for the dockercore/builder-deb tags
|
# TODO consider using frozen images for the dockercore/builder-deb tags
|
||||||
|
|||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST=$1
|
# subshell so that we can export PATH and TZ without breaking other things
|
||||||
|
|
||||||
# subshell so that we can export PATH without breaking other things
|
|
||||||
(
|
(
|
||||||
|
export TZ=UTC # make sure our "date" variables are UTC-based
|
||||||
|
|
||||||
source "$(dirname "$BASH_SOURCE")/.integration-daemon-start"
|
source "$(dirname "$BASH_SOURCE")/.integration-daemon-start"
|
||||||
|
|
||||||
# TODO consider using frozen images for the dockercore/builder-rpm tags
|
# TODO consider using frozen images for the dockercore/builder-rpm tags
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST="$1"
|
|
||||||
|
|
||||||
bundle_cover() {
|
bundle_cover() {
|
||||||
coverprofiles=( "$DEST/../"*"/coverprofiles/"* )
|
coverprofiles=( "$DEST/../"*"/coverprofiles/"* )
|
||||||
for p in "${coverprofiles[@]}"; do
|
for p in "${coverprofiles[@]}"; do
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST=$1
|
|
||||||
|
|
||||||
# explicit list of os/arch combos that support being a daemon
|
# explicit list of os/arch combos that support being a daemon
|
||||||
declare -A daemonSupporting
|
declare -A daemonSupporting
|
||||||
daemonSupporting=(
|
daemonSupporting=(
|
||||||
@@ -21,13 +19,15 @@ fi
|
|||||||
|
|
||||||
for platform in $DOCKER_CROSSPLATFORMS; do
|
for platform in $DOCKER_CROSSPLATFORMS; do
|
||||||
(
|
(
|
||||||
mkdir -p "$DEST/$platform" # bundles/VERSION/cross/GOOS/GOARCH/docker-VERSION
|
export DEST="$DEST/$platform" # bundles/VERSION/cross/GOOS/GOARCH/docker-VERSION
|
||||||
|
mkdir -p "$DEST"
|
||||||
|
ABS_DEST="$(cd "$DEST" && pwd -P)"
|
||||||
export GOOS=${platform%/*}
|
export GOOS=${platform%/*}
|
||||||
export GOARCH=${platform##*/}
|
export GOARCH=${platform##*/}
|
||||||
if [ -z "${daemonSupporting[$platform]}" ]; then
|
if [ -z "${daemonSupporting[$platform]}" ]; then
|
||||||
export LDFLAGS_STATIC_DOCKER="" # we just need a simple client for these platforms
|
export LDFLAGS_STATIC_DOCKER="" # we just need a simple client for these platforms
|
||||||
export BUILDFLAGS=( "${ORIG_BUILDFLAGS[@]/ daemon/}" ) # remove the "daemon" build tag from platforms that aren't supported
|
export BUILDFLAGS=( "${ORIG_BUILDFLAGS[@]/ daemon/}" ) # remove the "daemon" build tag from platforms that aren't supported
|
||||||
fi
|
fi
|
||||||
source "${MAKEDIR}/binary" "$DEST/$platform"
|
source "${MAKEDIR}/binary"
|
||||||
)
|
)
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST=$1
|
|
||||||
|
|
||||||
if [ -z "$DOCKER_CLIENTONLY" ]; then
|
if [ -z "$DOCKER_CLIENTONLY" ]; then
|
||||||
source "${MAKEDIR}/.dockerinit"
|
source "${MAKEDIR}/.dockerinit"
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST=$1
|
|
||||||
|
|
||||||
if [ -z "$DOCKER_CLIENTONLY" ]; then
|
if [ -z "$DOCKER_CLIENTONLY" ]; then
|
||||||
source "${MAKEDIR}/.dockerinit-gccgo"
|
source "${MAKEDIR}/.dockerinit-gccgo"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST=$1
|
|
||||||
BINARY_NAME="docker-$VERSION"
|
BINARY_NAME="docker-$VERSION"
|
||||||
BINARY_EXTENSION="$(binary_extension)"
|
BINARY_EXTENSION="$(binary_extension)"
|
||||||
BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
|
BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST=$1
|
|
||||||
|
|
||||||
# subshell so that we can export PATH without breaking other things
|
# subshell so that we can export PATH without breaking other things
|
||||||
(
|
(
|
||||||
source "${MAKEDIR}/.integration-daemon-start"
|
source "${MAKEDIR}/.integration-daemon-start"
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST=$1
|
|
||||||
|
|
||||||
bundle_test_integration_cli() {
|
bundle_test_integration_cli() {
|
||||||
go_test_dir ./integration-cli
|
go_test_dir ./integration-cli
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -1,7 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEST=$1
|
|
||||||
: ${PARALLEL_JOBS:=$(nproc 2>/dev/null || echo 1)} # if nproc fails (usually because we don't have it), let's not parallelize by default
|
: ${PARALLEL_JOBS:=$(nproc 2>/dev/null || echo 1)} # if nproc fails (usually because we don't have it), let's not parallelize by default
|
||||||
|
|
||||||
RED=$'\033[31m'
|
RED=$'\033[31m'
|
||||||
@@ -26,10 +25,9 @@ bundle_test_unit() {
|
|||||||
export LDFLAGS
|
export LDFLAGS
|
||||||
export TESTFLAGS
|
export TESTFLAGS
|
||||||
export HAVE_GO_TEST_COVER
|
export HAVE_GO_TEST_COVER
|
||||||
export DEST
|
|
||||||
|
|
||||||
# some hack to export array variables
|
# some hack to export array variables
|
||||||
export BUILDFLAGS_FILE="buildflags_file"
|
export BUILDFLAGS_FILE="$DEST/buildflags-file"
|
||||||
( IFS=$'\n'; echo "${BUILDFLAGS[*]}" ) > "$BUILDFLAGS_FILE"
|
( IFS=$'\n'; echo "${BUILDFLAGS[*]}" ) > "$BUILDFLAGS_FILE"
|
||||||
|
|
||||||
if command -v parallel &> /dev/null; then
|
if command -v parallel &> /dev/null; then
|
||||||
@@ -59,7 +57,7 @@ go_run_test_dir() {
|
|||||||
while read dir; do
|
while read dir; do
|
||||||
echo
|
echo
|
||||||
echo '+ go test' $TESTFLAGS "${DOCKER_PKG}${dir#.}"
|
echo '+ go test' $TESTFLAGS "${DOCKER_PKG}${dir#.}"
|
||||||
precompiled="$DEST/precompiled/$dir.test$(binary_extension)"
|
precompiled="$ABS_DEST/precompiled/$dir.test$(binary_extension)"
|
||||||
if ! ( cd "$dir" && test_env "$precompiled" $TESTFLAGS ); then
|
if ! ( cd "$dir" && test_env "$precompiled" $TESTFLAGS ); then
|
||||||
TESTS_FAILED+=("$dir")
|
TESTS_FAILED+=("$dir")
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
DEST="$1"
|
|
||||||
CROSS="$DEST/../cross"
|
CROSS="$DEST/../cross"
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|||||||
+4
-6
@@ -1,7 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
DEST=$1
|
|
||||||
|
|
||||||
PKGVERSION="${VERSION//-/'~'}"
|
PKGVERSION="${VERSION//-/'~'}"
|
||||||
# if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better
|
# if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better
|
||||||
if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then
|
if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then
|
||||||
@@ -37,7 +35,7 @@ PACKAGE_LICENSE="Apache-2.0"
|
|||||||
# Build docker as an ubuntu package using FPM and REPREPRO (sue me).
|
# Build docker as an ubuntu package using FPM and REPREPRO (sue me).
|
||||||
# bundle_binary must be called first.
|
# bundle_binary must be called first.
|
||||||
bundle_ubuntu() {
|
bundle_ubuntu() {
|
||||||
DIR=$DEST/build
|
DIR="$ABS_DEST/build"
|
||||||
|
|
||||||
# Include our udev rules
|
# Include our udev rules
|
||||||
mkdir -p "$DIR/etc/udev/rules.d"
|
mkdir -p "$DIR/etc/udev/rules.d"
|
||||||
@@ -140,9 +138,9 @@ EOF
|
|||||||
# create lxc-docker-VERSION package
|
# create lxc-docker-VERSION package
|
||||||
fpm -s dir -C "$DIR" \
|
fpm -s dir -C "$DIR" \
|
||||||
--name "lxc-docker-$VERSION" --version "$PKGVERSION" \
|
--name "lxc-docker-$VERSION" --version "$PKGVERSION" \
|
||||||
--after-install "$DEST/postinst" \
|
--after-install "$ABS_DEST/postinst" \
|
||||||
--before-remove "$DEST/prerm" \
|
--before-remove "$ABS_DEST/prerm" \
|
||||||
--after-remove "$DEST/postrm" \
|
--after-remove "$ABS_DEST/postrm" \
|
||||||
--architecture "$PACKAGE_ARCHITECTURE" \
|
--architecture "$PACKAGE_ARCHITECTURE" \
|
||||||
--prefix / \
|
--prefix / \
|
||||||
--depends iptables \
|
--depends iptables \
|
||||||
|
|||||||
+1
-1
@@ -266,7 +266,7 @@ Architectures: amd64 i386
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Add the DEB package to the APT repo
|
# Add the DEB package to the APT repo
|
||||||
DEBFILE=bundles/$VERSION/ubuntu/lxc-docker*.deb
|
DEBFILE=( bundles/$VERSION/ubuntu/lxc-docker*.deb )
|
||||||
reprepro -b "$APTDIR" includedeb docker "$DEBFILE"
|
reprepro -b "$APTDIR" includedeb docker "$DEBFILE"
|
||||||
|
|
||||||
# Sign
|
# Sign
|
||||||
|
|||||||
+2
-2
@@ -55,12 +55,12 @@ clone hg code.google.com/p/go.net 84a4013f96e0
|
|||||||
clone hg code.google.com/p/gosqlite 74691fb6f837
|
clone hg code.google.com/p/gosqlite 74691fb6f837
|
||||||
|
|
||||||
#get libnetwork packages
|
#get libnetwork packages
|
||||||
clone git github.com/docker/libnetwork 2da2dc055de5a474c8540871ad88a48213b0994f
|
clone git github.com/docker/libnetwork f72ad20491e8c46d9664da3f32a0eddb301e7c8d
|
||||||
clone git github.com/vishvananda/netns 008d17ae001344769b031375bdb38a86219154c6
|
clone git github.com/vishvananda/netns 008d17ae001344769b031375bdb38a86219154c6
|
||||||
clone git github.com/vishvananda/netlink 8eb64238879fed52fd51c5b30ad20b928fb4c36c
|
clone git github.com/vishvananda/netlink 8eb64238879fed52fd51c5b30ad20b928fb4c36c
|
||||||
|
|
||||||
# get distribution packages
|
# get distribution packages
|
||||||
clone git github.com/docker/distribution d957768537c5af40e4f4cd96871f7b2bde9e2923
|
clone git github.com/docker/distribution b9eeb328080d367dbde850ec6e94f1e4ac2b5efe
|
||||||
mv src/github.com/docker/distribution/digest tmp-digest
|
mv src/github.com/docker/distribution/digest tmp-digest
|
||||||
mv src/github.com/docker/distribution/registry/api tmp-api
|
mv src/github.com/docker/distribution/registry/api tmp-api
|
||||||
rm -rf src/github.com/docker/distribution
|
rm -rf src/github.com/docker/distribution
|
||||||
|
|||||||
@@ -51,6 +51,39 @@ func (s *DockerSuite) TestContainerApiGetAll(c *check.C) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// regression test for empty json field being omitted #13691
|
||||||
|
func (s *DockerSuite) TestContainerApiGetJSONNoFieldsOmitted(c *check.C) {
|
||||||
|
runCmd := exec.Command(dockerBinary, "run", "busybox", "true")
|
||||||
|
_, err := runCommand(runCmd)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
status, body, err := sockRequest("GET", "/containers/json?all=1", nil)
|
||||||
|
c.Assert(status, check.Equals, http.StatusOK)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
// empty Labels field triggered this bug, make sense to check for everything
|
||||||
|
// cause even Ports for instance can trigger this bug
|
||||||
|
// better safe than sorry..
|
||||||
|
fields := []string{
|
||||||
|
"Id",
|
||||||
|
"Names",
|
||||||
|
"Image",
|
||||||
|
"Command",
|
||||||
|
"Created",
|
||||||
|
"Ports",
|
||||||
|
"Labels",
|
||||||
|
"Status",
|
||||||
|
}
|
||||||
|
|
||||||
|
// decoding into types.Container do not work since it eventually unmarshal
|
||||||
|
// and empty field to an empty go map, so we just check for a string
|
||||||
|
for _, f := range fields {
|
||||||
|
if !strings.Contains(string(body), f) {
|
||||||
|
c.Fatalf("Field %s is missing and it shouldn't", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestContainerApiGetExport(c *check.C) {
|
func (s *DockerSuite) TestContainerApiGetExport(c *check.C) {
|
||||||
name := "exportcontainer"
|
name := "exportcontainer"
|
||||||
runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test")
|
runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test")
|
||||||
@@ -254,7 +287,7 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestContainerStatsRmRunning(c *check.C) {
|
func (s *DockerSuite) TestGetContainerStatsRmRunning(c *check.C) {
|
||||||
out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
|
out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
|
||||||
id := strings.TrimSpace(out)
|
id := strings.TrimSpace(out)
|
||||||
|
|
||||||
@@ -290,6 +323,89 @@ func (s *DockerSuite) TestContainerStatsRmRunning(c *check.C) {
|
|||||||
c.Assert(err, check.Not(check.IsNil))
|
c.Assert(err, check.Not(check.IsNil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// regression test for gh13421
|
||||||
|
// previous test was just checking one stat entry so it didn't fail (stats with
|
||||||
|
// stream false always return one stat)
|
||||||
|
func (s *DockerSuite) TestGetContainerStatsStream(c *check.C) {
|
||||||
|
name := "statscontainer"
|
||||||
|
runCmd := exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "top")
|
||||||
|
_, err := runCommand(runCmd)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
type b struct {
|
||||||
|
status int
|
||||||
|
body []byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
bc := make(chan b, 1)
|
||||||
|
go func() {
|
||||||
|
status, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
|
||||||
|
bc <- b{status, body, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// allow some time to stream the stats from the container
|
||||||
|
time.Sleep(4 * time.Second)
|
||||||
|
if _, err := runCommand(exec.Command(dockerBinary, "rm", "-f", name)); err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect the results from the stats stream or timeout and fail
|
||||||
|
// if the stream was not disconnected.
|
||||||
|
select {
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
c.Fatal("stream was not closed after container was removed")
|
||||||
|
case sr := <-bc:
|
||||||
|
c.Assert(sr.err, check.IsNil)
|
||||||
|
c.Assert(sr.status, check.Equals, http.StatusOK)
|
||||||
|
|
||||||
|
s := string(sr.body)
|
||||||
|
// count occurrences of "read" of types.Stats
|
||||||
|
if l := strings.Count(s, "read"); l < 2 {
|
||||||
|
c.Fatalf("Expected more than one stat streamed, got %d", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestGetContainerStatsNoStream(c *check.C) {
|
||||||
|
name := "statscontainer"
|
||||||
|
runCmd := exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "top")
|
||||||
|
_, err := runCommand(runCmd)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
type b struct {
|
||||||
|
status int
|
||||||
|
body []byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
bc := make(chan b, 1)
|
||||||
|
go func() {
|
||||||
|
status, body, err := sockRequest("GET", "/containers/"+name+"/stats?stream=0", nil)
|
||||||
|
bc <- b{status, body, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// allow some time to stream the stats from the container
|
||||||
|
time.Sleep(4 * time.Second)
|
||||||
|
if _, err := runCommand(exec.Command(dockerBinary, "rm", "-f", name)); err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect the results from the stats stream or timeout and fail
|
||||||
|
// if the stream was not disconnected.
|
||||||
|
select {
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
c.Fatal("stream was not closed after container was removed")
|
||||||
|
case sr := <-bc:
|
||||||
|
c.Assert(sr.err, check.IsNil)
|
||||||
|
c.Assert(sr.status, check.Equals, http.StatusOK)
|
||||||
|
|
||||||
|
s := string(sr.body)
|
||||||
|
// count occurrences of "read" of types.Stats
|
||||||
|
if l := strings.Count(s, "read"); l != 1 {
|
||||||
|
c.Fatalf("Expected only one stat streamed, got %d", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) {
|
func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) {
|
||||||
// TODO: this test does nothing because we are c.Assert'ing in goroutine
|
// TODO: this test does nothing because we are c.Assert'ing in goroutine
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/go-check/check"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestCliStatsNoStreamGetCpu(c *check.C) {
|
||||||
|
out, _ := dockerCmd(c, "run", "-d", "--cpu-quota=2000", "busybox", "/bin/sh", "-c", "while true;do echo 'Hello';done")
|
||||||
|
|
||||||
|
id := strings.TrimSpace(out)
|
||||||
|
if err := waitRun(id); err != nil {
|
||||||
|
c.Fatal(err)
|
||||||
|
}
|
||||||
|
ch := make(chan error)
|
||||||
|
var v *types.Stats
|
||||||
|
go func() {
|
||||||
|
_, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=1", id), nil, "")
|
||||||
|
if err != nil {
|
||||||
|
ch <- err
|
||||||
|
}
|
||||||
|
dec := json.NewDecoder(body)
|
||||||
|
if err := dec.Decode(&v); err != nil {
|
||||||
|
ch <- err
|
||||||
|
}
|
||||||
|
ch <- nil
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case e := <-ch:
|
||||||
|
if e == nil {
|
||||||
|
var cpuPercent = 0.0
|
||||||
|
cpuDelta := float64(v.CpuStats.CpuUsage.TotalUsage - v.PreCpuStats.CpuUsage.TotalUsage)
|
||||||
|
systemDelta := float64(v.CpuStats.SystemUsage - v.PreCpuStats.SystemUsage)
|
||||||
|
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0
|
||||||
|
if cpuPercent < 1.8 || cpuPercent > 2.2 {
|
||||||
|
c.Fatal("docker stats with no-stream get cpu usage failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
case <-time.After(4 * time.Second):
|
||||||
|
c.Fatal("docker stats with no-stream timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1165,3 +1165,19 @@ func (s *DockerDaemonSuite) TestRunContainerWithBridgeNone(c *check.C) {
|
|||||||
c.Assert(strings.Contains(out, "eth0"), check.Equals, false,
|
c.Assert(strings.Contains(out, "eth0"), check.Equals, false,
|
||||||
check.Commentf("There shouldn't be eth0 in container when network is disabled: %s", out))
|
check.Commentf("There shouldn't be eth0 in container when network is disabled: %s", out))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerDaemonSuite) TestDaemonRestartWithContainerRunning(t *check.C) {
|
||||||
|
if err := s.d.StartWithBusybox(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if out, err := s.d.Cmd("run", "-ti", "-d", "--name", "test", "busybox"); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
if err := s.d.Restart(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Container 'test' should be removed without error
|
||||||
|
if out, err := s.d.Cmd("rm", "test"); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,8 +17,13 @@ func (s *DockerSuite) TestExperimentalVersion(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, line := range strings.Split(out, "\n") {
|
for _, line := range strings.Split(out, "\n") {
|
||||||
if strings.HasPrefix(line, "Client version:") || strings.HasPrefix(line, "Server version:") {
|
if strings.HasPrefix(line, "Experimental (client):") || strings.HasPrefix(line, "Experimental (server):") {
|
||||||
c.Assert(line, check.Matches, "*-experimental")
|
c.Assert(line, check.Matches, "*true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
versionCmd = exec.Command(dockerBinary, "-v")
|
||||||
|
if out, _, err = runCommandWithOutput(versionCmd); err != nil || !strings.Contains(out, ", experimental") {
|
||||||
|
c.Fatalf("docker version did not contain experimental: %s, %v", out, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
|||||||
// Skip first line, its "Commands:"
|
// Skip first line, its "Commands:"
|
||||||
cmds := []string{}
|
cmds := []string{}
|
||||||
for _, cmd := range strings.Split(out[i:], "\n")[1:] {
|
for _, cmd := range strings.Split(out[i:], "\n")[1:] {
|
||||||
|
var stderr string
|
||||||
|
|
||||||
// Stop on blank line or non-idented line
|
// Stop on blank line or non-idented line
|
||||||
if cmd == "" || !unicode.IsSpace(rune(cmd[0])) {
|
if cmd == "" || !unicode.IsSpace(rune(cmd[0])) {
|
||||||
break
|
break
|
||||||
@@ -102,12 +104,24 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
|||||||
cmd = strings.Split(strings.TrimSpace(cmd), " ")[0]
|
cmd = strings.Split(strings.TrimSpace(cmd), " ")[0]
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
// Check the full usage text
|
||||||
helpCmd := exec.Command(dockerBinary, cmd, "--help")
|
helpCmd := exec.Command(dockerBinary, cmd, "--help")
|
||||||
helpCmd.Env = newEnvs
|
helpCmd.Env = newEnvs
|
||||||
out, ec, err := runCommandWithOutput(helpCmd)
|
out, stderr, ec, err = runCommandWithStdoutStderr(helpCmd)
|
||||||
|
if len(stderr) != 0 {
|
||||||
|
c.Fatalf("Error on %q help. non-empty stderr:%q", cmd, stderr)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(out, "\n\n") {
|
||||||
|
c.Fatalf("Should not have blank line on %q\nout:%q", cmd, out)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, "--help=false") {
|
||||||
|
c.Fatalf("Should show full usage on %q\nout:%q", cmd, out)
|
||||||
|
}
|
||||||
if err != nil || ec != 0 {
|
if err != nil || ec != 0 {
|
||||||
c.Fatalf("Error on %q help: %s\nexit code:%d", cmd, out, ec)
|
c.Fatalf("Error on %q help: %s\nexit code:%d", cmd, out, ec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check each line for lots of stuff
|
||||||
lines := strings.Split(out, "\n")
|
lines := strings.Split(out, "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if len(line) > 80 {
|
if len(line) > 80 {
|
||||||
@@ -142,6 +156,77 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For each command make sure we generate an error
|
||||||
|
// if we give a bad arg
|
||||||
|
dCmd := exec.Command(dockerBinary, cmd, "--badArg")
|
||||||
|
out, stderr, ec, err = runCommandWithStdoutStderr(dCmd)
|
||||||
|
if len(out) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
|
||||||
|
c.Fatalf("Bad results from 'docker %s --badArg'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", cmd, ec, out, stderr, err)
|
||||||
|
}
|
||||||
|
// Be really picky
|
||||||
|
if strings.HasSuffix(stderr, "\n\n") {
|
||||||
|
c.Fatalf("Should not have a blank line at the end of 'docker rm'\n%s", stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now make sure that each command will print a short-usage
|
||||||
|
// (not a full usage - meaning no opts section) if we
|
||||||
|
// are missing a required arg or pass in a bad arg
|
||||||
|
|
||||||
|
// These commands will never print a short-usage so don't test
|
||||||
|
noShortUsage := map[string]string{
|
||||||
|
"images": "",
|
||||||
|
"login": "",
|
||||||
|
"logout": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := noShortUsage[cmd]; !ok {
|
||||||
|
// For each command run it w/o any args. It will either return
|
||||||
|
// valid output or print a short-usage
|
||||||
|
var dCmd *exec.Cmd
|
||||||
|
var stdout, stderr string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
// skipNoArgs are ones that we don't want to try w/o
|
||||||
|
// any args. Either because it'll hang the test or
|
||||||
|
// lead to incorrect test result (like false negative).
|
||||||
|
// Whatever the reason, skip trying to run w/o args and
|
||||||
|
// jump to trying with a bogus arg.
|
||||||
|
skipNoArgs := map[string]string{
|
||||||
|
"events": "",
|
||||||
|
"load": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
ec = 0
|
||||||
|
if _, ok := skipNoArgs[cmd]; !ok {
|
||||||
|
args = []string{cmd}
|
||||||
|
dCmd = exec.Command(dockerBinary, args...)
|
||||||
|
stdout, stderr, ec, err = runCommandWithStdoutStderr(dCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If its ok w/o any args then try again with an arg
|
||||||
|
if ec == 0 {
|
||||||
|
args = []string{cmd, "badArg"}
|
||||||
|
dCmd = exec.Command(dockerBinary, args...)
|
||||||
|
stdout, stderr, ec, err = runCommandWithStdoutStderr(dCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
|
||||||
|
c.Fatalf("Bad output from %q\nstdout:%q\nstderr:%q\nec:%d\nerr:%q", args, stdout, stderr, ec, err)
|
||||||
|
}
|
||||||
|
// Should have just short usage
|
||||||
|
if !strings.Contains(stderr, "\nUsage: ") {
|
||||||
|
c.Fatalf("Missing short usage on %q\nstderr:%q", args, stderr)
|
||||||
|
}
|
||||||
|
// But shouldn't have full usage
|
||||||
|
if strings.Contains(stderr, "--help=false") {
|
||||||
|
c.Fatalf("Should not have full usage on %q\nstderr:%q", args, stderr)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(stderr, "\n\n") {
|
||||||
|
c.Fatalf("Should not have a blank line on %q\nstderr:%q", args, stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := 39
|
expected := 39
|
||||||
@@ -153,31 +238,92 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestHelpErrorStderr(c *check.C) {
|
func (s *DockerSuite) TestHelpExitCodesHelpOutput(c *check.C) {
|
||||||
// If we had a generic CLI test file this one shoudl go in there
|
// Test to make sure the exit code and output (stdout vs stderr) of
|
||||||
|
// various good and bad cases are what we expect
|
||||||
|
|
||||||
cmd := exec.Command(dockerBinary, "boogie")
|
// docker : stdout=all, stderr=empty, rc=0
|
||||||
out, ec, err := runCommandWithOutput(cmd)
|
cmd := exec.Command(dockerBinary)
|
||||||
if err == nil || ec == 0 {
|
stdout, stderr, ec, err := runCommandWithStdoutStderr(cmd)
|
||||||
c.Fatalf("Boogie command should have failed")
|
if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
|
||||||
|
c.Fatalf("Bad results from 'docker'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
||||||
|
}
|
||||||
|
// Be really pick
|
||||||
|
if strings.HasSuffix(stdout, "\n\n") {
|
||||||
|
c.Fatalf("Should not have a blank line at the end of 'docker'\n%s", stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := "docker: 'boogie' is not a docker command. See 'docker --help'.\n"
|
// docker help: stdout=all, stderr=empty, rc=0
|
||||||
if out != expected {
|
cmd = exec.Command(dockerBinary, "help")
|
||||||
c.Fatalf("Bad output from boogie\nGot:%s\nExpected:%s", out, expected)
|
stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
|
||||||
|
if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
|
||||||
|
c.Fatalf("Bad results from 'docker help'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
||||||
|
}
|
||||||
|
// Be really pick
|
||||||
|
if strings.HasSuffix(stdout, "\n\n") {
|
||||||
|
c.Fatalf("Should not have a blank line at the end of 'docker help'\n%s", stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = exec.Command(dockerBinary, "rename", "foo", "bar")
|
// docker --help: stdout=all, stderr=empty, rc=0
|
||||||
out, ec, err = runCommandWithOutput(cmd)
|
cmd = exec.Command(dockerBinary, "--help")
|
||||||
if err == nil || ec == 0 {
|
stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
|
||||||
c.Fatalf("Rename should have failed")
|
if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
|
||||||
|
c.Fatalf("Bad results from 'docker --help'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
||||||
|
}
|
||||||
|
// Be really pick
|
||||||
|
if strings.HasSuffix(stdout, "\n\n") {
|
||||||
|
c.Fatalf("Should not have a blank line at the end of 'docker --help'\n%s", stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected = `Error response from daemon: no such id: foo
|
// docker inspect busybox: stdout=all, stderr=empty, rc=0
|
||||||
Error: failed to rename container named foo
|
// Just making sure stderr is empty on valid cmd
|
||||||
`
|
cmd = exec.Command(dockerBinary, "inspect", "busybox")
|
||||||
if out != expected {
|
stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
|
||||||
c.Fatalf("Bad output from rename\nGot:%s\nExpected:%s", out, expected)
|
if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
|
||||||
|
c.Fatalf("Bad results from 'docker inspect busybox'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
||||||
|
}
|
||||||
|
// Be really pick
|
||||||
|
if strings.HasSuffix(stdout, "\n\n") {
|
||||||
|
c.Fatalf("Should not have a blank line at the end of 'docker inspect busyBox'\n%s", stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// docker rm: stdout=empty, stderr=all, rc!=0
|
||||||
|
// testing the min arg error msg
|
||||||
|
cmd = exec.Command(dockerBinary, "rm")
|
||||||
|
stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
|
||||||
|
if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
|
||||||
|
c.Fatalf("Bad results from 'docker rm'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
||||||
|
}
|
||||||
|
// Should not contain full help text but should contain info about
|
||||||
|
// # of args and Usage line
|
||||||
|
if !strings.Contains(stderr, "requires a minimum") {
|
||||||
|
c.Fatalf("Missing # of args text from 'docker rm'\nstderr:%s", stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// docker rm NoSuchContainer: stdout=empty, stderr=all, rc=0
|
||||||
|
// testing to make sure no blank line on error
|
||||||
|
cmd = exec.Command(dockerBinary, "rm", "NoSuchContainer")
|
||||||
|
stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
|
||||||
|
if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
|
||||||
|
c.Fatalf("Bad results from 'docker rm NoSuchContainer'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
||||||
|
}
|
||||||
|
// Be really picky
|
||||||
|
if strings.HasSuffix(stderr, "\n\n") {
|
||||||
|
c.Fatalf("Should not have a blank line at the end of 'docker rm'\n%s", stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// docker BadCmd: stdout=empty, stderr=all, rc=0
|
||||||
|
cmd = exec.Command(dockerBinary, "BadCmd")
|
||||||
|
stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
|
||||||
|
if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
|
||||||
|
c.Fatalf("Bad results from 'docker BadCmd'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
|
||||||
|
}
|
||||||
|
if stderr != "docker: 'BadCmd' is not a docker command.\nSee 'docker --help'.\n" {
|
||||||
|
c.Fatalf("Unexcepted output for 'docker badCmd'\nstderr:%s", stderr)
|
||||||
|
}
|
||||||
|
// Be really picky
|
||||||
|
if strings.HasSuffix(stderr, "\n\n") {
|
||||||
|
c.Fatalf("Should not have a blank line at the end of 'docker rm'\n%s", stderr)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,3 +58,62 @@ func (s *DockerSuite) TestKillDifferentUserContainer(c *check.C) {
|
|||||||
c.Fatal("killed container is still running")
|
c.Fatal("killed container is still running")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// regression test about correct signal parsing see #13665
|
||||||
|
func (s *DockerSuite) TestKillWithSignal(c *check.C) {
|
||||||
|
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
|
||||||
|
out, _, err := runCommandWithOutput(runCmd)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
cid := strings.TrimSpace(out)
|
||||||
|
c.Assert(waitRun(cid), check.IsNil)
|
||||||
|
|
||||||
|
killCmd := exec.Command(dockerBinary, "kill", "-s", "SIGWINCH", cid)
|
||||||
|
_, err = runCommand(killCmd)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
running, err := inspectField(cid, "State.Running")
|
||||||
|
if running != "true" {
|
||||||
|
c.Fatal("Container should be in running state after SIGWINCH")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestKillWithInvalidSignal(c *check.C) {
|
||||||
|
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
|
||||||
|
out, _, err := runCommandWithOutput(runCmd)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
cid := strings.TrimSpace(out)
|
||||||
|
c.Assert(waitRun(cid), check.IsNil)
|
||||||
|
|
||||||
|
killCmd := exec.Command(dockerBinary, "kill", "-s", "0", cid)
|
||||||
|
out, _, err = runCommandWithOutput(killCmd)
|
||||||
|
c.Assert(err, check.NotNil)
|
||||||
|
if !strings.ContainsAny(out, "Invalid signal: 0") {
|
||||||
|
c.Fatal("Kill with an invalid signal didn't error out correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
running, err := inspectField(cid, "State.Running")
|
||||||
|
if running != "true" {
|
||||||
|
c.Fatal("Container should be in running state after an invalid signal")
|
||||||
|
}
|
||||||
|
|
||||||
|
runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top")
|
||||||
|
out, _, err = runCommandWithOutput(runCmd)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
cid = strings.TrimSpace(out)
|
||||||
|
c.Assert(waitRun(cid), check.IsNil)
|
||||||
|
|
||||||
|
killCmd = exec.Command(dockerBinary, "kill", "-s", "SIG42", cid)
|
||||||
|
out, _, err = runCommandWithOutput(killCmd)
|
||||||
|
c.Assert(err, check.NotNil)
|
||||||
|
if !strings.ContainsAny(out, "Invalid signal: SIG42") {
|
||||||
|
c.Fatal("Kill with an invalid signal error out correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
running, err = inspectField(cid, "State.Running")
|
||||||
|
if running != "true" {
|
||||||
|
c.Fatal("Container should be in running state after an invalid signal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -9,15 +10,14 @@ import (
|
|||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startServerContainer(c *check.C, proto string, port int) string {
|
func startServerContainer(c *check.C, msg string, port int) string {
|
||||||
pStr := fmt.Sprintf("%d:%d", port, port)
|
|
||||||
bCmd := fmt.Sprintf("nc -lp %d && echo bye", port)
|
|
||||||
cmd := []string{"-d", "-p", pStr, "busybox", "sh", "-c", bCmd}
|
|
||||||
if proto == "udp" {
|
|
||||||
cmd = append(cmd, "-u")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := "server"
|
name := "server"
|
||||||
|
cmd := []string{
|
||||||
|
"-d",
|
||||||
|
"-p", fmt.Sprintf("%d:%d", port, port),
|
||||||
|
"busybox",
|
||||||
|
"sh", "-c", fmt.Sprintf("echo %q | nc -lp %d", msg, port),
|
||||||
|
}
|
||||||
if err := waitForContainer(name, cmd...); err != nil {
|
if err := waitForContainer(name, cmd...); err != nil {
|
||||||
c.Fatalf("Failed to launch server container: %v", err)
|
c.Fatalf("Failed to launch server container: %v", err)
|
||||||
}
|
}
|
||||||
@@ -60,52 +60,41 @@ func getContainerStatus(c *check.C, containerID string) string {
|
|||||||
|
|
||||||
func (s *DockerSuite) TestNetworkNat(c *check.C) {
|
func (s *DockerSuite) TestNetworkNat(c *check.C) {
|
||||||
testRequires(c, SameHostDaemon, NativeExecDriver)
|
testRequires(c, SameHostDaemon, NativeExecDriver)
|
||||||
|
msg := "it works"
|
||||||
srv := startServerContainer(c, "tcp", 8080)
|
startServerContainer(c, msg, 8080)
|
||||||
|
|
||||||
// Spawn a new container which connects to the server through the
|
|
||||||
// interface address.
|
|
||||||
endpoint := getExternalAddress(c)
|
endpoint := getExternalAddress(c)
|
||||||
runCmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", endpoint))
|
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", endpoint.String(), 8080))
|
||||||
if out, _, err := runCommandWithOutput(runCmd); err != nil {
|
if err != nil {
|
||||||
c.Fatalf("Failed to connect to server: %v (output: %q)", err, string(out))
|
c.Fatalf("Failed to connect to container (%v)", err)
|
||||||
}
|
}
|
||||||
|
data, err := ioutil.ReadAll(conn)
|
||||||
result := getContainerLogs(c, srv)
|
conn.Close()
|
||||||
|
if err != nil {
|
||||||
// Ideally we'd like to check for "hello world" but sometimes
|
c.Fatal(err)
|
||||||
// nc doesn't show the data it received so instead let's look for
|
}
|
||||||
// the output of the 'echo bye' that should be printed once
|
final := strings.TrimRight(string(data), "\n")
|
||||||
// the nc command gets a connection
|
if final != msg {
|
||||||
expected := "bye"
|
c.Fatalf("Expected message %q but received %q", msg, final)
|
||||||
if !strings.Contains(result, expected) {
|
|
||||||
c.Fatalf("Unexpected output. Expected: %q, received: %q", expected, result)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestNetworkLocalhostTCPNat(c *check.C) {
|
func (s *DockerSuite) TestNetworkLocalhostTCPNat(c *check.C) {
|
||||||
testRequires(c, SameHostDaemon, NativeExecDriver)
|
testRequires(c, SameHostDaemon, NativeExecDriver)
|
||||||
|
var (
|
||||||
srv := startServerContainer(c, "tcp", 8081)
|
msg = "hi yall"
|
||||||
|
)
|
||||||
// Attempt to connect from the host to the listening container.
|
startServerContainer(c, msg, 8081)
|
||||||
conn, err := net.Dial("tcp", "localhost:8081")
|
conn, err := net.Dial("tcp", "localhost:8081")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("Failed to connect to container (%v)", err)
|
c.Fatalf("Failed to connect to container (%v)", err)
|
||||||
}
|
}
|
||||||
if _, err := conn.Write([]byte("hello world\n")); err != nil {
|
data, err := ioutil.ReadAll(conn)
|
||||||
|
conn.Close()
|
||||||
|
if err != nil {
|
||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
conn.Close()
|
final := strings.TrimRight(string(data), "\n")
|
||||||
|
if final != msg {
|
||||||
result := getContainerLogs(c, srv)
|
c.Fatalf("Expected message %q but received %q", msg, final)
|
||||||
|
|
||||||
// Ideally we'd like to check for "hello world" but sometimes
|
|
||||||
// nc doesn't show the data it received so instead let's look for
|
|
||||||
// the output of the 'echo bye' that should be printed once
|
|
||||||
// the nc command gets a connection
|
|
||||||
expected := "bye"
|
|
||||||
if !strings.Contains(result, expected) {
|
|
||||||
c.Fatalf("Unexpected output. Expected: %q, received: %q", expected, result)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,55 @@ func (s *DockerSuite) TestRmiTag(c *check.C) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestRmiImgIDMultipleTag(c *check.C) {
|
||||||
|
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-one'")
|
||||||
|
out, _, err := runCommandWithOutput(runCmd)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("failed to create a container:%s, %v", out, err)
|
||||||
|
}
|
||||||
|
containerID := strings.TrimSpace(out)
|
||||||
|
runCmd = exec.Command(dockerBinary, "commit", containerID, "busybox-one")
|
||||||
|
out, _, err = runCommandWithOutput(runCmd)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("failed to commit a new busybox-one:%s, %v", out, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
imagesBefore, _ := dockerCmd(c, "images", "-a")
|
||||||
|
dockerCmd(c, "tag", "busybox-one", "busybox-one:tag1")
|
||||||
|
dockerCmd(c, "tag", "busybox-one", "busybox-one:tag2")
|
||||||
|
|
||||||
|
imagesAfter, _ := dockerCmd(c, "images", "-a")
|
||||||
|
if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+2 {
|
||||||
|
c.Fatalf("tag busybox to create 2 more images with same imageID; docker images shows: %q\n", imagesAfter)
|
||||||
|
}
|
||||||
|
|
||||||
|
imgID, err := inspectField("busybox-one:tag1", "Id")
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
// run a container with the image
|
||||||
|
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox-one", "top"))
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("failed to create a container:%s, %v", out, err)
|
||||||
|
}
|
||||||
|
containerID = strings.TrimSpace(out)
|
||||||
|
|
||||||
|
// first checkout without force it fails
|
||||||
|
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "rmi", imgID))
|
||||||
|
expected := fmt.Sprintf("Conflict, cannot delete %s because the running container %s is using it, stop it and use -f to force", imgID[:12], containerID[:12])
|
||||||
|
if err == nil || !strings.Contains(out, expected) {
|
||||||
|
c.Fatalf("rmi tagged in multiple repos should have failed without force: %s, %v, expected: %s", out, err, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerCmd(c, "stop", containerID)
|
||||||
|
dockerCmd(c, "rmi", "-f", imgID)
|
||||||
|
|
||||||
|
imagesAfter, _ = dockerCmd(c, "images", "-a")
|
||||||
|
if strings.Contains(imagesAfter, imgID[:12]) {
|
||||||
|
c.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestRmiImgIDForce(c *check.C) {
|
func (s *DockerSuite) TestRmiImgIDForce(c *check.C) {
|
||||||
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-test'")
|
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-test'")
|
||||||
out, _, err := runCommandWithOutput(runCmd)
|
out, _, err := runCommandWithOutput(runCmd)
|
||||||
@@ -119,7 +168,6 @@ func (s *DockerSuite) TestRmiImgIDForce(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TestRmiTagWithExistingContainers(c *check.C) {
|
func (s *DockerSuite) TestRmiTagWithExistingContainers(c *check.C) {
|
||||||
|
|
||||||
container := "test-delete-tag"
|
container := "test-delete-tag"
|
||||||
newtag := "busybox:newtag"
|
newtag := "busybox:newtag"
|
||||||
bb := "busybox:latest"
|
bb := "busybox:latest"
|
||||||
|
|||||||
@@ -3186,3 +3186,26 @@ func (s *DockerSuite) TestRunUnshareProc(c *check.C) {
|
|||||||
c.Fatalf("unshare should have failed with permission denied, got: %s, %v", out, err)
|
c.Fatalf("unshare should have failed with permission denied, got: %s, %v", out, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestRunPublishPort(c *check.C) {
|
||||||
|
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "test", "--expose", "8080", "busybox", "top"))
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "port", "test"))
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
out = strings.Trim(out, "\r\n")
|
||||||
|
if out != "" {
|
||||||
|
c.Fatalf("run without --publish-all should not publish port, out should be nil, but got: %s", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue #10184.
|
||||||
|
func (s *DockerSuite) TestDevicePermissions(c *check.C) {
|
||||||
|
const permissions = "crw-rw-rw-"
|
||||||
|
out, status := dockerCmd(c, "run", "--device", "/dev/fuse:/dev/fuse:mrw", "busybox:latest", "ls", "-l", "/dev/fuse")
|
||||||
|
if status != 0 {
|
||||||
|
c.Fatalf("expected status 0, got %d", status)
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(out, permissions) {
|
||||||
|
c.Fatalf("output should begin with %q, got %q", permissions, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func (s *DockerSuite) TestCliStatsNoStream(c *check.C) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("Error running stats: %v", err)
|
c.Fatalf("Error running stats: %v", err)
|
||||||
}
|
}
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(3 * time.Second):
|
||||||
statsCmd.Process.Kill()
|
statsCmd.Process.Kill()
|
||||||
c.Fatalf("stats did not return immediately when not streaming")
|
c.Fatalf("stats did not return immediately when not streaming")
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -60,10 +60,10 @@ func SortPortMap(ports []Port, bindings PortMap) {
|
|||||||
for _, b := range binding {
|
for _, b := range binding {
|
||||||
s = append(s, portMapEntry{port: p, binding: b})
|
s = append(s, portMapEntry{port: p, binding: b})
|
||||||
}
|
}
|
||||||
|
bindings[p] = []PortBinding{}
|
||||||
} else {
|
} else {
|
||||||
s = append(s, portMapEntry{port: p})
|
s = append(s, portMapEntry{port: p})
|
||||||
}
|
}
|
||||||
bindings[p] = []PortBinding{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(s)
|
sort.Sort(s)
|
||||||
@@ -79,9 +79,11 @@ func SortPortMap(ports []Port, bindings PortMap) {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
// reorder bindings for this port
|
// reorder bindings for this port
|
||||||
|
if _, ok := bindings[entry.port]; ok {
|
||||||
bindings[entry.port] = append(bindings[entry.port], entry.binding)
|
bindings[entry.port] = append(bindings[entry.port], entry.binding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toInt(s string) int64 {
|
func toInt(s string) int64 {
|
||||||
i, err := strconv.ParseInt(s, 10, 64)
|
i, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
|||||||
+1
-10
@@ -33,7 +33,7 @@ func MapVar(values map[string]string, names []string, usage string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LogOptsVar(values map[string]string, names []string, usage string) {
|
func LogOptsVar(values map[string]string, names []string, usage string) {
|
||||||
flag.Var(newMapOpt(values, ValidateLogOpts), names, usage)
|
flag.Var(newMapOpt(values, nil), names, usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HostListVar(values *[]string, names []string, usage string) {
|
func HostListVar(values *[]string, names []string, usage string) {
|
||||||
@@ -176,15 +176,6 @@ func newMapOpt(values map[string]string, validator ValidatorFctType) *MapOpts {
|
|||||||
type ValidatorFctType func(val string) (string, error)
|
type ValidatorFctType func(val string) (string, error)
|
||||||
type ValidatorFctListType func(val string) ([]string, error)
|
type ValidatorFctListType func(val string) ([]string, error)
|
||||||
|
|
||||||
func ValidateLogOpts(val string) (string, error) {
|
|
||||||
allowedKeys := map[string]string{}
|
|
||||||
vals := strings.Split(val, "=")
|
|
||||||
if allowedKeys[vals[0]] != "" {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("%s is not a valid log opt", vals[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateAttach(val string) (string, error) {
|
func ValidateAttach(val string) (string, error) {
|
||||||
s := strings.ToLower(val)
|
s := strings.ToLower(val)
|
||||||
for _, str := range []string{"stdin", "stdout", "stderr"} {
|
for _, str := range []string{"stdin", "stdout", "stderr"} {
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ioutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FprintfIfNotEmpty prints the string value if it's not empty
|
||||||
|
func FprintfIfNotEmpty(w io.Writer, format, value string) (int, error) {
|
||||||
|
if value != "" {
|
||||||
|
return fmt.Fprintf(w, format, value)
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ioutils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestFprintfIfNotEmpty(t *testing.T) {
|
||||||
|
wc := NewWriteCounter(&NopWriter{})
|
||||||
|
n, _ := FprintfIfNotEmpty(wc, "foo%s", "")
|
||||||
|
|
||||||
|
if wc.Count != 0 || n != 0 {
|
||||||
|
t.Errorf("Wrong count: %v vs. %v vs. 0", wc.Count, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, _ = FprintfIfNotEmpty(wc, "foo%s", "bar")
|
||||||
|
if wc.Count != 6 || n != 6 {
|
||||||
|
t.Errorf("Wrong count: %v vs. %v vs. 6", wc.Count, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-5
@@ -290,6 +290,7 @@ type FlagSet struct {
|
|||||||
// The field is a function (not a method) that may be changed to point to
|
// The field is a function (not a method) that may be changed to point to
|
||||||
// a custom error handler.
|
// a custom error handler.
|
||||||
Usage func()
|
Usage func()
|
||||||
|
ShortUsage func()
|
||||||
|
|
||||||
name string
|
name string
|
||||||
parsed bool
|
parsed bool
|
||||||
@@ -511,6 +512,12 @@ func (f *FlagSet) PrintDefaults() {
|
|||||||
if runtime.GOOS != "windows" && home == "/" {
|
if runtime.GOOS != "windows" && home == "/" {
|
||||||
home = ""
|
home = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a blank line between cmd description and list of options
|
||||||
|
if f.FlagCount() > 0 {
|
||||||
|
fmt.Fprintln(writer, "")
|
||||||
|
}
|
||||||
|
|
||||||
f.VisitAll(func(flag *Flag) {
|
f.VisitAll(func(flag *Flag) {
|
||||||
format := " -%s=%s"
|
format := " -%s=%s"
|
||||||
names := []string{}
|
names := []string{}
|
||||||
@@ -564,6 +571,12 @@ var Usage = func() {
|
|||||||
PrintDefaults()
|
PrintDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Usage prints to standard error a usage message documenting the standard command layout
|
||||||
|
// The function is a variable that may be changed to point to a custom function.
|
||||||
|
var ShortUsage = func() {
|
||||||
|
fmt.Fprintf(CommandLine.output, "Usage of %s:\n", os.Args[0])
|
||||||
|
}
|
||||||
|
|
||||||
// FlagCount returns the number of flags that have been defined.
|
// FlagCount returns the number of flags that have been defined.
|
||||||
func (f *FlagSet) FlagCount() int { return len(sortFlags(f.formal)) }
|
func (f *FlagSet) FlagCount() int { return len(sortFlags(f.formal)) }
|
||||||
|
|
||||||
@@ -1067,12 +1080,15 @@ func (cmd *FlagSet) ParseFlags(args []string, withHelp bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if help != nil && *help {
|
if help != nil && *help {
|
||||||
|
cmd.SetOutput(os.Stdout)
|
||||||
cmd.Usage()
|
cmd.Usage()
|
||||||
// just in case Usage does not exit
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
if str := cmd.CheckArgs(); str != "" {
|
if str := cmd.CheckArgs(); str != "" {
|
||||||
|
cmd.SetOutput(os.Stderr)
|
||||||
cmd.ReportError(str, withHelp)
|
cmd.ReportError(str, withHelp)
|
||||||
|
cmd.ShortUsage()
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1080,13 +1096,12 @@ func (cmd *FlagSet) ParseFlags(args []string, withHelp bool) error {
|
|||||||
func (cmd *FlagSet) ReportError(str string, withHelp bool) {
|
func (cmd *FlagSet) ReportError(str string, withHelp bool) {
|
||||||
if withHelp {
|
if withHelp {
|
||||||
if os.Args[0] == cmd.Name() {
|
if os.Args[0] == cmd.Name() {
|
||||||
str += ". See '" + os.Args[0] + " --help'"
|
str += ".\nSee '" + os.Args[0] + " --help'"
|
||||||
} else {
|
} else {
|
||||||
str += ". See '" + os.Args[0] + " " + cmd.Name() + " --help'"
|
str += ".\nSee '" + os.Args[0] + " " + cmd.Name() + " --help'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(cmd.Out(), "docker: %s\n", str)
|
fmt.Fprintf(cmd.Out(), "docker: %s.\n", str)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parsed reports whether f.Parse has been called.
|
// Parsed reports whether f.Parse has been called.
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret inter
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
remoteErr, err := ioutil.ReadAll(resp.Body)
|
remoteErr, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package urlutil
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
var validUrlPrefixes = []string{
|
|
||||||
"http://",
|
|
||||||
"https://",
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsURL returns true if the provided str is a valid URL by doing
|
|
||||||
// a simple change for the transport of the url.
|
|
||||||
func IsURL(str string) bool {
|
|
||||||
for _, prefix := range validUrlPrefixes {
|
|
||||||
if strings.HasPrefix(str, prefix) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -6,26 +6,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
validPrefixes = []string{
|
validPrefixes = map[string][]string{
|
||||||
"git://",
|
"url": {"http://", "https://"},
|
||||||
"github.com/",
|
"git": {"git://", "github.com/", "git@"},
|
||||||
"git@",
|
"transport": {"tcp://", "udp://", "unix://"},
|
||||||
}
|
}
|
||||||
|
|
||||||
urlPathWithFragmentSuffix = regexp.MustCompile(".git(?:#.+)?$")
|
urlPathWithFragmentSuffix = regexp.MustCompile(".git(?:#.+)?$")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsURL returns true if the provided str is an HTTP(S) URL.
|
||||||
|
func IsURL(str string) bool {
|
||||||
|
return checkURL(str, "url")
|
||||||
|
}
|
||||||
|
|
||||||
// IsGitURL returns true if the provided str is a git repository URL.
|
// IsGitURL returns true if the provided str is a git repository URL.
|
||||||
func IsGitURL(str string) bool {
|
func IsGitURL(str string) bool {
|
||||||
if IsURL(str) && urlPathWithFragmentSuffix.MatchString(str) {
|
if IsURL(str) && urlPathWithFragmentSuffix.MatchString(str) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, prefix := range validPrefixes {
|
return checkURL(str, "git")
|
||||||
if strings.HasPrefix(str, prefix) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsGitTransport returns true if the provided str is a git transport by inspecting
|
// IsGitTransport returns true if the provided str is a git transport by inspecting
|
||||||
@@ -33,3 +32,17 @@ func IsGitURL(str string) bool {
|
|||||||
func IsGitTransport(str string) bool {
|
func IsGitTransport(str string) bool {
|
||||||
return IsURL(str) || strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "git@")
|
return IsURL(str) || strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "git@")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsTransportURL returns true if the provided str is a transport (tcp, udp, unix) URL.
|
||||||
|
func IsTransportURL(str string) bool {
|
||||||
|
return checkURL(str, "transport")
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkURL(str, kind string) bool {
|
||||||
|
for _, prefix := range validPrefixes[kind] {
|
||||||
|
if strings.HasPrefix(str, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
+1
-1
@@ -5,7 +5,7 @@ the Docker project.
|
|||||||
|
|
||||||
### CI
|
### CI
|
||||||
|
|
||||||
The Docker project uses [Jenkins](https://jenkins.dockerproject.com/) as our
|
The Docker project uses [Jenkins](https://jenkins.dockerproject.org/) as our
|
||||||
continuous integration server. Each Pull Request to Docker is tested by running the
|
continuous integration server. Each Pull Request to Docker is tested by running the
|
||||||
equivalent of `make all`. We chose Jenkins because we can host it ourselves and
|
equivalent of `make all`. We chose Jenkins because we can host it ourselves and
|
||||||
we run Docker in Docker to test.
|
we run Docker in Docker to test.
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||||
|
|
||||||
|
IFS=$'\n'
|
||||||
|
files=( $(validate_diff --diff-filter=ACMR --name-only -- 'MAINTAINERS' || true) )
|
||||||
|
unset IFS
|
||||||
|
|
||||||
|
badFiles=()
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
# we use "git show" here to validate that what's committed is formatted
|
||||||
|
if [ "$(git show "$VALIDATE_HEAD:$f" | tomlv)" ]; then
|
||||||
|
badFiles+=( "$f" )
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#badFiles[@]} -eq 0 ]; then
|
||||||
|
echo 'Congratulations! All toml source files have valid syntax.'
|
||||||
|
else
|
||||||
|
{
|
||||||
|
echo "These files are not valid toml:"
|
||||||
|
for f in "${badFiles[@]}"; do
|
||||||
|
echo " - $f"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo 'Please reformat the above files as valid toml'
|
||||||
|
echo
|
||||||
|
} >&2
|
||||||
|
false
|
||||||
|
fi
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
@@ -56,7 +57,10 @@ func init() {
|
|||||||
dockerUserAgent = useragent.AppendVersions("", httpVersion...)
|
dockerUserAgent = useragent.AppendVersions("", httpVersion...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpsRequestModifier struct{ tlsConfig *tls.Config }
|
type httpsRequestModifier struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
// DRAGONS(tiborvass): If someone wonders why do we set tlsconfig in a roundtrip,
|
// DRAGONS(tiborvass): If someone wonders why do we set tlsconfig in a roundtrip,
|
||||||
// it's because it's so as to match the current behavior in master: we generate the
|
// it's because it's so as to match the current behavior in master: we generate the
|
||||||
@@ -125,8 +129,10 @@ func (m *httpsRequestModifier) ModifyRequest(req *http.Request) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m.mu.Lock()
|
||||||
m.tlsConfig.RootCAs = roots
|
m.tlsConfig.RootCAs = roots
|
||||||
m.tlsConfig.Certificates = certs
|
m.tlsConfig.Certificates = certs
|
||||||
|
m.mu.Unlock()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -175,7 +181,7 @@ func NewTransport(timeout TimeoutType, secure bool) http.RoundTripper {
|
|||||||
if secure {
|
if secure {
|
||||||
// note: httpsTransport also handles http transport
|
// note: httpsTransport also handles http transport
|
||||||
// but for HTTPS, it sets up the certs
|
// but for HTTPS, it sets up the certs
|
||||||
return transport.NewTransport(tr, &httpsRequestModifier{tlsConfig})
|
return transport.NewTransport(tr, &httpsRequestModifier{tlsConfig: tlsConfig})
|
||||||
}
|
}
|
||||||
|
|
||||||
return tr
|
return tr
|
||||||
|
|||||||
+12
-2
@@ -84,7 +84,13 @@ func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) {
|
|||||||
if req.Header.Get("Authorization") == "" {
|
if req.Header.Get("Authorization") == "" {
|
||||||
if req.Header.Get("X-Docker-Token") == "true" && len(tr.Username) > 0 {
|
if req.Header.Get("X-Docker-Token") == "true" && len(tr.Username) > 0 {
|
||||||
req.SetBasicAuth(tr.Username, tr.Password)
|
req.SetBasicAuth(tr.Username, tr.Password)
|
||||||
} else if len(tr.token) > 0 {
|
} else if len(tr.token) > 0 &&
|
||||||
|
// Authorization should not be set on 302 redirect for untrusted locations.
|
||||||
|
// This logic mirrors the behavior in AddRequiredHeadersToRedirectedRequests.
|
||||||
|
// As the authorization logic is currently implemented in RoundTrip,
|
||||||
|
// a 302 redirect is detected by looking at the Referer header as go http package adds said header.
|
||||||
|
// This is safe as Docker doesn't set Referer in other scenarios.
|
||||||
|
(req.Header.Get("Referer") == "" || trustedLocation(orig)) {
|
||||||
req.Header.Set("Authorization", "Token "+strings.Join(tr.token, ","))
|
req.Header.Set("Authorization", "Token "+strings.Join(tr.token, ","))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,7 +104,11 @@ func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
resp.Body = &transport.OnEOFReader{
|
resp.Body = &transport.OnEOFReader{
|
||||||
Rc: resp.Body,
|
Rc: resp.Body,
|
||||||
Fn: func() { delete(tr.modReq, orig) },
|
Fn: func() {
|
||||||
|
tr.mu.Lock()
|
||||||
|
delete(tr.modReq, orig)
|
||||||
|
tr.mu.Unlock()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-11
@@ -68,10 +68,15 @@ func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bo
|
|||||||
// 1.c) if anything else, err
|
// 1.c) if anything else, err
|
||||||
// 2) PUT the created/signed manifest
|
// 2) PUT the created/signed manifest
|
||||||
//
|
//
|
||||||
func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) ([]byte, string, error) {
|
|
||||||
|
// GetV2ImageManifest simply fetches the bytes of a manifest and the remote
|
||||||
|
// digest, if available in the request. Note that the application shouldn't
|
||||||
|
// rely on the untrusted remoteDigest, and should also verify against a
|
||||||
|
// locally provided digest, if applicable.
|
||||||
|
func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) (remoteDigest digest.Digest, p []byte, err error) {
|
||||||
routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
|
routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
method := "GET"
|
method := "GET"
|
||||||
@@ -79,31 +84,45 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au
|
|||||||
|
|
||||||
req, err := http.NewRequest(method, routeURL, nil)
|
req, err := http.NewRequest(method, routeURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return nil, "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := r.client.Do(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
if res.StatusCode == 401 {
|
if res.StatusCode == 401 {
|
||||||
return nil, "", errLoginRequired
|
return "", nil, errLoginRequired
|
||||||
} else if res.StatusCode == 404 {
|
} else if res.StatusCode == 404 {
|
||||||
return nil, "", ErrDoesNotExist
|
return "", nil, ErrDoesNotExist
|
||||||
}
|
}
|
||||||
return nil, "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res)
|
return "", nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestBytes, err := ioutil.ReadAll(res.Body)
|
p, err = ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("Error while reading the http response: %s", err)
|
return "", nil, fmt.Errorf("Error while reading the http response: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return manifestBytes, res.Header.Get(DockerDigestHeader), nil
|
dgstHdr := res.Header.Get(DockerDigestHeader)
|
||||||
|
if dgstHdr != "" {
|
||||||
|
remoteDigest, err = digest.ParseDigest(dgstHdr)
|
||||||
|
if err != nil {
|
||||||
|
// NOTE(stevvooe): Including the remote digest is optional. We
|
||||||
|
// don't need to verify against it, but it is good practice.
|
||||||
|
remoteDigest = ""
|
||||||
|
logrus.Debugf("error parsing remote digest when fetching %v: %v", routeURL, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// - Succeeded to head image blob (already exists)
|
// - Succeeded to head image blob (already exists)
|
||||||
|
|||||||
+6
-1
@@ -10,6 +10,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/symlink"
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,7 +70,11 @@ func checkoutGit(fragment, root string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
|
if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
|
||||||
newCtx := filepath.Join(root, refAndDir[1])
|
newCtx, err := symlink.FollowSymlinkInScope(filepath.Join(root, refAndDir[1]), root)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error setting git context, %q not within git root: %s", refAndDir[1], err)
|
||||||
|
}
|
||||||
|
|
||||||
fi, err := os.Stat(newCtx)
|
fi, err := os.Stat(newCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -103,6 +103,14 @@ func TestCheckoutGit(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = os.Symlink("../subdir", filepath.Join(gitDir, "parentlink")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.Symlink("/subdir", filepath.Join(gitDir, "absolutelink")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err = gitWithinDir(gitDir, "add", "-A"); err != nil {
|
if _, err = gitWithinDir(gitDir, "add", "-A"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -147,6 +155,9 @@ func TestCheckoutGit(t *testing.T) {
|
|||||||
{":Dockerfile", "", true}, // not a directory error
|
{":Dockerfile", "", true}, // not a directory error
|
||||||
{"master:nosubdir", "", true},
|
{"master:nosubdir", "", true},
|
||||||
{"master:subdir", "FROM scratch\nEXPOSE 5000", false},
|
{"master:subdir", "FROM scratch\nEXPOSE 5000", false},
|
||||||
|
{"master:parentlink", "FROM scratch\nEXPOSE 5000", false},
|
||||||
|
{"master:absolutelink", "FROM scratch\nEXPOSE 5000", false},
|
||||||
|
{"master:../subdir", "", true},
|
||||||
{"test", "FROM scratch\nEXPOSE 3000", false},
|
{"test", "FROM scratch\nEXPOSE 3000", false},
|
||||||
{"test:", "FROM scratch\nEXPOSE 3000", false},
|
{"test:", "FROM scratch\nEXPOSE 3000", false},
|
||||||
{"test:subdir", "FROM busybox\nEXPOSE 5000", false},
|
{"test:subdir", "FROM busybox\nEXPOSE 5000", false},
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package digest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
@@ -16,6 +15,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
// DigestTarSumV1EmptyTar is the digest for the empty tar file.
|
// DigestTarSumV1EmptyTar is the digest for the empty tar file.
|
||||||
DigestTarSumV1EmptyTar = "tarsum.v1+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
DigestTarSumV1EmptyTar = "tarsum.v1+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
|
||||||
// DigestSha256EmptyTar is the canonical sha256 digest of empty data
|
// DigestSha256EmptyTar is the canonical sha256 digest of empty data
|
||||||
DigestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
DigestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
)
|
)
|
||||||
@@ -39,7 +39,7 @@ const (
|
|||||||
type Digest string
|
type Digest string
|
||||||
|
|
||||||
// NewDigest returns a Digest from alg and a hash.Hash object.
|
// NewDigest returns a Digest from alg and a hash.Hash object.
|
||||||
func NewDigest(alg string, h hash.Hash) Digest {
|
func NewDigest(alg Algorithm, h hash.Hash) Digest {
|
||||||
return Digest(fmt.Sprintf("%s:%x", alg, h.Sum(nil)))
|
return Digest(fmt.Sprintf("%s:%x", alg, h.Sum(nil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,13 +72,13 @@ func ParseDigest(s string) (Digest, error) {
|
|||||||
|
|
||||||
// FromReader returns the most valid digest for the underlying content.
|
// FromReader returns the most valid digest for the underlying content.
|
||||||
func FromReader(rd io.Reader) (Digest, error) {
|
func FromReader(rd io.Reader) (Digest, error) {
|
||||||
h := sha256.New()
|
digester := Canonical.New()
|
||||||
|
|
||||||
if _, err := io.Copy(h, rd); err != nil {
|
if _, err := io.Copy(digester.Hash(), rd); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewDigest("sha256", h), nil
|
return digester.Digest(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromTarArchive produces a tarsum digest from reader rd.
|
// FromTarArchive produces a tarsum digest from reader rd.
|
||||||
@@ -131,8 +131,8 @@ func (d Digest) Validate() error {
|
|||||||
return ErrDigestInvalidFormat
|
return ErrDigestInvalidFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
switch s[:i] {
|
switch Algorithm(s[:i]) {
|
||||||
case "sha256", "sha384", "sha512":
|
case SHA256, SHA384, SHA512:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return ErrDigestUnsupported
|
return ErrDigestUnsupported
|
||||||
@@ -143,8 +143,8 @@ func (d Digest) Validate() error {
|
|||||||
|
|
||||||
// Algorithm returns the algorithm portion of the digest. This will panic if
|
// Algorithm returns the algorithm portion of the digest. This will panic if
|
||||||
// the underlying digest is not in a valid format.
|
// the underlying digest is not in a valid format.
|
||||||
func (d Digest) Algorithm() string {
|
func (d Digest) Algorithm() Algorithm {
|
||||||
return string(d[:d.sepIndex()])
|
return Algorithm(d[:d.sepIndex()])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hex returns the hex digest portion of the digest. This will panic if the
|
// Hex returns the hex digest portion of the digest. This will panic if the
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func TestParseDigest(t *testing.T) {
|
|||||||
for _, testcase := range []struct {
|
for _, testcase := range []struct {
|
||||||
input string
|
input string
|
||||||
err error
|
err error
|
||||||
algorithm string
|
algorithm Algorithm
|
||||||
hex string
|
hex string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|||||||
+82
-31
@@ -1,44 +1,95 @@
|
|||||||
package digest
|
package digest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto"
|
||||||
"hash"
|
"hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Digester calculates the digest of written data. It is functionally
|
// Algorithm identifies and implementation of a digester by an identifier.
|
||||||
// equivalent to hash.Hash but provides methods for returning the Digest type
|
// Note the that this defines both the hash algorithm used and the string
|
||||||
// rather than raw bytes.
|
// encoding.
|
||||||
type Digester struct {
|
type Algorithm string
|
||||||
alg string
|
|
||||||
|
// supported digest types
|
||||||
|
const (
|
||||||
|
SHA256 Algorithm = "sha256" // sha256 with hex encoding
|
||||||
|
SHA384 Algorithm = "sha384" // sha384 with hex encoding
|
||||||
|
SHA512 Algorithm = "sha512" // sha512 with hex encoding
|
||||||
|
TarsumV1SHA256 Algorithm = "tarsum+v1+sha256" // supported tarsum version, verification only
|
||||||
|
|
||||||
|
// Canonical is the primary digest algorithm used with the distribution
|
||||||
|
// project. Other digests may be used but this one is the primary storage
|
||||||
|
// digest.
|
||||||
|
Canonical = SHA256
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// TODO(stevvooe): Follow the pattern of the standard crypto package for
|
||||||
|
// registration of digests. Effectively, we are a registerable set and
|
||||||
|
// common symbol access.
|
||||||
|
|
||||||
|
// algorithms maps values to hash.Hash implementations. Other algorithms
|
||||||
|
// may be available but they cannot be calculated by the digest package.
|
||||||
|
algorithms = map[Algorithm]crypto.Hash{
|
||||||
|
SHA256: crypto.SHA256,
|
||||||
|
SHA384: crypto.SHA384,
|
||||||
|
SHA512: crypto.SHA512,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Available returns true if the digest type is available for use. If this
|
||||||
|
// returns false, New and Hash will return nil.
|
||||||
|
func (a Algorithm) Available() bool {
|
||||||
|
h, ok := algorithms[a]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// check availability of the hash, as well
|
||||||
|
return h.Available()
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new digester for the specified algorithm. If the algorithm
|
||||||
|
// does not have a digester implementation, nil will be returned. This can be
|
||||||
|
// checked by calling Available before calling New.
|
||||||
|
func (a Algorithm) New() Digester {
|
||||||
|
return &digester{
|
||||||
|
alg: a,
|
||||||
|
hash: a.Hash(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns a new hash as used by the algorithm. If not available, nil is
|
||||||
|
// returned. Make sure to check Available before calling.
|
||||||
|
func (a Algorithm) Hash() hash.Hash {
|
||||||
|
if !a.Available() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return algorithms[a].New()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stevvooe): Allow resolution of verifiers using the digest type and
|
||||||
|
// this registration system.
|
||||||
|
|
||||||
|
// Digester calculates the digest of written data. Writes should go directly
|
||||||
|
// to the return value of Hash, while calling Digest will return the current
|
||||||
|
// value of the digest.
|
||||||
|
type Digester interface {
|
||||||
|
Hash() hash.Hash // provides direct access to underlying hash instance.
|
||||||
|
Digest() Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
// digester provides a simple digester definition that embeds a hasher.
|
||||||
|
type digester struct {
|
||||||
|
alg Algorithm
|
||||||
hash hash.Hash
|
hash hash.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDigester create a new Digester with the given hashing algorithm and instance
|
func (d *digester) Hash() hash.Hash {
|
||||||
// of that algo's hasher.
|
return d.hash
|
||||||
func NewDigester(alg string, h hash.Hash) Digester {
|
|
||||||
return Digester{
|
|
||||||
alg: alg,
|
|
||||||
hash: h,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCanonicalDigester is a convenience function to create a new Digester with
|
func (d *digester) Digest() Digest {
|
||||||
// out default settings.
|
|
||||||
func NewCanonicalDigester() Digester {
|
|
||||||
return NewDigester("sha256", sha256.New())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write data to the digester. These writes cannot fail.
|
|
||||||
func (d *Digester) Write(p []byte) (n int, err error) {
|
|
||||||
return d.hash.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Digest returns the current digest for this digester.
|
|
||||||
func (d *Digester) Digest() Digest {
|
|
||||||
return NewDigest(d.alg, d.hash)
|
return NewDigest(d.alg, d.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the state of the digester.
|
|
||||||
func (d *Digester) Reset() {
|
|
||||||
d.hash.Reset()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// +build !noresumabledigest
|
||||||
|
|
||||||
|
package digest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stevvooe/resumable"
|
||||||
|
_ "github.com/stevvooe/resumable/sha256"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestResumableDetection just ensures that the resumable capability of a hash
|
||||||
|
// is exposed through the digester type, which is just a hash plus a Digest
|
||||||
|
// method.
|
||||||
|
func TestResumableDetection(t *testing.T) {
|
||||||
|
d := Canonical.New()
|
||||||
|
|
||||||
|
if _, ok := d.Hash().(resumable.Hash); !ok {
|
||||||
|
t.Fatalf("expected digester to implement resumable.Hash: %#v, %v", d, d.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package digest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrDigestNotFound is used when a matching digest
|
||||||
|
// could not be found in a set.
|
||||||
|
ErrDigestNotFound = errors.New("digest not found")
|
||||||
|
|
||||||
|
// ErrDigestAmbiguous is used when multiple digests
|
||||||
|
// are found in a set. None of the matching digests
|
||||||
|
// should be considered valid matches.
|
||||||
|
ErrDigestAmbiguous = errors.New("ambiguous digest string")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set is used to hold a unique set of digests which
|
||||||
|
// may be easily referenced by easily referenced by a string
|
||||||
|
// representation of the digest as well as short representation.
|
||||||
|
// The uniqueness of the short representation is based on other
|
||||||
|
// digests in the set. If digests are ommited from this set,
|
||||||
|
// collisions in a larger set may not be detected, therefore it
|
||||||
|
// is important to always do short representation lookups on
|
||||||
|
// the complete set of digests. To mitigate collisions, an
|
||||||
|
// appropriately long short code should be used.
|
||||||
|
type Set struct {
|
||||||
|
entries digestEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSet creates an empty set of digests
|
||||||
|
// which may have digests added.
|
||||||
|
func NewSet() *Set {
|
||||||
|
return &Set{
|
||||||
|
entries: digestEntries{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkShortMatch checks whether two digests match as either whole
|
||||||
|
// values or short values. This function does not test equality,
|
||||||
|
// rather whether the second value could match against the first
|
||||||
|
// value.
|
||||||
|
func checkShortMatch(alg Algorithm, hex, shortAlg, shortHex string) bool {
|
||||||
|
if len(hex) == len(shortHex) {
|
||||||
|
if hex != shortHex {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(shortAlg) > 0 && string(alg) != shortAlg {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if !strings.HasPrefix(hex, shortHex) {
|
||||||
|
return false
|
||||||
|
} else if len(shortAlg) > 0 && string(alg) != shortAlg {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup looks for a digest matching the given string representation.
|
||||||
|
// If no digests could be found ErrDigestNotFound will be returned
|
||||||
|
// with an empty digest value. If multiple matches are found
|
||||||
|
// ErrDigestAmbiguous will be returned with an empty digest value.
|
||||||
|
func (dst *Set) Lookup(d string) (Digest, error) {
|
||||||
|
if len(dst.entries) == 0 {
|
||||||
|
return "", ErrDigestNotFound
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
searchFunc func(int) bool
|
||||||
|
alg Algorithm
|
||||||
|
hex string
|
||||||
|
)
|
||||||
|
dgst, err := ParseDigest(d)
|
||||||
|
if err == ErrDigestInvalidFormat {
|
||||||
|
hex = d
|
||||||
|
searchFunc = func(i int) bool {
|
||||||
|
return dst.entries[i].val >= d
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hex = dgst.Hex()
|
||||||
|
alg = dgst.Algorithm()
|
||||||
|
searchFunc = func(i int) bool {
|
||||||
|
if dst.entries[i].val == hex {
|
||||||
|
return dst.entries[i].alg >= alg
|
||||||
|
}
|
||||||
|
return dst.entries[i].val >= hex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idx := sort.Search(len(dst.entries), searchFunc)
|
||||||
|
if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) {
|
||||||
|
return "", ErrDigestNotFound
|
||||||
|
}
|
||||||
|
if dst.entries[idx].alg == alg && dst.entries[idx].val == hex {
|
||||||
|
return dst.entries[idx].digest, nil
|
||||||
|
}
|
||||||
|
if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) {
|
||||||
|
return "", ErrDigestAmbiguous
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst.entries[idx].digest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the given digests to the set. An error will be returned
|
||||||
|
// if the given digest is invalid. If the digest already exists in the
|
||||||
|
// table, this operation will be a no-op.
|
||||||
|
func (dst *Set) Add(d Digest) error {
|
||||||
|
if err := d.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
|
||||||
|
searchFunc := func(i int) bool {
|
||||||
|
if dst.entries[i].val == entry.val {
|
||||||
|
return dst.entries[i].alg >= entry.alg
|
||||||
|
}
|
||||||
|
return dst.entries[i].val >= entry.val
|
||||||
|
}
|
||||||
|
idx := sort.Search(len(dst.entries), searchFunc)
|
||||||
|
if idx == len(dst.entries) {
|
||||||
|
dst.entries = append(dst.entries, entry)
|
||||||
|
return nil
|
||||||
|
} else if dst.entries[idx].digest == d {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := append(dst.entries, nil)
|
||||||
|
copy(entries[idx+1:], entries[idx:len(entries)-1])
|
||||||
|
entries[idx] = entry
|
||||||
|
dst.entries = entries
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShortCodeTable returns a map of Digest to unique short codes. The
|
||||||
|
// length represents the minimum value, the maximum length may be the
|
||||||
|
// entire value of digest if uniqueness cannot be achieved without the
|
||||||
|
// full value. This function will attempt to make short codes as short
|
||||||
|
// as possible to be unique.
|
||||||
|
func ShortCodeTable(dst *Set, length int) map[Digest]string {
|
||||||
|
m := make(map[Digest]string, len(dst.entries))
|
||||||
|
l := length
|
||||||
|
resetIdx := 0
|
||||||
|
for i := 0; i < len(dst.entries); i++ {
|
||||||
|
var short string
|
||||||
|
extended := true
|
||||||
|
for extended {
|
||||||
|
extended = false
|
||||||
|
if len(dst.entries[i].val) <= l {
|
||||||
|
short = dst.entries[i].digest.String()
|
||||||
|
} else {
|
||||||
|
short = dst.entries[i].val[:l]
|
||||||
|
for j := i + 1; j < len(dst.entries); j++ {
|
||||||
|
if checkShortMatch(dst.entries[j].alg, dst.entries[j].val, "", short) {
|
||||||
|
if j > resetIdx {
|
||||||
|
resetIdx = j
|
||||||
|
}
|
||||||
|
extended = true
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if extended {
|
||||||
|
l++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m[dst.entries[i].digest] = short
|
||||||
|
if i >= resetIdx {
|
||||||
|
l = length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
type digestEntry struct {
|
||||||
|
alg Algorithm
|
||||||
|
val string
|
||||||
|
digest Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
type digestEntries []*digestEntry
|
||||||
|
|
||||||
|
func (d digestEntries) Len() int {
|
||||||
|
return len(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d digestEntries) Less(i, j int) bool {
|
||||||
|
if d[i].val != d[j].val {
|
||||||
|
return d[i].val < d[j].val
|
||||||
|
}
|
||||||
|
return d[i].alg < d[j].alg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d digestEntries) Swap(i, j int) {
|
||||||
|
d[i], d[j] = d[j], d[i]
|
||||||
|
}
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
package digest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertEqualDigests(t *testing.T, d1, d2 Digest) {
|
||||||
|
if d1 != d2 {
|
||||||
|
t.Fatalf("Digests do not match:\n\tActual: %s\n\tExpected: %s", d1, d2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookup(t *testing.T) {
|
||||||
|
digests := []Digest{
|
||||||
|
"sha256:12345",
|
||||||
|
"sha256:1234",
|
||||||
|
"sha256:12346",
|
||||||
|
"sha256:54321",
|
||||||
|
"sha256:65431",
|
||||||
|
"sha256:64321",
|
||||||
|
"sha256:65421",
|
||||||
|
"sha256:65321",
|
||||||
|
}
|
||||||
|
|
||||||
|
dset := NewSet()
|
||||||
|
for i := range digests {
|
||||||
|
if err := dset.Add(digests[i]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dgst, err := dset.Lookup("54")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertEqualDigests(t, dgst, digests[3])
|
||||||
|
|
||||||
|
dgst, err = dset.Lookup("1234")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected ambiguous error looking up: 1234")
|
||||||
|
}
|
||||||
|
if err != ErrDigestAmbiguous {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dgst, err = dset.Lookup("9876")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected ambiguous error looking up: 9876")
|
||||||
|
}
|
||||||
|
if err != ErrDigestNotFound {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dgst, err = dset.Lookup("sha256:1234")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertEqualDigests(t, dgst, digests[1])
|
||||||
|
|
||||||
|
dgst, err = dset.Lookup("sha256:12345")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertEqualDigests(t, dgst, digests[0])
|
||||||
|
|
||||||
|
dgst, err = dset.Lookup("sha256:12346")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertEqualDigests(t, dgst, digests[2])
|
||||||
|
|
||||||
|
dgst, err = dset.Lookup("12346")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertEqualDigests(t, dgst, digests[2])
|
||||||
|
|
||||||
|
dgst, err = dset.Lookup("12345")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertEqualDigests(t, dgst, digests[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddDuplication(t *testing.T) {
|
||||||
|
digests := []Digest{
|
||||||
|
"sha256:1234",
|
||||||
|
"sha256:12345",
|
||||||
|
"sha256:12346",
|
||||||
|
"sha256:54321",
|
||||||
|
"sha256:65431",
|
||||||
|
"sha512:65431",
|
||||||
|
"sha512:65421",
|
||||||
|
"sha512:65321",
|
||||||
|
}
|
||||||
|
|
||||||
|
dset := NewSet()
|
||||||
|
for i := range digests {
|
||||||
|
if err := dset.Add(digests[i]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dset.entries) != 8 {
|
||||||
|
t.Fatal("Invalid dset size")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dset.Add(Digest("sha256:12345")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dset.entries) != 8 {
|
||||||
|
t.Fatal("Duplicate digest insert allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dset.Add(Digest("sha384:12345")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dset.entries) != 9 {
|
||||||
|
t.Fatal("Insert with different algorithm not allowed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEqualShort(t *testing.T, actual, expected string) {
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("Unexpected short value:\n\tExpected: %s\n\tActual: %s", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortCodeTable(t *testing.T) {
|
||||||
|
digests := []Digest{
|
||||||
|
"sha256:1234",
|
||||||
|
"sha256:12345",
|
||||||
|
"sha256:12346",
|
||||||
|
"sha256:54321",
|
||||||
|
"sha256:65431",
|
||||||
|
"sha256:64321",
|
||||||
|
"sha256:65421",
|
||||||
|
"sha256:65321",
|
||||||
|
}
|
||||||
|
|
||||||
|
dset := NewSet()
|
||||||
|
for i := range digests {
|
||||||
|
if err := dset.Add(digests[i]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dump := ShortCodeTable(dset, 2)
|
||||||
|
|
||||||
|
if len(dump) < len(digests) {
|
||||||
|
t.Fatalf("Error unexpected size: %d, expecting %d", len(dump), len(digests))
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqualShort(t, dump[digests[0]], "sha256:1234")
|
||||||
|
assertEqualShort(t, dump[digests[1]], "sha256:12345")
|
||||||
|
assertEqualShort(t, dump[digests[2]], "sha256:12346")
|
||||||
|
assertEqualShort(t, dump[digests[3]], "54")
|
||||||
|
assertEqualShort(t, dump[digests[4]], "6543")
|
||||||
|
assertEqualShort(t, dump[digests[5]], "64")
|
||||||
|
assertEqualShort(t, dump[digests[6]], "6542")
|
||||||
|
assertEqualShort(t, dump[digests[7]], "653")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDigests(count int) ([]Digest, error) {
|
||||||
|
r := rand.New(rand.NewSource(25823))
|
||||||
|
digests := make([]Digest, count)
|
||||||
|
for i := range digests {
|
||||||
|
h := sha256.New()
|
||||||
|
if err := binary.Write(h, binary.BigEndian, r.Int63()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
digests[i] = NewDigest("sha256", h)
|
||||||
|
}
|
||||||
|
return digests, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchAddNTable(b *testing.B, n int) {
|
||||||
|
digests, err := createDigests(n)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
|
||||||
|
for j := range digests {
|
||||||
|
if err = dset.Add(digests[j]); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchLookupNTable(b *testing.B, n int, shortLen int) {
|
||||||
|
digests, err := createDigests(n)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
|
||||||
|
for i := range digests {
|
||||||
|
if err := dset.Add(digests[i]); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shorts := make([]string, 0, n)
|
||||||
|
for _, short := range ShortCodeTable(dset, shortLen) {
|
||||||
|
shorts = append(shorts, short)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if _, err = dset.Lookup(shorts[i%n]); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchShortCodeNTable(b *testing.B, n int, shortLen int) {
|
||||||
|
digests, err := createDigests(n)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
|
||||||
|
for i := range digests {
|
||||||
|
if err := dset.Add(digests[i]); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ShortCodeTable(dset, shortLen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAdd10(b *testing.B) {
|
||||||
|
benchAddNTable(b, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAdd100(b *testing.B) {
|
||||||
|
benchAddNTable(b, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAdd1000(b *testing.B) {
|
||||||
|
benchAddNTable(b, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLookup10(b *testing.B) {
|
||||||
|
benchLookupNTable(b, 10, 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLookup100(b *testing.B) {
|
||||||
|
benchLookupNTable(b, 100, 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLookup1000(b *testing.B) {
|
||||||
|
benchLookupNTable(b, 1000, 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkShortCode10(b *testing.B) {
|
||||||
|
benchShortCodeNTable(b, 10, 12)
|
||||||
|
}
|
||||||
|
func BenchmarkShortCode100(b *testing.B) {
|
||||||
|
benchShortCodeNTable(b, 100, 12)
|
||||||
|
}
|
||||||
|
func BenchmarkShortCode1000(b *testing.B) {
|
||||||
|
benchShortCodeNTable(b, 1000, 12)
|
||||||
|
}
|
||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TarSumRegexp defines a reguler expression to match tarsum identifiers.
|
// TarSumRegexp defines a regular expression to match tarsum identifiers.
|
||||||
var TarsumRegexp = regexp.MustCompile("tarsum(?:.[a-z0-9]+)?\\+[a-zA-Z0-9]+:[A-Fa-f0-9]+")
|
var TarsumRegexp = regexp.MustCompile("tarsum(?:.[a-z0-9]+)?\\+[a-zA-Z0-9]+:[A-Fa-f0-9]+")
|
||||||
|
|
||||||
// TarsumRegexpCapturing defines a reguler expression to match tarsum identifiers with
|
// TarsumRegexpCapturing defines a regular expression to match tarsum identifiers with
|
||||||
// capture groups corresponding to each component.
|
// capture groups corresponding to each component.
|
||||||
var TarsumRegexpCapturing = regexp.MustCompile("(tarsum)(.([a-z0-9]+))?\\+([a-zA-Z0-9]+):([A-Fa-f0-9]+)")
|
var TarsumRegexpCapturing = regexp.MustCompile("(tarsum)(.([a-z0-9]+))?\\+([a-zA-Z0-9]+):([A-Fa-f0-9]+)")
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user