Merge pull request #25142 from tiborvass/1.12.0-final-cherry-picks

1.12.0 final cherry picks
This commit is contained in:
Justin Cormack 2016-07-28 00:21:36 +01:00 committed by GitHub
commit 664fcd9f28
19 changed files with 170 additions and 113 deletions

View file

@ -71,6 +71,7 @@ type Builder struct {
disableCommit bool
cacheBusted bool
allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
directive parser.Directive
// TODO: remove once docker.Commit can receive a tag
id string
@ -130,9 +131,15 @@ func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, back
tmpContainers: map[string]struct{}{},
id: stringid.GenerateNonCryptoID(),
allowedBuildArgs: make(map[string]bool),
directive: parser.Directive{
EscapeSeen: false,
LookingForDirectives: true,
},
}
parser.SetEscapeToken(parser.DefaultEscapeToken, &b.directive) // Assume the default token for escape
if dockerfile != nil {
b.dockerfile, err = parser.Parse(dockerfile)
b.dockerfile, err = parser.Parse(dockerfile, &b.directive)
if err != nil {
return nil, err
}
@ -218,7 +225,7 @@ func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (stri
for k, v := range b.options.Labels {
line += fmt.Sprintf("%q=%q ", k, v)
}
_, node, err := parser.ParseLine(line)
_, node, err := parser.ParseLine(line, &b.directive)
if err != nil {
return "", err
}
@ -291,7 +298,12 @@ func (b *Builder) Cancel() {
//
// TODO: Remove?
func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
b, err := NewBuilder(context.Background(), nil, nil, nil, nil)
if err != nil {
return nil, err
}
ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")), &b.directive)
if err != nil {
return nil, err
}
@ -303,10 +315,6 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
}
}
b, err := NewBuilder(context.Background(), nil, nil, nil, nil)
if err != nil {
return nil, err
}
b.runConfig = config
b.Stdout = ioutil.Discard
b.Stderr = ioutil.Discard

View file

@ -173,7 +173,9 @@ func executeTestCase(t *testing.T, testCase dispatchTestCase) {
}()
r := strings.NewReader(testCase.dockerfile)
n, err := parser.Parse(r)
d := parser.Directive{}
parser.SetEscapeToken(parser.DefaultEscapeToken, &d)
n, err := parser.Parse(r, &d)
if err != nil {
t.Fatalf("Error when parsing Dockerfile: %s", err)

View file

@ -427,7 +427,7 @@ func (b *Builder) processImageFrom(img builder.Image) error {
// parse the ONBUILD triggers by invoking the parser
for _, step := range onBuildTriggers {
ast, err := parser.Parse(strings.NewReader(step))
ast, err := parser.Parse(strings.NewReader(step), &b.directive)
if err != nil {
return err
}
@ -648,7 +648,7 @@ func (b *Builder) parseDockerfile() error {
return fmt.Errorf("The Dockerfile (%s) cannot be empty", b.options.Dockerfile)
}
}
b.dockerfile, err = parser.Parse(f)
b.dockerfile, err = parser.Parse(f, &b.directive)
if err != nil {
return err
}

View file

@ -22,7 +22,10 @@ func main() {
panic(err)
}
ast, err := parser.Parse(f)
d := parser.Directive{LookingForDirectives: true}
parser.SetEscapeToken(parser.DefaultEscapeToken, &d)
ast, err := parser.Parse(f, &d)
if err != nil {
panic(err)
} else {

View file

@ -28,7 +28,10 @@ var validJSONArraysOfStrings = map[string][]string{
func TestJSONArraysOfStrings(t *testing.T) {
for json, expected := range validJSONArraysOfStrings {
if node, _, err := parseJSON(json); err != nil {
d := Directive{}
SetEscapeToken(DefaultEscapeToken, &d)
if node, _, err := parseJSON(json, &d); err != nil {
t.Fatalf("%q should be a valid JSON array of strings, but wasn't! (err: %q)", json, err)
} else {
i := 0
@ -48,7 +51,10 @@ func TestJSONArraysOfStrings(t *testing.T) {
}
}
for _, json := range invalidJSONArraysOfStrings {
if _, _, err := parseJSON(json); err != errDockerfileNotStringArray {
d := Directive{}
SetEscapeToken(DefaultEscapeToken, &d)
if _, _, err := parseJSON(json, &d); err != errDockerfileNotStringArray {
t.Fatalf("%q should be an invalid JSON array of strings, but wasn't!", json)
}
}

View file

@ -21,7 +21,7 @@ var (
// ignore the current argument. This will still leave a command parsed, but
// will not incorporate the arguments into the ast.
func parseIgnore(rest string) (*Node, map[string]bool, error) {
func parseIgnore(rest string, d *Directive) (*Node, map[string]bool, error) {
return &Node{}, nil, nil
}
@ -30,12 +30,12 @@ func parseIgnore(rest string) (*Node, map[string]bool, error) {
//
// ONBUILD RUN foo bar -> (onbuild (run foo bar))
//
func parseSubCommand(rest string) (*Node, map[string]bool, error) {
func parseSubCommand(rest string, d *Directive) (*Node, map[string]bool, error) {
if rest == "" {
return nil, nil, nil
}
_, child, err := ParseLine(rest)
_, child, err := ParseLine(rest, d)
if err != nil {
return nil, nil, err
}
@ -46,7 +46,7 @@ func parseSubCommand(rest string) (*Node, map[string]bool, error) {
// helper to parse words (i.e space delimited or quoted strings) in a statement.
// The quotes are preserved as part of this function and they are stripped later
// as part of processWords().
func parseWords(rest string) []string {
func parseWords(rest string, d *Directive) []string {
const (
inSpaces = iota // looking for start of a word
inWord
@ -96,7 +96,7 @@ func parseWords(rest string) []string {
blankOK = true
phase = inQuote
}
if ch == tokenEscape {
if ch == d.EscapeToken {
if pos+chWidth == len(rest) {
continue // just skip an escape token at end of line
}
@ -115,7 +115,7 @@ func parseWords(rest string) []string {
phase = inWord
}
// The escape token is special except for ' quotes - can't escape anything for '
if ch == tokenEscape && quote != '\'' {
if ch == d.EscapeToken && quote != '\'' {
if pos+chWidth == len(rest) {
phase = inWord
continue // just skip the escape token at end
@ -133,14 +133,14 @@ func parseWords(rest string) []string {
// parse environment like statements. Note that this does *not* handle
// variable interpolation, which will be handled in the evaluator.
func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
func parseNameVal(rest string, key string, d *Directive) (*Node, map[string]bool, error) {
// This is kind of tricky because we need to support the old
// variant: KEY name value
// as well as the new one: KEY name=value ...
// The trigger to know which one is being used will be whether we hit
// a space or = first. space ==> old, "=" ==> new
words := parseWords(rest)
words := parseWords(rest, d)
if len(words) == 0 {
return nil, nil, nil
}
@ -187,12 +187,12 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
return rootnode, nil, nil
}
func parseEnv(rest string) (*Node, map[string]bool, error) {
return parseNameVal(rest, "ENV")
func parseEnv(rest string, d *Directive) (*Node, map[string]bool, error) {
return parseNameVal(rest, "ENV", d)
}
func parseLabel(rest string) (*Node, map[string]bool, error) {
return parseNameVal(rest, "LABEL")
func parseLabel(rest string, d *Directive) (*Node, map[string]bool, error) {
return parseNameVal(rest, "LABEL", d)
}
// parses a statement containing one or more keyword definition(s) and/or
@ -203,8 +203,8 @@ func parseLabel(rest string) (*Node, map[string]bool, error) {
// In addition, a keyword definition alone is of the form `keyword` like `name1`
// above. And the assignments `name2=` and `name3=""` are equivalent and
// assign an empty value to the respective keywords.
func parseNameOrNameVal(rest string) (*Node, map[string]bool, error) {
words := parseWords(rest)
func parseNameOrNameVal(rest string, d *Directive) (*Node, map[string]bool, error) {
words := parseWords(rest, d)
if len(words) == 0 {
return nil, nil, nil
}
@ -229,7 +229,7 @@ func parseNameOrNameVal(rest string) (*Node, map[string]bool, error) {
// parses a whitespace-delimited set of arguments. The result is effectively a
// linked list of string arguments.
func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
func parseStringsWhitespaceDelimited(rest string, d *Directive) (*Node, map[string]bool, error) {
if rest == "" {
return nil, nil, nil
}
@ -253,7 +253,7 @@ func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error
}
// parsestring just wraps the string in quotes and returns a working node.
func parseString(rest string) (*Node, map[string]bool, error) {
func parseString(rest string, d *Directive) (*Node, map[string]bool, error) {
if rest == "" {
return nil, nil, nil
}
@ -263,7 +263,7 @@ func parseString(rest string) (*Node, map[string]bool, error) {
}
// parseJSON converts JSON arrays to an AST.
func parseJSON(rest string) (*Node, map[string]bool, error) {
func parseJSON(rest string, d *Directive) (*Node, map[string]bool, error) {
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
if !strings.HasPrefix(rest, "[") {
return nil, nil, fmt.Errorf(`Error parsing "%s" as a JSON array`, rest)
@ -296,12 +296,12 @@ func parseJSON(rest string) (*Node, map[string]bool, error) {
// parseMaybeJSON determines if the argument appears to be a JSON array. If
// so, passes to parseJSON; if not, quotes the result and returns a single
// node.
func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
func parseMaybeJSON(rest string, d *Directive) (*Node, map[string]bool, error) {
if rest == "" {
return nil, nil, nil
}
node, attrs, err := parseJSON(rest)
node, attrs, err := parseJSON(rest, d)
if err == nil {
return node, attrs, nil
@ -318,8 +318,8 @@ func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
// parseMaybeJSONToList determines if the argument appears to be a JSON array. If
// so, passes to parseJSON; if not, attempts to parse it as a whitespace
// delimited string.
func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) {
node, attrs, err := parseJSON(rest)
func parseMaybeJSONToList(rest string, d *Directive) (*Node, map[string]bool, error) {
node, attrs, err := parseJSON(rest, d)
if err == nil {
return node, attrs, nil
@ -328,11 +328,11 @@ func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) {
return nil, nil, err
}
return parseStringsWhitespaceDelimited(rest)
return parseStringsWhitespaceDelimited(rest, d)
}
// The HEALTHCHECK command is like parseMaybeJSON, but has an extra type argument.
func parseHealthConfig(rest string) (*Node, map[string]bool, error) {
func parseHealthConfig(rest string, d *Directive) (*Node, map[string]bool, error) {
// Find end of first argument
var sep int
for ; sep < len(rest); sep++ {
@ -352,7 +352,7 @@ func parseHealthConfig(rest string) (*Node, map[string]bool, error) {
}
typ := rest[:sep]
cmd, attrs, err := parseMaybeJSON(rest[next:])
cmd, attrs, err := parseMaybeJSON(rest[next:], d)
if err != nil {
return nil, nil, err
}

View file

@ -36,26 +36,32 @@ type Node struct {
EndLine int // the line in the original dockerfile where the node ends
}
// Directive is the structure used during a build run to hold the state of
// parsing directives.
type Directive struct {
EscapeToken rune // Current escape token
LineContinuationRegex *regexp.Regexp // Current line contination regex
LookingForDirectives bool // Whether we are currently looking for directives
EscapeSeen bool // Whether the escape directive has been seen
}
var (
dispatch map[string]func(string) (*Node, map[string]bool, error)
tokenWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
tokenLineContinuation *regexp.Regexp
tokenEscape rune
tokenEscapeCommand = regexp.MustCompile(`^#[ \t]*escape[ \t]*=[ \t]*(?P<escapechar>.).*$`)
tokenComment = regexp.MustCompile(`^#.*$`)
lookingForDirectives bool
directiveEscapeSeen bool
dispatch map[string]func(string, *Directive) (*Node, map[string]bool, error)
tokenWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
tokenEscapeCommand = regexp.MustCompile(`^#[ \t]*escape[ \t]*=[ \t]*(?P<escapechar>.).*$`)
tokenComment = regexp.MustCompile(`^#.*$`)
)
const defaultTokenEscape = "\\"
// DefaultEscapeToken is the default escape token
const DefaultEscapeToken = "\\"
// setTokenEscape sets the default token for escaping characters in a Dockerfile.
func setTokenEscape(s string) error {
// SetEscapeToken sets the default token for escaping characters in a Dockerfile.
func SetEscapeToken(s string, d *Directive) error {
if s != "`" && s != "\\" {
return fmt.Errorf("invalid ESCAPE '%s'. Must be ` or \\", s)
}
tokenEscape = rune(s[0])
tokenLineContinuation = regexp.MustCompile(`\` + s + `[ \t]*$`)
d.EscapeToken = rune(s[0])
d.LineContinuationRegex = regexp.MustCompile(`\` + s + `[ \t]*$`)
return nil
}
@ -66,7 +72,7 @@ func init() {
// reformulating the arguments according to the rules in the parser
// functions. Errors are propagated up by Parse() and the resulting AST can
// be incorporated directly into the existing AST as a next.
dispatch = map[string]func(string) (*Node, map[string]bool, error){
dispatch = map[string]func(string, *Directive) (*Node, map[string]bool, error){
command.Add: parseMaybeJSONToList,
command.Arg: parseNameOrNameVal,
command.Cmd: parseMaybeJSON,
@ -89,36 +95,35 @@ func init() {
}
// ParseLine parse a line and return the remainder.
func ParseLine(line string) (string, *Node, error) {
func ParseLine(line string, d *Directive) (string, *Node, error) {
// Handle the parser directive '# escape=<char>. Parser directives must precede
// any builder instruction or other comments, and cannot be repeated.
if lookingForDirectives {
if d.LookingForDirectives {
tecMatch := tokenEscapeCommand.FindStringSubmatch(strings.ToLower(line))
if len(tecMatch) > 0 {
if directiveEscapeSeen == true {
if d.EscapeSeen == true {
return "", nil, fmt.Errorf("only one escape parser directive can be used")
}
for i, n := range tokenEscapeCommand.SubexpNames() {
if n == "escapechar" {
if err := setTokenEscape(tecMatch[i]); err != nil {
if err := SetEscapeToken(tecMatch[i], d); err != nil {
return "", nil, err
}
directiveEscapeSeen = true
d.EscapeSeen = true
return "", nil, nil
}
}
}
}
lookingForDirectives = false
d.LookingForDirectives = false
if line = stripComments(line); line == "" {
return "", nil, nil
}
if tokenLineContinuation.MatchString(line) {
line = tokenLineContinuation.ReplaceAllString(line, "")
if d.LineContinuationRegex.MatchString(line) {
line = d.LineContinuationRegex.ReplaceAllString(line, "")
return line, nil, nil
}
@ -130,7 +135,7 @@ func ParseLine(line string) (string, *Node, error) {
node := &Node{}
node.Value = cmd
sexp, attrs, err := fullDispatch(cmd, args)
sexp, attrs, err := fullDispatch(cmd, args, d)
if err != nil {
return "", nil, err
}
@ -145,10 +150,7 @@ func ParseLine(line string) (string, *Node, error) {
// Parse is the main parse routine.
// It handles an io.ReadWriteCloser and returns the root of the AST.
func Parse(rwc io.Reader) (*Node, error) {
directiveEscapeSeen = false
lookingForDirectives = true
setTokenEscape(defaultTokenEscape) // Assume the default token for escape
func Parse(rwc io.Reader, d *Directive) (*Node, error) {
currentLine := 0
root := &Node{}
root.StartLine = -1
@ -163,7 +165,7 @@ func Parse(rwc io.Reader) (*Node, error) {
}
scannedLine := strings.TrimLeftFunc(string(scannedBytes), unicode.IsSpace)
currentLine++
line, child, err := ParseLine(scannedLine)
line, child, err := ParseLine(scannedLine, d)
if err != nil {
return nil, err
}
@ -178,7 +180,7 @@ func Parse(rwc io.Reader) (*Node, error) {
continue
}
line, child, err = ParseLine(line + newline)
line, child, err = ParseLine(line+newline, d)
if err != nil {
return nil, err
}
@ -188,7 +190,7 @@ func Parse(rwc io.Reader) (*Node, error) {
}
}
if child == nil && line != "" {
_, child, err = ParseLine(line)
_, child, err = ParseLine(line, d)
if err != nil {
return nil, err
}

View file

@ -39,7 +39,9 @@ func TestTestNegative(t *testing.T) {
t.Fatalf("Dockerfile missing for %s: %v", dir, err)
}
_, err = Parse(df)
d := Directive{LookingForDirectives: true}
SetEscapeToken(DefaultEscapeToken, &d)
_, err = Parse(df, &d)
if err == nil {
t.Fatalf("No error parsing broken dockerfile for %s", dir)
}
@ -59,7 +61,9 @@ func TestTestData(t *testing.T) {
}
defer df.Close()
ast, err := Parse(df)
d := Directive{LookingForDirectives: true}
SetEscapeToken(DefaultEscapeToken, &d)
ast, err := Parse(df, &d)
if err != nil {
t.Fatalf("Error parsing %s's dockerfile: %v", dir, err)
}
@ -119,13 +123,15 @@ func TestParseWords(t *testing.T) {
}
for _, test := range tests {
words := parseWords(test["input"][0])
d := Directive{LookingForDirectives: true}
SetEscapeToken(DefaultEscapeToken, &d)
words := parseWords(test["input"][0], &d)
if len(words) != len(test["expect"]) {
t.Fatalf("length check failed. input: %v, expect: %v, output: %v", test["input"][0], test["expect"], words)
t.Fatalf("length check failed. input: %v, expect: %q, output: %q", test["input"][0], test["expect"], words)
}
for i, word := range words {
if word != test["expect"][i] {
t.Fatalf("word check failed for word: %q. input: %v, expect: %v, output: %v", word, test["input"][0], test["expect"], words)
t.Fatalf("word check failed for word: %q. input: %q, expect: %q, output: %q", word, test["input"][0], test["expect"], words)
}
}
}
@ -138,7 +144,9 @@ func TestLineInformation(t *testing.T) {
}
defer df.Close()
ast, err := Parse(df)
d := Directive{LookingForDirectives: true}
SetEscapeToken(DefaultEscapeToken, &d)
ast, err := Parse(df, &d)
if err != nil {
t.Fatalf("Error parsing dockerfile %s: %v", testFileLineInfo, err)
}

View file

@ -36,7 +36,7 @@ func (node *Node) Dump() string {
// performs the dispatch based on the two primal strings, cmd and args. Please
// look at the dispatch table in parser.go to see how these dispatchers work.
func fullDispatch(cmd, args string) (*Node, map[string]bool, error) {
func fullDispatch(cmd, args string, d *Directive) (*Node, map[string]bool, error) {
fn := dispatch[cmd]
// Ignore invalid Dockerfile instructions
@ -44,7 +44,7 @@ func fullDispatch(cmd, args string) (*Node, map[string]bool, error) {
fn = parseIgnore
}
sexp, attrs, err := fn(args)
sexp, attrs, err := fn(args, d)
if err != nil {
return nil, nil, err
}

View file

@ -1741,6 +1741,7 @@ _docker_service_update() {
if [ "$subcommand" = "create" ] ; then
options_with_args="$options_with_args
--container-label
--mode
"
@ -1754,6 +1755,8 @@ _docker_service_update() {
if [ "$subcommand" = "update" ] ; then
options_with_args="$options_with_args
--arg
--container-label-add
--container-label-rm
--image
"
@ -1814,7 +1817,6 @@ _docker_service_update() {
_docker_swarm() {
local subcommands="
init
inspect
join
join-token
leave
@ -1855,20 +1857,6 @@ _docker_swarm_init() {
esac
}
_docker_swarm_inspect() {
case "$prev" in
--format|-f)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--format -f --help" -- "$cur" ) )
;;
esac
}
_docker_swarm_join() {
case "$prev" in
--token)

View file

@ -2,7 +2,6 @@
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network.target
Requires=docker.socket
[Service]
Type=notify

View file

@ -199,6 +199,7 @@ func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverO
upper := stringid.GenerateRandomID()
deleteFile := "file-remove.txt"
deleteFileContent := []byte("This file should get removed in upper!")
deleteDir := "var/lib"
if err := driver.Create(base, "", "", nil); err != nil {
t.Fatal(err)
@ -212,6 +213,10 @@ func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverO
t.Fatal(err)
}
if err := addDirectory(driver, base, deleteDir); err != nil {
t.Fatal(err)
}
if err := driver.Create(upper, base, "", nil); err != nil {
t.Fatal(err)
}
@ -220,7 +225,7 @@ func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverO
t.Fatal(err)
}
if err := removeFile(driver, upper, deleteFile); err != nil {
if err := removeAll(driver, upper, deleteFile, deleteDir); err != nil {
t.Fatal(err)
}
@ -271,6 +276,10 @@ func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverO
if err := checkFileRemoved(driver, diff, deleteFile); err != nil {
t.Fatal(err)
}
if err := checkFileRemoved(driver, diff, deleteDir); err != nil {
t.Fatal(err)
}
}
// DriverTestChanges tests computed changes on a layer matches changes made

View file

@ -78,14 +78,29 @@ func addFile(drv graphdriver.Driver, layer, filename string, content []byte) err
return ioutil.WriteFile(path.Join(root, filename), content, 0755)
}
func removeFile(drv graphdriver.Driver, layer, filename string) error {
func addDirectory(drv graphdriver.Driver, layer, dir string) error {
root, err := drv.Get(layer, "")
if err != nil {
return err
}
defer drv.Put(layer)
return os.Remove(path.Join(root, filename))
return os.MkdirAll(path.Join(root, dir), 0755)
}
func removeAll(drv graphdriver.Driver, layer string, names ...string) error {
root, err := drv.Get(layer, "")
if err != nil {
return err
}
defer drv.Put(layer)
for _, filename := range names {
if err := os.RemoveAll(path.Join(root, filename)); err != nil {
return err
}
}
return nil
}
func checkFileRemoved(drv graphdriver.Driver, layer, filename string) error {

View file

@ -29,7 +29,6 @@ update delay:
--replicas 3 \
--name redis \
--update-delay 10s \
--update-parallelism 1 \
redis:3.0.6
0u6a4s31ybk7yw2wyvtikmu50
@ -37,18 +36,21 @@ update delay:
You configure the rolling update policy at service deployment time.
The `--update-parallelism` flag configures the number of service tasks that
the scheduler can update simultaneously. When updates to individual tasks
return a state of `RUNNING` or `FAILED`, the scheduler schedules another
task to update until all tasks are updated.
The `--update-delay` flag configures the time delay between updates to a
service task or sets of tasks.
service task or sets of tasks. You can describe the time `T` as a
combination of the number of seconds `Ts`, minutes `Tm`, or hours `Th`. So
`10m30s` indicates a 10 minute 30 second delay.
You can describe the time `T` as a combination of the number of seconds
`Ts`, minutes `Tm`, or hours `Th`. So `10m30s` indicates a 10 minute 30
second delay.
By default the scheduler updates 1 task at a time. You can pass the
`--update-parallelism` flag to configure the maximum number of service tasks
that the scheduler updates simultaneously.
By default, when an update to an individual task returns a state of
`RUNNING`, the scheduler schedules another task to update until all tasks
are updated. If, at any time during an update a task returns `FAILED`, the
scheduler pauses the update. You can control the behavior using the
`--update-failure-action` flag for `docker service create` or
`docker service update`.
3. Inspect the `redis` service:
@ -77,13 +79,15 @@ applies the update to nodes according to the `UpdateConfig` policy:
redis
```
The scheduler applies rolling updates as follows:
The scheduler applies rolling updates as follows by default:
* Stop the initial number of tasks according to `--update-parallelism`.
* Schedule updates for the stopped tasks.
* Start the containers for the updated tasks.
* After an update to a task completes, wait for the specified delay
period before stopping the next task.
* Stop the first task.
* Schedule update for the stopped task.
* Start the container for the updated task.
* If the update to a task returns `RUNNING`, wait for the
specified delay period then stop the next task.
* If, at any time during the update, a task returns `FAILED`, pause the
update.
5. Run `docker service inspect --pretty redis` to see the new image in the
desired state:

View file

@ -65,7 +65,7 @@ clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837
clone git github.com/imdario/mergo 0.2.1
#get libnetwork packages
clone git github.com/docker/libnetwork c7dc6dc476a5f00f9b28efebe591347dd64264fc
clone git github.com/docker/libnetwork 443b7be96fdf0ed8f65ec92953aa8df4f9a725dc
clone git github.com/docker/go-events afb2b9f2c23f33ada1a22b03651775fdc65a5089
clone git github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec

View file

@ -563,6 +563,8 @@ func (clnt *client) Restore(containerID string, options ...CreateOption) error {
clnt.remote.Lock()
return nil
}
// relock because of the defer
clnt.remote.Lock()
clnt.deleteContainer(containerID)

View file

@ -23,7 +23,8 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os
// convert whiteouts to AUFS format
if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 {
// we just rename the file and make it normal
hdr.Name = WhiteoutPrefix + hdr.Name
dir, filename := filepath.Split(hdr.Name)
hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename)
hdr.Mode = 0600
hdr.Typeflag = tar.TypeReg
hdr.Size = 0

View file

@ -726,6 +726,12 @@ func (sb *sandbox) restoreOslSandbox() error {
joinInfo := ep.joinInfo
i := ep.iface
ep.Unlock()
if i == nil {
log.Errorf("error restoring endpoint %s for container %s", ep.Name(), sb.ContainerID())
continue
}
ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().Address(i.addr), sb.osSbox.InterfaceOptions().Routes(i.routes))
if i.addrv6 != nil && i.addrv6.IP.To16() != nil {
ifaceOptions = append(ifaceOptions, sb.osSbox.InterfaceOptions().AddressIPv6(i.addrv6))

View file

@ -245,6 +245,10 @@ func (c *controller) sandboxCleanup(activeSandboxes map[string]interface{}) {
ep = &endpoint{id: eps.Eid, network: n, sandboxID: sbs.ID}
}
}
if _, ok := activeSandboxes[sb.ID()]; ok && err != nil {
logrus.Errorf("failed to restore endpoint %s in %s for container %s due to %v", eps.Eid, eps.Nid, sb.ContainerID(), err)
continue
}
heap.Push(&sb.endpoints, ep)
}