mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-22 14:48:26 +00:00
Compare commits
135 Commits
docs
...
v1.6.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
| 746e830230 | |||
| 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,22 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 1.6.0 (2015-04-07)
|
||||||
|
|
||||||
|
#### Builder
|
||||||
|
+ Building images from an image ID
|
||||||
|
+ `commit --change` to apply specified Dockerfile instructions while committing the image
|
||||||
|
+ `import --change` to apply specified Dockerfile instructions while importing the image
|
||||||
|
|
||||||
|
#### Client
|
||||||
|
+ Windows Support
|
||||||
|
|
||||||
|
#### Runtime
|
||||||
|
+ Container and image Labels
|
||||||
|
+ `--cgroup-parent` for specifying a parent cgroup to place container cgroup within
|
||||||
|
+ Logging drivers, `json-file` or `syslog`
|
||||||
|
+ Pulling images by ID
|
||||||
|
+ `--ulimit` to set the ulimit on a container
|
||||||
|
|
||||||
## 1.5.0 (2015-02-10)
|
## 1.5.0 (2015-02-10)
|
||||||
|
|
||||||
#### Builder
|
#### Builder
|
||||||
|
|||||||
+23
-27
@@ -769,18 +769,6 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
|||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
utils.ParseFlags(cmd, args, true)
|
utils.ParseFlags(cmd, args, true)
|
||||||
|
|
||||||
hijacked := make(chan io.Closer)
|
|
||||||
// Block the return until the chan gets closed
|
|
||||||
defer func() {
|
|
||||||
log.Debugf("CmdStart() returned, defer waiting for hijack to finish.")
|
|
||||||
if _, ok := <-hijacked; ok {
|
|
||||||
log.Errorf("Hijack did not finish (chan still open)")
|
|
||||||
}
|
|
||||||
if *openStdin || *attach {
|
|
||||||
cli.in.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if *attach || *openStdin {
|
if *attach || *openStdin {
|
||||||
if cmd.NArg() > 1 {
|
if cmd.NArg() > 1 {
|
||||||
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
||||||
@@ -816,26 +804,34 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
|||||||
v.Set("stdout", "1")
|
v.Set("stdout", "1")
|
||||||
v.Set("stderr", "1")
|
v.Set("stderr", "1")
|
||||||
|
|
||||||
|
hijacked := make(chan io.Closer)
|
||||||
|
// Block the return until the chan gets closed
|
||||||
|
defer func() {
|
||||||
|
log.Debugf("CmdStart() returned, defer waiting for hijack to finish.")
|
||||||
|
if _, ok := <-hijacked; ok {
|
||||||
|
log.Errorf("Hijack did not finish (chan still open)")
|
||||||
|
}
|
||||||
|
cli.in.Close()
|
||||||
|
}()
|
||||||
cErr = promise.Go(func() error {
|
cErr = promise.Go(func() error {
|
||||||
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil)
|
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil)
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
close(hijacked)
|
// Acknowledge the hijack before starting
|
||||||
|
select {
|
||||||
|
case closer := <-hijacked:
|
||||||
|
// Make sure that the hijack gets closed when returning (results
|
||||||
|
// in closing the hijack chan and freeing server's goroutines)
|
||||||
|
if closer != nil {
|
||||||
|
defer closer.Close()
|
||||||
|
}
|
||||||
|
case err := <-cErr:
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acknowledge the hijack before starting
|
|
||||||
select {
|
|
||||||
case closer := <-hijacked:
|
|
||||||
// Make sure that the hijack gets closed when returning (results
|
|
||||||
// in closing the hijack chan and freeing server's goroutines)
|
|
||||||
if closer != nil {
|
|
||||||
defer closer.Close()
|
|
||||||
}
|
|
||||||
case err := <-cErr:
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var encounteredError error
|
var encounteredError error
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false))
|
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false))
|
||||||
|
|||||||
@@ -312,7 +312,11 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
|||||||
var str string
|
var str string
|
||||||
str = ast.Value
|
str = ast.Value
|
||||||
if _, ok := replaceEnvAllowed[cmd]; ok {
|
if _, ok := replaceEnvAllowed[cmd]; ok {
|
||||||
str = b.replaceEnv(ast.Value)
|
var err error
|
||||||
|
str, err = ProcessWord(ast.Value, b.Config.Env)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
strList[i+l] = str
|
strList[i+l] = str
|
||||||
msgList[i] = ast.Value
|
msgList[i] = ast.Value
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
|||||||
if blankOK || len(word) > 0 {
|
if blankOK || len(word) > 0 {
|
||||||
words = append(words, word)
|
words = append(words, word)
|
||||||
|
|
||||||
// Look for = and if no there assume
|
// Look for = and if not there assume
|
||||||
// we're doing the old stuff and
|
// we're doing the old stuff and
|
||||||
// just read the rest of the line
|
// just read the rest of the line
|
||||||
if !strings.Contains(word, "=") {
|
if !strings.Contains(word, "=") {
|
||||||
@@ -107,12 +107,15 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
|||||||
quote = ch
|
quote = ch
|
||||||
blankOK = true
|
blankOK = true
|
||||||
phase = inQuote
|
phase = inQuote
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if ch == '\\' {
|
if ch == '\\' {
|
||||||
if pos+1 == len(rest) {
|
if pos+1 == len(rest) {
|
||||||
continue // just skip \ at end
|
continue // just skip \ at end
|
||||||
}
|
}
|
||||||
|
// If we're not quoted and we see a \, then always just
|
||||||
|
// add \ plus the char to the word, even if the char
|
||||||
|
// is a quote.
|
||||||
|
word += string(ch)
|
||||||
pos++
|
pos++
|
||||||
ch = rune(rest[pos])
|
ch = rune(rest[pos])
|
||||||
}
|
}
|
||||||
@@ -122,15 +125,17 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
|||||||
if phase == inQuote {
|
if phase == inQuote {
|
||||||
if ch == quote {
|
if ch == quote {
|
||||||
phase = inWord
|
phase = inWord
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if ch == '\\' {
|
// \ is special except for ' quotes - can't escape anything for '
|
||||||
|
if ch == '\\' && quote != '\'' {
|
||||||
if pos+1 == len(rest) {
|
if pos+1 == len(rest) {
|
||||||
phase = inWord
|
phase = inWord
|
||||||
continue // just skip \ at end
|
continue // just skip \ at end
|
||||||
}
|
}
|
||||||
pos++
|
pos++
|
||||||
ch = rune(rest[pos])
|
nextCh := rune(rest[pos])
|
||||||
|
word += string(ch)
|
||||||
|
ch = nextCh
|
||||||
}
|
}
|
||||||
word += string(ch)
|
word += string(ch)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -7,6 +7,14 @@ ENV name=value\ value2
|
|||||||
ENV name="value'quote space'value2"
|
ENV name="value'quote space'value2"
|
||||||
ENV name='value"double quote"value2'
|
ENV name='value"double quote"value2'
|
||||||
ENV name=value\ value2 name2=value2\ value3
|
ENV name=value\ value2 name2=value2\ value3
|
||||||
|
ENV name="a\"b"
|
||||||
|
ENV name="a\'b"
|
||||||
|
ENV name='a\'b'
|
||||||
|
ENV name='a\'b''
|
||||||
|
ENV name='a\"b'
|
||||||
|
ENV name="''"
|
||||||
|
# don't put anything after the next line - it must be the last line of the
|
||||||
|
# Dockerfile and it must end with \
|
||||||
ENV name=value \
|
ENV name=value \
|
||||||
name1=value1 \
|
name1=value1 \
|
||||||
name2="value2a \
|
name2="value2a \
|
||||||
|
|||||||
Vendored
+12
-6
@@ -2,9 +2,15 @@
|
|||||||
(env "name" "value")
|
(env "name" "value")
|
||||||
(env "name" "value")
|
(env "name" "value")
|
||||||
(env "name" "value" "name2" "value2")
|
(env "name" "value" "name2" "value2")
|
||||||
(env "name" "value value1")
|
(env "name" "\"value value1\"")
|
||||||
(env "name" "value value2")
|
(env "name" "value\\ value2")
|
||||||
(env "name" "value'quote space'value2")
|
(env "name" "\"value'quote space'value2\"")
|
||||||
(env "name" "value\"double quote\"value2")
|
(env "name" "'value\"double quote\"value2'")
|
||||||
(env "name" "value value2" "name2" "value2 value3")
|
(env "name" "value\\ value2" "name2" "value2\\ value3")
|
||||||
(env "name" "value" "name1" "value1" "name2" "value2a value2b" "name3" "value3an\"value3b\"" "name4" "value4a\\nvalue4b")
|
(env "name" "\"a\\\"b\"")
|
||||||
|
(env "name" "\"a\\'b\"")
|
||||||
|
(env "name" "'a\\'b'")
|
||||||
|
(env "name" "'a\\'b''")
|
||||||
|
(env "name" "'a\\\"b'")
|
||||||
|
(env "name" "\"''\"")
|
||||||
|
(env "name" "value" "name1" "value1" "name2" "\"value2a value2b\"" "name3" "\"value3a\\n\\\"value3b\\\"\"" "name4" "\"value4a\\\\nvalue4b\"")
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
// This will take a single word and an array of env variables and
|
||||||
|
// process all quotes (" and ') as well as $xxx and ${xxx} env variable
|
||||||
|
// tokens. Tries to mimic bash shell process.
|
||||||
|
// It doesn't support all flavors of ${xx:...} formats but new ones can
|
||||||
|
// be added by adding code to the "special ${} format processing" section
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type shellWord struct {
|
||||||
|
word string
|
||||||
|
envs []string
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessWord(word string, env []string) (string, error) {
|
||||||
|
sw := &shellWord{
|
||||||
|
word: word,
|
||||||
|
envs: env,
|
||||||
|
pos: 0,
|
||||||
|
}
|
||||||
|
return sw.process()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *shellWord) process() (string, error) {
|
||||||
|
return sw.processStopOn('\000')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the word, starting at 'pos', and stop when we get to the
|
||||||
|
// end of the word or the 'stopChar' character
|
||||||
|
func (sw *shellWord) processStopOn(stopChar rune) (string, error) {
|
||||||
|
var result string
|
||||||
|
var charFuncMapping = map[rune]func() (string, error){
|
||||||
|
'\'': sw.processSingleQuote,
|
||||||
|
'"': sw.processDoubleQuote,
|
||||||
|
'$': sw.processDollar,
|
||||||
|
}
|
||||||
|
|
||||||
|
for sw.pos < len(sw.word) {
|
||||||
|
ch := sw.peek()
|
||||||
|
if stopChar != '\000' && ch == stopChar {
|
||||||
|
sw.next()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if fn, ok := charFuncMapping[ch]; ok {
|
||||||
|
// Call special processing func for certain chars
|
||||||
|
tmp, err := fn()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result += tmp
|
||||||
|
} else {
|
||||||
|
// Not special, just add it to the result
|
||||||
|
ch = sw.next()
|
||||||
|
if ch == '\\' {
|
||||||
|
// '\' escapes, except end of line
|
||||||
|
ch = sw.next()
|
||||||
|
if ch == '\000' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += string(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *shellWord) peek() rune {
|
||||||
|
if sw.pos == len(sw.word) {
|
||||||
|
return '\000'
|
||||||
|
}
|
||||||
|
return rune(sw.word[sw.pos])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *shellWord) next() rune {
|
||||||
|
if sw.pos == len(sw.word) {
|
||||||
|
return '\000'
|
||||||
|
}
|
||||||
|
ch := rune(sw.word[sw.pos])
|
||||||
|
sw.pos++
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *shellWord) processSingleQuote() (string, error) {
|
||||||
|
// All chars between single quotes are taken as-is
|
||||||
|
// Note, you can't escape '
|
||||||
|
var result string
|
||||||
|
|
||||||
|
sw.next()
|
||||||
|
|
||||||
|
for {
|
||||||
|
ch := sw.next()
|
||||||
|
if ch == '\000' || ch == '\'' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result += string(ch)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *shellWord) processDoubleQuote() (string, error) {
|
||||||
|
// All chars up to the next " are taken as-is, even ', except any $ chars
|
||||||
|
// But you can escape " with a \
|
||||||
|
var result string
|
||||||
|
|
||||||
|
sw.next()
|
||||||
|
|
||||||
|
for sw.pos < len(sw.word) {
|
||||||
|
ch := sw.peek()
|
||||||
|
if ch == '"' {
|
||||||
|
sw.next()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ch == '$' {
|
||||||
|
tmp, err := sw.processDollar()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result += tmp
|
||||||
|
} else {
|
||||||
|
ch = sw.next()
|
||||||
|
if ch == '\\' {
|
||||||
|
chNext := sw.peek()
|
||||||
|
|
||||||
|
if chNext == '\000' {
|
||||||
|
// Ignore \ at end of word
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if chNext == '"' || chNext == '$' {
|
||||||
|
// \" and \$ can be escaped, all other \'s are left as-is
|
||||||
|
ch = sw.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += string(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *shellWord) processDollar() (string, error) {
|
||||||
|
sw.next()
|
||||||
|
ch := sw.peek()
|
||||||
|
if ch == '{' {
|
||||||
|
sw.next()
|
||||||
|
name := sw.processName()
|
||||||
|
ch = sw.peek()
|
||||||
|
if ch == '}' {
|
||||||
|
// Normal ${xx} case
|
||||||
|
sw.next()
|
||||||
|
return sw.getEnv(name), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("Unsupported ${} substitution: %s", sw.word)
|
||||||
|
} else {
|
||||||
|
// $xxx case
|
||||||
|
name := sw.processName()
|
||||||
|
if name == "" {
|
||||||
|
return "$", nil
|
||||||
|
}
|
||||||
|
return sw.getEnv(name), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *shellWord) processName() string {
|
||||||
|
// Read in a name (alphanumeric or _)
|
||||||
|
// If it starts with a numeric then just return $#
|
||||||
|
var name string
|
||||||
|
|
||||||
|
for sw.pos < len(sw.word) {
|
||||||
|
ch := sw.peek()
|
||||||
|
if len(name) == 0 && unicode.IsDigit(ch) {
|
||||||
|
ch = sw.next()
|
||||||
|
return string(ch)
|
||||||
|
}
|
||||||
|
if !unicode.IsLetter(ch) && !unicode.IsDigit(ch) && ch != '_' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ch = sw.next()
|
||||||
|
name += string(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *shellWord) getEnv(name string) string {
|
||||||
|
for _, env := range sw.envs {
|
||||||
|
i := strings.Index(env, "=")
|
||||||
|
if i < 0 {
|
||||||
|
if name == env {
|
||||||
|
// Should probably never get here, but just in case treat
|
||||||
|
// it like "var" and "var=" are the same
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name != env[:i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return env[i+1:]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShellParser(t *testing.T) {
|
||||||
|
file, err := os.Open("words")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Can't open 'words': %s", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
envs := []string{"PWD=/home", "SHELL=bash"}
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
// Trim comments and blank lines
|
||||||
|
i := strings.Index(line, "#")
|
||||||
|
if i >= 0 {
|
||||||
|
line = line[:i]
|
||||||
|
}
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
words := strings.Split(line, "|")
|
||||||
|
if len(words) != 2 {
|
||||||
|
t.Fatalf("Error in 'words' - should be 2 words:%q", words)
|
||||||
|
}
|
||||||
|
|
||||||
|
words[0] = strings.TrimSpace(words[0])
|
||||||
|
words[1] = strings.TrimSpace(words[1])
|
||||||
|
|
||||||
|
newWord, err := ProcessWord(words[0], envs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
newWord = "error"
|
||||||
|
}
|
||||||
|
|
||||||
|
if newWord != words[1] {
|
||||||
|
t.Fatalf("Error. Src: %s Calc: %s Expected: %s", words[0], newWord, words[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,50 +1,9 @@
|
|||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// `\\\\+|[^\\]|\b|\A` - match any number of "\\" (ie, properly-escaped backslashes), or a single non-backslash character, or a word boundary, or beginning-of-line
|
|
||||||
// `\$` - match literal $
|
|
||||||
// `[[:alnum:]_]+` - match things like `$SOME_VAR`
|
|
||||||
// `{[[:alnum:]_]+}` - match things like `${SOME_VAR}`
|
|
||||||
tokenEnvInterpolation = regexp.MustCompile(`(\\|\\\\+|[^\\]|\b|\A)\$([[:alnum:]_]+|{[[:alnum:]_]+})`)
|
|
||||||
// this intentionally punts on more exotic interpolations like ${SOME_VAR%suffix} and lets the shell handle those directly
|
|
||||||
)
|
|
||||||
|
|
||||||
// handle environment replacement. Used in dispatcher.
|
|
||||||
func (b *Builder) replaceEnv(str string) string {
|
|
||||||
for _, match := range tokenEnvInterpolation.FindAllString(str, -1) {
|
|
||||||
idx := strings.Index(match, "\\$")
|
|
||||||
if idx != -1 {
|
|
||||||
if idx+2 >= len(match) {
|
|
||||||
str = strings.Replace(str, match, "\\$", -1)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := match[:idx]
|
|
||||||
stripped := match[idx+2:]
|
|
||||||
str = strings.Replace(str, match, prefix+"$"+stripped, -1)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
match = match[strings.Index(match, "$"):]
|
|
||||||
matchKey := strings.Trim(match, "${}")
|
|
||||||
|
|
||||||
for _, keyval := range b.Config.Env {
|
|
||||||
tmp := strings.SplitN(keyval, "=", 2)
|
|
||||||
if tmp[0] == matchKey {
|
|
||||||
str = strings.Replace(str, match, tmp[1], -1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleJsonArgs(args []string, attributes map[string]bool) []string {
|
func handleJsonArgs(args []string, attributes map[string]bool) []string {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return []string{}
|
return []string{}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
hello | hello
|
||||||
|
he'll'o | hello
|
||||||
|
he'llo | hello
|
||||||
|
he\'llo | he'llo
|
||||||
|
he\\'llo | he\llo
|
||||||
|
abc\tdef | abctdef
|
||||||
|
"abc\tdef" | abc\tdef
|
||||||
|
'abc\tdef' | abc\tdef
|
||||||
|
hello\ | hello
|
||||||
|
hello\\ | hello\
|
||||||
|
"hello | hello
|
||||||
|
"hello\" | hello"
|
||||||
|
"hel'lo" | hel'lo
|
||||||
|
'hello | hello
|
||||||
|
'hello\' | hello\
|
||||||
|
"''" | ''
|
||||||
|
$. | $.
|
||||||
|
$1 |
|
||||||
|
he$1x | hex
|
||||||
|
he$.x | he$.x
|
||||||
|
he$pwd. | he.
|
||||||
|
he$PWD | he/home
|
||||||
|
he\$PWD | he$PWD
|
||||||
|
he\\$PWD | he\/home
|
||||||
|
he\${} | he${}
|
||||||
|
he\${}xx | he${}xx
|
||||||
|
he${} | he
|
||||||
|
he${}xx | hexx
|
||||||
|
he${hi} | he
|
||||||
|
he${hi}xx | hexx
|
||||||
|
he${PWD} | he/home
|
||||||
|
he${.} | error
|
||||||
|
'he${XX}' | he${XX}
|
||||||
|
"he${PWD}" | he/home
|
||||||
|
"he'$PWD'" | he'/home'
|
||||||
|
"$PWD" | /home
|
||||||
|
'$PWD' | $PWD
|
||||||
|
'\$PWD' | \$PWD
|
||||||
|
'"hello"' | "hello"
|
||||||
|
he\$PWD | he$PWD
|
||||||
|
"he\$PWD" | he$PWD
|
||||||
|
'he\$PWD' | he\$PWD
|
||||||
|
he${PWD | error
|
||||||
+1
-1
@@ -83,7 +83,7 @@ func (config *Config) InstallFlags() {
|
|||||||
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
|
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
|
||||||
config.Ulimits = make(map[string]*ulimit.Ulimit)
|
config.Ulimits = make(map[string]*ulimit.Ulimit)
|
||||||
opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
|
opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
|
||||||
flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver(json-file/none)")
|
flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultNetworkMtu() int {
|
func getDefaultNetworkMtu() int {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
"github.com/docker/docker/daemon/logger"
|
"github.com/docker/docker/daemon/logger"
|
||||||
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
||||||
|
"github.com/docker/docker/daemon/logger/syslog"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/links"
|
"github.com/docker/docker/links"
|
||||||
@@ -1380,6 +1381,12 @@ func (container *Container) startLogging() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
l = dl
|
l = dl
|
||||||
|
case "syslog":
|
||||||
|
dl, err := syslog.New(container.ID[:12])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l = dl
|
||||||
case "none":
|
case "none":
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package syslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/syslog"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/docker/daemon/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Syslog struct {
|
||||||
|
writer *syslog.Writer
|
||||||
|
tag string
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(tag string) (logger.Logger, error) {
|
||||||
|
log, err := syslog.New(syslog.LOG_USER, path.Base(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Syslog{
|
||||||
|
writer: log,
|
||||||
|
tag: tag,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Syslog) Log(msg *logger.Message) error {
|
||||||
|
logMessage := fmt.Sprintf("%s: %s", s.tag, string(msg.Line))
|
||||||
|
if msg.Source == "stderr" {
|
||||||
|
if err := s.writer.Err(logMessage); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if err := s.writer.Info(logMessage); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Syslog) Close() error {
|
||||||
|
if s.writer != nil {
|
||||||
|
return s.writer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Syslog) Name() string {
|
||||||
|
return "Syslog"
|
||||||
|
}
|
||||||
@@ -70,10 +70,11 @@ func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
|
const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
|
||||||
|
portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", beginPortRange, endPortRange)
|
||||||
|
|
||||||
file, err := os.Open(portRangeKernelParam)
|
file, err := os.Open(portRangeKernelParam)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to read %s kernel parameter: %v", portRangeKernelParam, err)
|
log.Warnf("port allocator - %s due to error: %v", portRangeFallback, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var start, end int
|
var start, end int
|
||||||
@@ -82,7 +83,7 @@ func init() {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
|
err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
|
||||||
}
|
}
|
||||||
log.Errorf("Failed to parse port range from %s: %v", portRangeKernelParam, err)
|
log.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
beginPortRange = start
|
beginPortRange = start
|
||||||
|
|||||||
+3
-1
@@ -24,6 +24,7 @@ type Mount struct {
|
|||||||
Writable bool
|
Writable bool
|
||||||
copyData bool
|
copyData bool
|
||||||
from *Container
|
from *Container
|
||||||
|
isBind bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mnt *Mount) Export(resource string) (io.ReadCloser, error) {
|
func (mnt *Mount) Export(resource string) (io.ReadCloser, error) {
|
||||||
@@ -79,7 +80,7 @@ func (m *Mount) initialize() error {
|
|||||||
if hostPath, exists := m.container.Volumes[m.MountToPath]; exists {
|
if hostPath, exists := m.container.Volumes[m.MountToPath]; exists {
|
||||||
// If this is a bind-mount/volumes-from, maybe it was passed in at start instead of create
|
// If this is a bind-mount/volumes-from, maybe it was passed in at start instead of create
|
||||||
// We need to make sure bind-mounts/volumes-from passed on start can override existing ones.
|
// We need to make sure bind-mounts/volumes-from passed on start can override existing ones.
|
||||||
if !m.volume.IsBindMount && m.from == nil {
|
if (!m.volume.IsBindMount && !m.isBind) && m.from == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if m.volume.Path == hostPath {
|
if m.volume.Path == hostPath {
|
||||||
@@ -172,6 +173,7 @@ func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error)
|
|||||||
volume: vol,
|
volume: vol,
|
||||||
MountToPath: mountToPath,
|
MountToPath: mountToPath,
|
||||||
Writable: writable,
|
Writable: writable,
|
||||||
|
isBind: true, // in case the volume itself is a normal volume, but is being mounted in as a bindmount here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ IMAGE [COMMAND] [ARG...]
|
|||||||
**--lxc-conf**=[]
|
**--lxc-conf**=[]
|
||||||
(lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
(lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
||||||
|
|
||||||
**--log-driver**="|*json-file*|*none*"
|
**--log-driver**="|*json-file*|*syslog*|*none*"
|
||||||
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.
|
||||||
|
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ which interface and port to use.
|
|||||||
**--lxc-conf**=[]
|
**--lxc-conf**=[]
|
||||||
(lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
(lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
||||||
|
|
||||||
**--log-driver**="|*json-file*|*none*"
|
**--log-driver**="|*json-file*|*syslog*|*none*"
|
||||||
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.
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ unix://[/path/to/socket] to use.
|
|||||||
**--label**="[]"
|
**--label**="[]"
|
||||||
Set key=value labels to the daemon (displayed in `docker info`)
|
Set key=value labels to the daemon (displayed in `docker info`)
|
||||||
|
|
||||||
**--log-driver**="*json-file*|*none*"
|
**--log-driver**="*json-file*|*syslog*|*none*"
|
||||||
Container's logging driver. Default is `default`.
|
Container's logging driver. Default is `default`.
|
||||||
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ Json Parameters:
|
|||||||
`Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
|
`Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
|
||||||
- **LogConfig** - Logging configuration to container, format
|
- **LogConfig** - Logging configuration to container, format
|
||||||
`{ "Type": "<driver_name>", "Config": {"key1": "val1"}}
|
`{ "Type": "<driver_name>", "Config": {"key1": "val1"}}
|
||||||
Available types: `json-file`, `none`.
|
Available types: `json-file`, `syslog`, `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 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.
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,17 @@ The instructions that handle environment variables in the `Dockerfile` are:
|
|||||||
`ONBUILD` instructions are **NOT** supported for environment replacement, even
|
`ONBUILD` instructions are **NOT** supported for environment replacement, even
|
||||||
the instructions above.
|
the instructions above.
|
||||||
|
|
||||||
|
Environment variable subtitution will use the same value for each variable
|
||||||
|
throughout the entire command. In other words, in this example:
|
||||||
|
|
||||||
|
ENV abc=hello
|
||||||
|
ENV abc=bye def=$abc
|
||||||
|
ENV ghi=$abc
|
||||||
|
|
||||||
|
will result in `def` having a value of `hello`, not `bye`. However,
|
||||||
|
`ghi` will have a value of `bye` because it is not part of the same command
|
||||||
|
that set `abc` to `bye`.
|
||||||
|
|
||||||
## The `.dockerignore` file
|
## The `.dockerignore` file
|
||||||
|
|
||||||
If a file named `.dockerignore` exists in the source repository, then it
|
If a file named `.dockerignore` exists in the source repository, then it
|
||||||
|
|||||||
@@ -657,6 +657,11 @@ 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
|
||||||
|
|
||||||
|
## Logging driver: syslog
|
||||||
|
|
||||||
|
Syslog logging driver for Docker. Writes log messages to syslog. `docker logs`
|
||||||
|
command is not available for this logging driver
|
||||||
|
|
||||||
## Overriding Dockerfile image defaults
|
## Overriding Dockerfile image defaults
|
||||||
|
|
||||||
When a developer builds an image from a [*Dockerfile*](/reference/builder)
|
When a developer builds an image from a [*Dockerfile*](/reference/builder)
|
||||||
|
|||||||
+1
-1
@@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution
|
|||||||
mkdir -p src/github.com/docker/distribution
|
mkdir -p src/github.com/docker/distribution
|
||||||
mv tmp-digest src/github.com/docker/distribution/digest
|
mv tmp-digest src/github.com/docker/distribution/digest
|
||||||
|
|
||||||
clone git github.com/docker/libcontainer 4a72e540feb67091156b907c4700e580a99f5a9d
|
clone git github.com/docker/libcontainer fd0087d3acdc4c5865de1829d4accee5e3ebb658
|
||||||
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
|
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
|
||||||
rm -rf src/github.com/docker/libcontainer/vendor
|
rm -rf src/github.com/docker/libcontainer/vendor
|
||||||
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')"
|
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')"
|
||||||
|
|||||||
@@ -516,3 +516,40 @@ func TestBuildApiDockerfileSymlink(t *testing.T) {
|
|||||||
|
|
||||||
logDone("container REST API - check build w/bad Dockerfile symlink path")
|
logDone("container REST API - check build w/bad Dockerfile symlink path")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #9981 - Allow a docker created volume (ie, one in /var/lib/docker/volumes) to be used to overwrite (via passing in Binds on api start) an existing volume
|
||||||
|
func TestPostContainerBindNormalVolume(t *testing.T) {
|
||||||
|
defer deleteAllContainers()
|
||||||
|
|
||||||
|
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=one", "busybox"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fooDir, err := inspectFieldMap("one", "Volumes", "/foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=two", "busybox"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}}
|
||||||
|
_, err = sockRequest("POST", "/containers/two/start", bindSpec)
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fooDir2, err := inspectFieldMap("two", "Volumes", "/foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fooDir2 != fooDir {
|
||||||
|
t.Fatal("expected volume path to be %s, got: %s", fooDir, fooDir2)
|
||||||
|
}
|
||||||
|
|
||||||
|
logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume")
|
||||||
|
}
|
||||||
|
|||||||
@@ -241,9 +241,18 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) {
|
|||||||
|
|
||||||
_, err := buildImage(name,
|
_, err := buildImage(name,
|
||||||
`
|
`
|
||||||
FROM scratch
|
FROM busybox
|
||||||
ENV foo foo
|
ENV foo zzz
|
||||||
ENV bar ${foo}
|
ENV bar ${foo}
|
||||||
|
ENV abc1='$foo'
|
||||||
|
ENV env1=$foo env2=${foo} env3="$foo" env4="${foo}"
|
||||||
|
RUN [ "$abc1" = '$foo' ] && (echo "$abc1" | grep -q foo)
|
||||||
|
ENV abc2="\$foo"
|
||||||
|
RUN [ "$abc2" = '$foo' ] && (echo "$abc2" | grep -q foo)
|
||||||
|
ENV abc3 '$foo'
|
||||||
|
RUN [ "$abc3" = '$foo' ] && (echo "$abc3" | grep -q foo)
|
||||||
|
ENV abc4 "\$foo"
|
||||||
|
RUN [ "$abc4" = '$foo' ] && (echo "$abc4" | grep -q foo)
|
||||||
`, true)
|
`, true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -262,13 +271,19 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
envCount := 0
|
||||||
|
|
||||||
for _, env := range envResult {
|
for _, env := range envResult {
|
||||||
parts := strings.SplitN(env, "=", 2)
|
parts := strings.SplitN(env, "=", 2)
|
||||||
if parts[0] == "bar" {
|
if parts[0] == "bar" {
|
||||||
found = true
|
found = true
|
||||||
if parts[1] != "foo" {
|
if parts[1] != "zzz" {
|
||||||
t.Fatalf("Could not find replaced var for env `bar`: got %q instead of `foo`", parts[1])
|
t.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1])
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(parts[0], "env") {
|
||||||
|
envCount++
|
||||||
|
if parts[1] != "zzz" {
|
||||||
|
t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,6 +292,10 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) {
|
|||||||
t.Fatal("Never found the `bar` env variable")
|
t.Fatal("Never found the `bar` env variable")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if envCount != 4 {
|
||||||
|
t.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult)
|
||||||
|
}
|
||||||
|
|
||||||
logDone("build - env environment replacement")
|
logDone("build - env environment replacement")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,8 +382,8 @@ func TestBuildHandleEscapes(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := result[`\\\\\\${FOO}`]; !ok {
|
if _, ok := result[`\\\${FOO}`]; !ok {
|
||||||
t.Fatal(`Could not find volume \\\\\\${FOO} set from env foo in volumes table`)
|
t.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
logDone("build - handle escapes")
|
logDone("build - handle escapes")
|
||||||
@@ -2256,7 +2275,7 @@ func TestBuildRelativeWorkdir(t *testing.T) {
|
|||||||
|
|
||||||
func TestBuildWorkdirWithEnvVariables(t *testing.T) {
|
func TestBuildWorkdirWithEnvVariables(t *testing.T) {
|
||||||
name := "testbuildworkdirwithenvvariables"
|
name := "testbuildworkdirwithenvvariables"
|
||||||
expected := "/test1/test2/$MISSING_VAR"
|
expected := "/test1/test2"
|
||||||
defer deleteImages(name)
|
defer deleteImages(name)
|
||||||
_, err := buildImage(name,
|
_, err := buildImage(name,
|
||||||
`FROM busybox
|
`FROM busybox
|
||||||
@@ -4025,9 +4044,9 @@ ENV abc=zzz TO=/docker/world/hello
|
|||||||
ADD $FROM $TO
|
ADD $FROM $TO
|
||||||
RUN [ "$(cat $TO)" = "hello" ]
|
RUN [ "$(cat $TO)" = "hello" ]
|
||||||
ENV abc "zzz"
|
ENV abc "zzz"
|
||||||
RUN [ $abc = \"zzz\" ]
|
RUN [ $abc = "zzz" ]
|
||||||
ENV abc 'yyy'
|
ENV abc 'yyy'
|
||||||
RUN [ $abc = \'yyy\' ]
|
RUN [ $abc = 'yyy' ]
|
||||||
ENV abc=
|
ENV abc=
|
||||||
RUN [ "$abc" = "" ]
|
RUN [ "$abc" = "" ]
|
||||||
|
|
||||||
@@ -4043,13 +4062,34 @@ RUN [ "$abc" = "'foo'" ]
|
|||||||
ENV abc=\"foo\"
|
ENV abc=\"foo\"
|
||||||
RUN [ "$abc" = "\"foo\"" ]
|
RUN [ "$abc" = "\"foo\"" ]
|
||||||
ENV abc "foo"
|
ENV abc "foo"
|
||||||
RUN [ "$abc" = "\"foo\"" ]
|
RUN [ "$abc" = "foo" ]
|
||||||
ENV abc 'foo'
|
ENV abc 'foo'
|
||||||
RUN [ "$abc" = "'foo'" ]
|
RUN [ "$abc" = 'foo' ]
|
||||||
ENV abc \'foo\'
|
ENV abc \'foo\'
|
||||||
RUN [ "$abc" = "\\'foo\\'" ]
|
RUN [ "$abc" = "'foo'" ]
|
||||||
ENV abc \"foo\"
|
ENV abc \"foo\"
|
||||||
RUN [ "$abc" = "\\\"foo\\\"" ]
|
RUN [ "$abc" = '"foo"' ]
|
||||||
|
|
||||||
|
ENV e1=bar
|
||||||
|
ENV e2=$e1
|
||||||
|
ENV e3=$e11
|
||||||
|
ENV e4=\$e1
|
||||||
|
ENV e5=\$e11
|
||||||
|
RUN [ "$e0,$e1,$e2,$e3,$e4,$e5" = ',bar,bar,,$e1,$e11' ]
|
||||||
|
|
||||||
|
ENV ee1 bar
|
||||||
|
ENV ee2 $ee1
|
||||||
|
ENV ee3 $ee11
|
||||||
|
ENV ee4 \$ee1
|
||||||
|
ENV ee5 \$ee11
|
||||||
|
RUN [ "$ee1,$ee2,$ee3,$ee4,$ee5" = 'bar,bar,,$ee1,$ee11' ]
|
||||||
|
|
||||||
|
ENV eee1="foo"
|
||||||
|
ENV eee2='foo'
|
||||||
|
ENV eee3 "foo"
|
||||||
|
ENV eee4 'foo'
|
||||||
|
RUN [ "$eee1,$eee2,$eee3,$eee4" = 'foo,foo,foo,foo' ]
|
||||||
|
|
||||||
`
|
`
|
||||||
ctx, err := fakeContext(dockerfile, map[string]string{
|
ctx, err := fakeContext(dockerfile, map[string]string{
|
||||||
"hello/docker/world": "hello",
|
"hello/docker/world": "hello",
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ func TestPullImageWithAliases(t *testing.T) {
|
|||||||
|
|
||||||
// pulling library/hello-world should show verified message
|
// pulling library/hello-world should show verified message
|
||||||
func TestPullVerified(t *testing.T) {
|
func TestPullVerified(t *testing.T) {
|
||||||
t.Skip("problems verifying library/hello-world (to be fixed)")
|
|
||||||
|
|
||||||
// Image must be pulled from central repository to get verified message
|
// Image must be pulled from central repository to get verified message
|
||||||
// unless keychain is manually updated to contain the daemon's sign key.
|
// unless keychain is manually updated to contain the daemon's sign key.
|
||||||
|
|
||||||
|
|||||||
@@ -240,3 +240,49 @@ func TestStartMultipleContainers(t *testing.T) {
|
|||||||
|
|
||||||
logDone("start - start multiple containers continue on one failed")
|
logDone("start - start multiple containers continue on one failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStartAttachMultipleContainers(t *testing.T) {
|
||||||
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
|
||||||
|
defer deleteAllContainers()
|
||||||
|
// run multiple containers to test
|
||||||
|
for _, container := range []string{"test1", "test2", "test3"} {
|
||||||
|
cmd = exec.Command(dockerBinary, "run", "-d", "--name", container, "busybox", "top")
|
||||||
|
if out, _, err := runCommandWithOutput(cmd); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop all the containers
|
||||||
|
for _, container := range []string{"test1", "test2", "test3"} {
|
||||||
|
cmd = exec.Command(dockerBinary, "stop", container)
|
||||||
|
if out, _, err := runCommandWithOutput(cmd); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test start and attach multiple containers at once, expected error
|
||||||
|
for _, option := range []string{"-a", "-i", "-ai"} {
|
||||||
|
cmd = exec.Command(dockerBinary, "start", option, "test1", "test2", "test3")
|
||||||
|
out, _, err := runCommandWithOutput(cmd)
|
||||||
|
if !strings.Contains(out, "You cannot start and attach multiple containers at once.") || err == nil {
|
||||||
|
t.Fatal("Expected error but got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm the state of all the containers be stopped
|
||||||
|
for container, expected := range map[string]string{"test1": "false", "test2": "false", "test3": "false"} {
|
||||||
|
cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", container)
|
||||||
|
out, _, err := runCommandWithOutput(cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
out = strings.Trim(out, "\r\n")
|
||||||
|
if out != expected {
|
||||||
|
t.Fatal("Container running state wrong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logDone("start - error on start and attach multiple containers at once")
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ const (
|
|||||||
// identifies if test suite is running on a unix platform
|
// identifies if test suite is running on a unix platform
|
||||||
isUnixCli = false
|
isUnixCli = false
|
||||||
|
|
||||||
// this is the expected file permission set on windows: gh#11047
|
// this is the expected file permission set on windows: gh#11395
|
||||||
expectedFileChmod = "-rwx------"
|
expectedFileChmod = "-rwxr-xr-x"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,10 +28,9 @@ func CanonicalTarNameForPath(p string) (string, error) {
|
|||||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
||||||
// on the platform the archival is done.
|
// on the platform the archival is done.
|
||||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
||||||
// Clear r/w on grp/others: no precise equivalen of group/others on NTFS.
|
perm &= 0755
|
||||||
perm &= 0711
|
|
||||||
// Add the x bit: make everything +x from windows
|
// Add the x bit: make everything +x from windows
|
||||||
perm |= 0100
|
perm |= 0111
|
||||||
|
|
||||||
return perm
|
return perm
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ func TestChmodTarEntry(t *testing.T) {
|
|||||||
cases := []struct {
|
cases := []struct {
|
||||||
in, expected os.FileMode
|
in, expected os.FileMode
|
||||||
}{
|
}{
|
||||||
{0000, 0100},
|
{0000, 0111},
|
||||||
{0777, 0711},
|
{0777, 0755},
|
||||||
{0644, 0700},
|
{0644, 0755},
|
||||||
{0755, 0711},
|
{0755, 0755},
|
||||||
{0444, 0500},
|
{0444, 0555},
|
||||||
}
|
}
|
||||||
for _, v := range cases {
|
for _, v := range cases {
|
||||||
if out := chmodTarEntry(v.in); out != v.expected {
|
if out := chmodTarEntry(v.in); out != v.expected {
|
||||||
|
|||||||
@@ -220,8 +220,8 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
|||||||
oldStat.Gid() != newStat.Gid() ||
|
oldStat.Gid() != newStat.Gid() ||
|
||||||
oldStat.Rdev() != newStat.Rdev() ||
|
oldStat.Rdev() != newStat.Rdev() ||
|
||||||
// Don't look at size for dirs, its not a good measure of change
|
// Don't look at size for dirs, its not a good measure of change
|
||||||
(oldStat.Size() != newStat.Size() && oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR) ||
|
(oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
|
||||||
!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) ||
|
(!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) ||
|
||||||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
|
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
|
||||||
change := Change{
|
change := Change{
|
||||||
Path: newChild.path(),
|
Path: newChild.path(),
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ func TestChangesDirsMutated(t *testing.T) {
|
|||||||
expectedChanges := []Change{
|
expectedChanges := []Change{
|
||||||
{"/dir1", ChangeDelete},
|
{"/dir1", ChangeDelete},
|
||||||
{"/dir2", ChangeModify},
|
{"/dir2", ChangeModify},
|
||||||
{"/dir3", ChangeModify},
|
|
||||||
{"/dirnew", ChangeAdd},
|
{"/dirnew", ChangeAdd},
|
||||||
{"/file1", ChangeDelete},
|
{"/file1", ChangeDelete},
|
||||||
{"/file2", ChangeModify},
|
{"/file2", ChangeModify},
|
||||||
|
|||||||
+111
-8
@@ -2,8 +2,11 @@ package ioutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type readCloserWrapper struct {
|
type readCloserWrapper struct {
|
||||||
@@ -42,20 +45,40 @@ func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bufReader allows the underlying reader to continue to produce
|
||||||
|
// output by pre-emptively reading from the wrapped reader.
|
||||||
|
// This is achieved by buffering this data in bufReader's
|
||||||
|
// expanding buffer.
|
||||||
type bufReader struct {
|
type bufReader struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
buf *bytes.Buffer
|
buf *bytes.Buffer
|
||||||
reader io.Reader
|
reader io.Reader
|
||||||
err error
|
err error
|
||||||
wait sync.Cond
|
wait sync.Cond
|
||||||
drainBuf []byte
|
drainBuf []byte
|
||||||
|
reuseBuf []byte
|
||||||
|
maxReuse int64
|
||||||
|
resetTimeout time.Duration
|
||||||
|
bufLenResetThreshold int64
|
||||||
|
maxReadDataReset int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBufReader(r io.Reader) *bufReader {
|
func NewBufReader(r io.Reader) *bufReader {
|
||||||
|
var timeout int
|
||||||
|
if randVal, err := rand.Int(rand.Reader, big.NewInt(120)); err == nil {
|
||||||
|
timeout = int(randVal.Int64()) + 180
|
||||||
|
} else {
|
||||||
|
timeout = 300
|
||||||
|
}
|
||||||
reader := &bufReader{
|
reader := &bufReader{
|
||||||
buf: &bytes.Buffer{},
|
buf: &bytes.Buffer{},
|
||||||
drainBuf: make([]byte, 1024),
|
drainBuf: make([]byte, 1024),
|
||||||
reader: r,
|
reuseBuf: make([]byte, 4096),
|
||||||
|
maxReuse: 1000,
|
||||||
|
resetTimeout: time.Second * time.Duration(timeout),
|
||||||
|
bufLenResetThreshold: 100 * 1024,
|
||||||
|
maxReadDataReset: 10 * 1024 * 1024,
|
||||||
|
reader: r,
|
||||||
}
|
}
|
||||||
reader.wait.L = &reader.Mutex
|
reader.wait.L = &reader.Mutex
|
||||||
go reader.drain()
|
go reader.drain()
|
||||||
@@ -74,14 +97,94 @@ func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *bufReader) drain() {
|
func (r *bufReader) drain() {
|
||||||
|
var (
|
||||||
|
duration time.Duration
|
||||||
|
lastReset time.Time
|
||||||
|
now time.Time
|
||||||
|
reset bool
|
||||||
|
bufLen int64
|
||||||
|
dataSinceReset int64
|
||||||
|
maxBufLen int64
|
||||||
|
reuseBufLen int64
|
||||||
|
reuseCount int64
|
||||||
|
)
|
||||||
|
reuseBufLen = int64(len(r.reuseBuf))
|
||||||
|
lastReset = time.Now()
|
||||||
for {
|
for {
|
||||||
n, err := r.reader.Read(r.drainBuf)
|
n, err := r.reader.Read(r.drainBuf)
|
||||||
|
dataSinceReset += int64(n)
|
||||||
r.Lock()
|
r.Lock()
|
||||||
|
bufLen = int64(r.buf.Len())
|
||||||
|
if bufLen > maxBufLen {
|
||||||
|
maxBufLen = bufLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid unbounded growth of the buffer over time.
|
||||||
|
// This has been discovered to be the only non-intrusive
|
||||||
|
// solution to the unbounded growth of the buffer.
|
||||||
|
// Alternative solutions such as compression, multiple
|
||||||
|
// buffers, channels and other similar pieces of code
|
||||||
|
// were reducing throughput, overall Docker performance
|
||||||
|
// or simply crashed Docker.
|
||||||
|
// This solution releases the buffer when specific
|
||||||
|
// conditions are met to avoid the continuous resizing
|
||||||
|
// of the buffer for long lived containers.
|
||||||
|
//
|
||||||
|
// Move data to the front of the buffer if it's
|
||||||
|
// smaller than what reuseBuf can store
|
||||||
|
if bufLen > 0 && reuseBufLen >= bufLen {
|
||||||
|
n, _ := r.buf.Read(r.reuseBuf)
|
||||||
|
r.buf.Write(r.reuseBuf[0:n])
|
||||||
|
// Take action if the buffer has been reused too many
|
||||||
|
// times and if there's data in the buffer.
|
||||||
|
// The timeout is also used as means to avoid doing
|
||||||
|
// these operations more often or less often than
|
||||||
|
// required.
|
||||||
|
// The various conditions try to detect heavy activity
|
||||||
|
// in the buffer which might be indicators of heavy
|
||||||
|
// growth of the buffer.
|
||||||
|
} else if reuseCount >= r.maxReuse && bufLen > 0 {
|
||||||
|
now = time.Now()
|
||||||
|
duration = now.Sub(lastReset)
|
||||||
|
timeoutReached := duration >= r.resetTimeout
|
||||||
|
|
||||||
|
// The timeout has been reached and the
|
||||||
|
// buffered data couldn't be moved to the front
|
||||||
|
// of the buffer, so the buffer gets reset.
|
||||||
|
if timeoutReached && bufLen > reuseBufLen {
|
||||||
|
reset = true
|
||||||
|
}
|
||||||
|
// The amount of buffered data is too high now,
|
||||||
|
// reset the buffer.
|
||||||
|
if timeoutReached && maxBufLen >= r.bufLenResetThreshold {
|
||||||
|
reset = true
|
||||||
|
}
|
||||||
|
// Reset the buffer if a certain amount of
|
||||||
|
// data has gone through the buffer since the
|
||||||
|
// last reset.
|
||||||
|
if timeoutReached && dataSinceReset >= r.maxReadDataReset {
|
||||||
|
reset = true
|
||||||
|
}
|
||||||
|
// The buffered data is moved to a fresh buffer,
|
||||||
|
// swap the old buffer with the new one and
|
||||||
|
// reset all counters.
|
||||||
|
if reset {
|
||||||
|
newbuf := &bytes.Buffer{}
|
||||||
|
newbuf.ReadFrom(r.buf)
|
||||||
|
r.buf = newbuf
|
||||||
|
lastReset = now
|
||||||
|
reset = false
|
||||||
|
dataSinceReset = 0
|
||||||
|
maxBufLen = 0
|
||||||
|
reuseCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.err = err
|
r.err = err
|
||||||
} else {
|
} else {
|
||||||
r.buf.Write(r.drainBuf[0:n])
|
r.buf.Write(r.drainBuf[0:n])
|
||||||
}
|
}
|
||||||
|
reuseCount++
|
||||||
r.wait.Signal()
|
r.wait.Signal()
|
||||||
r.Unlock()
|
r.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -32,3 +32,61 @@ func TestBufReader(t *testing.T) {
|
|||||||
t.Error(string(output))
|
t.Error(string(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type repeatedReader struct {
|
||||||
|
readCount int
|
||||||
|
maxReads int
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRepeatedReader(max int, data []byte) *repeatedReader {
|
||||||
|
return &repeatedReader{0, max, data}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repeatedReader) Read(p []byte) (int, error) {
|
||||||
|
if r.readCount >= r.maxReads {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
r.readCount++
|
||||||
|
n := copy(p, r.data)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testWithData(data []byte, reads int) {
|
||||||
|
reader := newRepeatedReader(reads, data)
|
||||||
|
bufReader := NewBufReader(reader)
|
||||||
|
io.Copy(ioutil.Discard, bufReader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark1M10BytesReads(b *testing.B) {
|
||||||
|
reads := 1000000
|
||||||
|
readSize := int64(10)
|
||||||
|
data := make([]byte, readSize)
|
||||||
|
b.SetBytes(readSize * int64(reads))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testWithData(data, reads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark1M1024BytesReads(b *testing.B) {
|
||||||
|
reads := 1000000
|
||||||
|
readSize := int64(1024)
|
||||||
|
data := make([]byte, readSize)
|
||||||
|
b.SetBytes(readSize * int64(reads))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testWithData(data, reads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark10k32KBytesReads(b *testing.B) {
|
||||||
|
reads := 10000
|
||||||
|
readSize := int64(32 * 1024)
|
||||||
|
data := make([]byte, readSize)
|
||||||
|
b.SetBytes(readSize * int64(reads))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testWithData(data, reads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package term
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/term/winconsole"
|
"github.com/docker/docker/pkg/term/winconsole"
|
||||||
)
|
)
|
||||||
@@ -114,5 +115,23 @@ func GetFdInfo(in interface{}) (uintptr, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||||
return winconsole.StdStreams()
|
var shouldEmulateANSI bool
|
||||||
|
switch {
|
||||||
|
case os.Getenv("ConEmuANSI") == "ON":
|
||||||
|
// ConEmu shell, ansi emulated by default and ConEmu does an extensively
|
||||||
|
// good emulation.
|
||||||
|
shouldEmulateANSI = false
|
||||||
|
case os.Getenv("MSYSTEM") != "":
|
||||||
|
// MSYS (mingw) cannot fully emulate well and still shows escape characters
|
||||||
|
// mostly because it's still running on cmd.exe window.
|
||||||
|
shouldEmulateANSI = true
|
||||||
|
default:
|
||||||
|
shouldEmulateANSI = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldEmulateANSI {
|
||||||
|
return winconsole.StdStreams()
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Stdin, os.Stdout, os.Stderr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -99,12 +99,11 @@ func (m *Manager) Apply(pid int) error {
|
|||||||
// created then join consists of writing the process pids to cgroup.procs
|
// created then join consists of writing the process pids to cgroup.procs
|
||||||
p, err := d.path(name)
|
p, err := d.path(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if cgroups.IsNotFound(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !cgroups.PathExists(p) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
paths[name] = p
|
paths[name] = p
|
||||||
}
|
}
|
||||||
m.Paths = paths
|
m.Paths = paths
|
||||||
|
|||||||
@@ -17,12 +17,8 @@ type BlkioGroup struct {
|
|||||||
|
|
||||||
func (s *BlkioGroup) Apply(d *data) error {
|
func (s *BlkioGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("blkio")
|
dir, err := d.join("blkio")
|
||||||
if err != nil {
|
if err != nil && !cgroups.IsNotFound(err) {
|
||||||
if cgroups.IsNotFound(err) {
|
return err
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Set(dir, d.c); err != nil {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
|
|||||||
@@ -18,11 +18,7 @@ func (s *CpuGroup) Apply(d *data) error {
|
|||||||
// on a container basis
|
// on a container basis
|
||||||
dir, err := d.join("cpu")
|
dir, err := d.join("cpu")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cgroups.IsNotFound(err) {
|
return err
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Set(dir, d.c); err != nil {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
|
|||||||
@@ -11,11 +11,7 @@ type DevicesGroup struct {
|
|||||||
func (s *DevicesGroup) Apply(d *data) error {
|
func (s *DevicesGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("devices")
|
dir, err := d.join("devices")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cgroups.IsNotFound(err) {
|
return err
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Set(dir, d.c); err != nil {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
|
|||||||
@@ -13,12 +13,8 @@ type FreezerGroup struct {
|
|||||||
|
|
||||||
func (s *FreezerGroup) Apply(d *data) error {
|
func (s *FreezerGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("freezer")
|
dir, err := d.join("freezer")
|
||||||
if err != nil {
|
if err != nil && !cgroups.IsNotFound(err) {
|
||||||
if cgroups.IsNotFound(err) {
|
return err
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Set(dir, d.c); err != nil {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
|
|||||||
@@ -16,12 +16,9 @@ type MemoryGroup struct {
|
|||||||
|
|
||||||
func (s *MemoryGroup) Apply(d *data) error {
|
func (s *MemoryGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("memory")
|
dir, err := d.join("memory")
|
||||||
if err != nil {
|
// only return an error for memory if it was specified
|
||||||
if cgroups.IsNotFound(err) {
|
if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) {
|
||||||
return nil
|
return err
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func UseSystemd() bool {
|
|||||||
ddf := newProp("DefaultDependencies", false)
|
ddf := newProp("DefaultDependencies", false)
|
||||||
if _, err := theConn.StartTransientUnit("docker-systemd-test-default-dependencies.scope", "replace", ddf); err != nil {
|
if _, err := theConn.StartTransientUnit("docker-systemd-test-default-dependencies.scope", "replace", ddf); err != nil {
|
||||||
if dbusError, ok := err.(dbus.Error); ok {
|
if dbusError, ok := err.(dbus.Error); ok {
|
||||||
if dbusError.Name == "org.freedesktop.DBus.Error.PropertyReadOnly" {
|
if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
|
||||||
hasTransientDefaultDependencies = false
|
hasTransientDefaultDependencies = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -659,7 +659,7 @@ func networkSetNsAction(iface *net.Interface, rtattr *RtAttr) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move a particular network interface to a particular network namespace
|
// Move a particular network interface to a particular network namespace
|
||||||
// specified by PID. This is idential to running: ip link set dev $name netns $pid
|
// specified by PID. This is identical to running: ip link set dev $name netns $pid
|
||||||
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
|
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
|
||||||
data := uint32Attr(syscall.IFLA_NET_NS_PID, uint32(nspid))
|
data := uint32Attr(syscall.IFLA_NET_NS_PID, uint32(nspid))
|
||||||
return networkSetNsAction(iface, data)
|
return networkSetNsAction(iface, data)
|
||||||
@@ -673,7 +673,7 @@ func NetworkSetNsFd(iface *net.Interface, fd int) error {
|
|||||||
return networkSetNsAction(iface, data)
|
return networkSetNsAction(iface, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rname a particular interface to a different name
|
// Rename a particular interface to a different name
|
||||||
// !!! Note that you can't rename an active interface. You need to bring it down before renaming it.
|
// !!! Note that you can't rename an active interface. You need to bring it down before renaming it.
|
||||||
// This is identical to running: ip link set dev ${oldName} name ${newName}
|
// This is identical to running: ip link set dev ${oldName} name ${newName}
|
||||||
func NetworkChangeName(iface *net.Interface, newName string) error {
|
func NetworkChangeName(iface *net.Interface, newName string) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user