diff --git a/builder/dockerfile/builder.go b/builder/dockerfile/builder.go index 21d84cb513..c63661e7f8 100644 --- a/builder/dockerfile/builder.go +++ b/builder/dockerfile/builder.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "sort" "strings" "time" @@ -208,13 +209,26 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder { return b } +// Build 'LABEL' command(s) from '--label' options and add to the last stage +func buildLabelOptions(labels map[string]string, stages []instructions.Stage) { + keys := []string{} + for key := range labels { + keys = append(keys, key) + } + + // Sort the label to have a repeatable order + sort.Strings(keys) + for _, key := range keys { + value := labels[key] + stages[len(stages)-1].AddCommand(instructions.NewLabelCommand(key, value, true)) + } +} + // Build runs the Dockerfile builder by parsing the Dockerfile and executing // the instructions from the file. func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*builder.Result, error) { defer b.imageSources.Unmount() - addNodesForLabelOption(dockerfile.AST, b.options.Labels) - stages, metaArgs, err := instructions.Parse(dockerfile.AST) if err != nil { if instructions.IsUnknownInstruction(err) { @@ -231,6 +245,9 @@ func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*buil stages = stages[:targetIx+1] } + // Add 'LABEL' command specified by '--label' option to the last stage + buildLabelOptions(b.options.Labels, stages) + dockerfile.PrintWarnings(b.Stderr) dispatchState, err := b.dispatchDockerfileWithCancellation(stages, metaArgs, dockerfile.EscapeToken, source) if err != nil { diff --git a/builder/dockerfile/instructions/commands.go b/builder/dockerfile/instructions/commands.go index 9d864e5325..633a2b3fc7 100644 --- a/builder/dockerfile/instructions/commands.go +++ b/builder/dockerfile/instructions/commands.go @@ -110,17 +110,37 @@ type MaintainerCommand struct { Maintainer string } +// NewLabelCommand creates a new 'LABEL' command +func NewLabelCommand(k string, v string, NoExp bool) *LabelCommand { + kvp := KeyValuePair{Key: k, Value: v} + c := "LABEL " + c += kvp.String() + nc := withNameAndCode{code: c, name: "label"} + cmd := &LabelCommand{ + withNameAndCode: nc, + Labels: KeyValuePairs{ + kvp, + }, + noExpand: NoExp, + } + return cmd +} + // LabelCommand : LABEL some json data describing the image // // Sets the Label variable foo to bar, // type LabelCommand struct { withNameAndCode - Labels KeyValuePairs // kvp slice instead of map to preserve ordering + Labels KeyValuePairs // kvp slice instead of map to preserve ordering + noExpand bool } // Expand variables func (c *LabelCommand) Expand(expander SingleWordExpander) error { + if c.noExpand { + return nil + } return expandKvpsInPlace(c.Labels, expander) }