From cdfdfbfb6223fdd5b319942d412caac6bc09cdeb Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Tue, 17 Feb 2015 07:20:06 -0800 Subject: [PATCH] Allow specification of Label Name/Value pairs in image json content Save "LABEL" field in Dockerfile into image content. This will allow a user to save user data into an image, which can later be retrieved using: docker inspect IMAGEID I have copied this from the "Comment" handling in docker images. We want to be able to add Name/Value data to an image to describe the image, and then be able to use other tools to look at this data, to be able to do security checks based on this data. We are thinking about adding version names, Perhaps listing the content of the dockerfile. Descriptions of where the code came from etc. This LABEL field should also be allowed to be specified in the docker import --change LABEL:Name=Value docker commit --change LABEL:Name=Value Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- builder/command/command.go | 1 + builder/dispatchers.go | 31 +++++++++++++++++++ builder/evaluator.go | 1 + builder/parser/line_parsers.go | 20 ++++++++---- builder/parser/parser.go | 1 + contrib/syntax/kate/Dockerfile.xml | 1 + .../Syntaxes/Dockerfile.tmLanguage | 2 +- contrib/syntax/vim/syntax/dockerfile.vim | 2 +- .../reference/api/docker_remote_api.md | 7 +++++ .../reference/api/docker_remote_api_v1.17.md | 6 ++++ docs/sources/reference/builder.md | 12 +++++++ integration-cli/docker_cli_build_test.go | 22 +++++++++++++ runconfig/config.go | 5 +++ runconfig/merge.go | 10 ++++++ 14 files changed, 113 insertions(+), 8 deletions(-) diff --git a/builder/command/command.go b/builder/command/command.go index f99fa2d906..dd24ee44c5 100644 --- a/builder/command/command.go +++ b/builder/command/command.go @@ -3,6 +3,7 @@ package command const ( Env = "env" + Label = "label" Maintainer = "maintainer" Add = "add" Copy = "copy" diff --git a/builder/dispatchers.go b/builder/dispatchers.go index 52757281f3..965fd68c03 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -85,6 +85,37 @@ func maintainer(b *Builder, args []string, attributes map[string]bool, original return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer)) } +// LABEL some json data describing the image +// +// Sets the Label variable foo to bar, +// +func label(b *Builder, args []string, attributes map[string]bool, original string) error { + if len(args) == 0 { + return fmt.Errorf("LABEL is missing arguments") + } + if len(args)%2 != 0 { + // should never get here, but just in case + return fmt.Errorf("Bad input to LABEL, too many args") + } + + commitStr := "LABEL" + + if b.Config.Labels == nil { + b.Config.Labels = map[string]string{} + } + + for j := 0; j < len(args); j++ { + // name ==> args[j] + // value ==> args[j+1] + newVar := args[j] + "=" + args[j+1] + "" + commitStr += " " + newVar + + b.Config.Labels[args[j]] = args[j+1] + j++ + } + return b.commit("", b.Config.Cmd, commitStr) +} + // ADD foo /path // // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling diff --git a/builder/evaluator.go b/builder/evaluator.go index eadef4a1e0..389fcc25e8 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -62,6 +62,7 @@ var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) e func init() { evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{ command.Env: env, + command.Label: label, command.Maintainer: maintainer, command.Add: add, command.Copy: dispatchCopy, // copy() is a go builtin diff --git a/builder/parser/line_parsers.go b/builder/parser/line_parsers.go index c7fed13dbe..06115e831a 100644 --- a/builder/parser/line_parsers.go +++ b/builder/parser/line_parsers.go @@ -44,10 +44,10 @@ func parseSubCommand(rest string) (*Node, map[string]bool, error) { // parse environment like statements. Note that this does *not* handle // variable interpolation, which will be handled in the evaluator. -func parseEnv(rest string) (*Node, map[string]bool, error) { +func parseNameVal(rest string, key string) (*Node, map[string]bool, error) { // This is kind of tricky because we need to support the old - // variant: ENV name value - // as well as the new one: ENV name=value ... + // 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 @@ -137,10 +137,10 @@ func parseEnv(rest string) (*Node, map[string]bool, error) { } if len(words) == 0 { - return nil, nil, fmt.Errorf("ENV requires at least one argument") + return nil, nil, fmt.Errorf(key + " requires at least one argument") } - // Old format (ENV name value) + // Old format (KEY name value) var rootnode *Node if !strings.Contains(words[0], "=") { @@ -149,7 +149,7 @@ func parseEnv(rest string) (*Node, map[string]bool, error) { strs := TOKEN_WHITESPACE.Split(rest, 2) if len(strs) < 2 { - return nil, nil, fmt.Errorf("ENV must have two arguments") + return nil, nil, fmt.Errorf(key + " must have two arguments") } node.Value = strs[0] @@ -182,6 +182,14 @@ func parseEnv(rest string) (*Node, map[string]bool, error) { return rootnode, nil, nil } +func parseEnv(rest string) (*Node, map[string]bool, error) { + return parseNameVal(rest, "ENV") +} + +func parseLabel(rest string) (*Node, map[string]bool, error) { + return parseNameVal(rest, "LABEL") +} + // 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) { diff --git a/builder/parser/parser.go b/builder/parser/parser.go index 69bbfd0dc1..1ab151b30d 100644 --- a/builder/parser/parser.go +++ b/builder/parser/parser.go @@ -50,6 +50,7 @@ func init() { command.Onbuild: parseSubCommand, command.Workdir: parseString, command.Env: parseEnv, + command.Label: parseLabel, command.Maintainer: parseString, command.From: parseString, command.Add: parseMaybeJSONToList, diff --git a/contrib/syntax/kate/Dockerfile.xml b/contrib/syntax/kate/Dockerfile.xml index e5602397ba..4fdef2393b 100644 --- a/contrib/syntax/kate/Dockerfile.xml +++ b/contrib/syntax/kate/Dockerfile.xml @@ -22,6 +22,7 @@ CMD WORKDIR USER + LABEL diff --git a/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage b/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage index 1d19a3ba2e..75efc2e811 100644 --- a/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage +++ b/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage @@ -12,7 +12,7 @@ match - ^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY)\s + ^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|LABEL|WORKDIR|COPY)\s captures 0 diff --git a/contrib/syntax/vim/syntax/dockerfile.vim b/contrib/syntax/vim/syntax/dockerfile.vim index 2984bec5f8..36691e2504 100644 --- a/contrib/syntax/vim/syntax/dockerfile.vim +++ b/contrib/syntax/vim/syntax/dockerfile.vim @@ -11,7 +11,7 @@ let b:current_syntax = "dockerfile" syntax case ignore -syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|VOLUME|WORKDIR|COPY)\s/ +syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY)\s/ highlight link dockerfileKeyword Keyword syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/ diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 051b90ce97..0919d511b7 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -71,6 +71,13 @@ This endpoint now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`. ### What's new +**New!** +Build now has support for `LABEL` command which can be used to add user data +to an image. For example you could add data describing the content of an image. + +`LABEL "Vendor"="ACME Incorporated"` + +**New!** `POST /containers/(id)/attach` and `POST /exec/(id)/start` **New!** diff --git a/docs/sources/reference/api/docker_remote_api_v1.17.md b/docs/sources/reference/api/docker_remote_api_v1.17.md index 96887559c2..f4e16b29dc 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.17.md +++ b/docs/sources/reference/api/docker_remote_api_v1.17.md @@ -129,6 +129,11 @@ Create a container ], "Entrypoint": "", "Image": "ubuntu", + "Labels": { + "Vendor": "Acme", + "License": "GPL", + "Version": "1.0" + }, "Volumes": { "/tmp": {} }, @@ -1169,6 +1174,7 @@ Return low-level information on the image `name` "Cmd": ["/bin/bash"], "Dns": null, "Image": "ubuntu", + "Labels": null, "Volumes": null, "VolumesFrom": "", "WorkingDir": "" diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 3d34db38cc..00c70e6fa1 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -328,6 +328,17 @@ default specified in `CMD`. > the result; `CMD` does not execute anything at build time, but specifies > the intended command for the image. +## LABEL + LABEL = = = ... + + --The `LABEL` instruction allows you to describe the image your `Dockerfile` +is building. `LABEL` is specified as name value pairs. This data can +be retrieved using the `docker inspect` command + + + LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" + LABEL Version="1.0" + ## EXPOSE EXPOSE [...] @@ -907,6 +918,7 @@ For example you might add something like this: FROM ubuntu MAINTAINER Victor Vieux + LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0" RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server # Firefox over VNC diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index ecef76f9da..c7749be233 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -4541,6 +4541,28 @@ func TestBuildWithTabs(t *testing.T) { logDone("build - with tabs") } +func TestBuildLabels(t *testing.T) { + name := "testbuildlabel" + expected := `{"License":"GPL","Vendor":"Acme"}` + defer deleteImages(name) + _, err := buildImage(name, + `FROM busybox + LABEL Vendor=Acme + LABEL License GPL`, + true) + if err != nil { + t.Fatal(err) + } + res, err := inspectFieldJSON(name, "Config.Labels") + if err != nil { + t.Fatal(err) + } + if res != expected { + t.Fatalf("Labels %s, expected %s", res, expected) + } + logDone("build - label") +} + func TestBuildStderr(t *testing.T) { // This test just makes sure that no non-error output goes // to stderr diff --git a/runconfig/config.go b/runconfig/config.go index ca5c3240b6..bcd3c46ec5 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -33,6 +33,8 @@ type Config struct { NetworkDisabled bool MacAddress string OnBuild []string + SecurityOpt []string + Labels map[string]string } func ContainerConfigFromJob(job *engine.Job) *Config { @@ -66,6 +68,9 @@ func ContainerConfigFromJob(job *engine.Job) *Config { if Cmd := job.GetenvList("Cmd"); Cmd != nil { config.Cmd = Cmd } + + job.GetenvJson("Labels", &config.Labels) + if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil { config.Entrypoint = Entrypoint } diff --git a/runconfig/merge.go b/runconfig/merge.go index 9bc4748446..9bbdc6ad25 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -84,6 +84,16 @@ func Merge(userConf, imageConf *Config) error { } } + if userConf.Labels == nil { + userConf.Labels = map[string]string{} + } + if imageConf.Labels != nil { + for l := range userConf.Labels { + imageConf.Labels[l] = userConf.Labels[l] + } + userConf.Labels = imageConf.Labels + } + if len(userConf.Entrypoint) == 0 { if len(userConf.Cmd) == 0 { userConf.Cmd = imageConf.Cmd