mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #9882 from ibuildthecloud/labels
Proposal: One Meta Data to Rule Them All => Labels
This commit is contained in:
commit
b6ac111abf
31 changed files with 704 additions and 22 deletions
|
@ -3,6 +3,7 @@ package command
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Env = "env"
|
Env = "env"
|
||||||
|
Label = "label"
|
||||||
Maintainer = "maintainer"
|
Maintainer = "maintainer"
|
||||||
Add = "add"
|
Add = "add"
|
||||||
Copy = "copy"
|
Copy = "copy"
|
||||||
|
|
|
@ -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))
|
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 foo /path
|
||||||
//
|
//
|
||||||
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
||||||
|
|
|
@ -62,6 +62,7 @@ var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) e
|
||||||
func init() {
|
func init() {
|
||||||
evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{
|
evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{
|
||||||
command.Env: env,
|
command.Env: env,
|
||||||
|
command.Label: label,
|
||||||
command.Maintainer: maintainer,
|
command.Maintainer: maintainer,
|
||||||
command.Add: add,
|
command.Add: add,
|
||||||
command.Copy: dispatchCopy, // copy() is a go builtin
|
command.Copy: dispatchCopy, // copy() is a go builtin
|
||||||
|
|
|
@ -44,10 +44,10 @@ func parseSubCommand(rest string) (*Node, map[string]bool, error) {
|
||||||
|
|
||||||
// parse environment like statements. Note that this does *not* handle
|
// parse environment like statements. Note that this does *not* handle
|
||||||
// variable interpolation, which will be handled in the evaluator.
|
// 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
|
// This is kind of tricky because we need to support the old
|
||||||
// variant: ENV name value
|
// variant: KEY name value
|
||||||
// as well as the new one: ENV 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
|
// The trigger to know which one is being used will be whether we hit
|
||||||
// a space or = first. space ==> old, "=" ==> new
|
// a space or = first. space ==> old, "=" ==> new
|
||||||
|
|
||||||
|
@ -137,10 +137,10 @@ func parseEnv(rest string) (*Node, map[string]bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(words) == 0 {
|
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
|
var rootnode *Node
|
||||||
|
|
||||||
if !strings.Contains(words[0], "=") {
|
if !strings.Contains(words[0], "=") {
|
||||||
|
@ -149,7 +149,7 @@ func parseEnv(rest string) (*Node, map[string]bool, error) {
|
||||||
strs := TOKEN_WHITESPACE.Split(rest, 2)
|
strs := TOKEN_WHITESPACE.Split(rest, 2)
|
||||||
|
|
||||||
if len(strs) < 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]
|
node.Value = strs[0]
|
||||||
|
@ -182,6 +182,14 @@ func parseEnv(rest string) (*Node, map[string]bool, error) {
|
||||||
return rootnode, nil, nil
|
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
|
// parses a whitespace-delimited set of arguments. The result is effectively a
|
||||||
// linked list of string arguments.
|
// linked list of string arguments.
|
||||||
func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
|
func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
|
||||||
|
|
|
@ -50,6 +50,7 @@ func init() {
|
||||||
command.Onbuild: parseSubCommand,
|
command.Onbuild: parseSubCommand,
|
||||||
command.Workdir: parseString,
|
command.Workdir: parseString,
|
||||||
command.Env: parseEnv,
|
command.Env: parseEnv,
|
||||||
|
command.Label: parseLabel,
|
||||||
command.Maintainer: parseString,
|
command.Maintainer: parseString,
|
||||||
command.From: parseString,
|
command.From: parseString,
|
||||||
command.Add: parseMaybeJSONToList,
|
command.Add: parseMaybeJSONToList,
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
<item> CMD </item>
|
<item> CMD </item>
|
||||||
<item> WORKDIR </item>
|
<item> WORKDIR </item>
|
||||||
<item> USER </item>
|
<item> USER </item>
|
||||||
|
<item> LABEL </item>
|
||||||
</list>
|
</list>
|
||||||
|
|
||||||
<contexts>
|
<contexts>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>match</key>
|
<key>match</key>
|
||||||
<string>^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY)\s</string>
|
<string>^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|LABEL|WORKDIR|COPY)\s</string>
|
||||||
<key>captures</key>
|
<key>captures</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>0</key>
|
<key>0</key>
|
||||||
|
|
|
@ -11,7 +11,7 @@ let b:current_syntax = "dockerfile"
|
||||||
|
|
||||||
syntax case ignore
|
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
|
highlight link dockerfileKeyword Keyword
|
||||||
|
|
||||||
syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/
|
syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/
|
||||||
|
|
|
@ -90,6 +90,10 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !psFilters.MatchKVList("label", container.Config.Labels) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if before != "" && !foundBefore {
|
if before != "" && !foundBefore {
|
||||||
if container.ID == beforeCont.ID {
|
if container.ID == beforeCont.ID {
|
||||||
foundBefore = true
|
foundBefore = true
|
||||||
|
@ -157,6 +161,7 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
|
||||||
out.SetInt64("SizeRw", sizeRw)
|
out.SetInt64("SizeRw", sizeRw)
|
||||||
out.SetInt64("SizeRootFs", sizeRootFs)
|
out.SetInt64("SizeRootFs", sizeRootFs)
|
||||||
}
|
}
|
||||||
|
out.SetJson("Labels", container.Config.Labels)
|
||||||
outs.Add(out)
|
outs.Add(out)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,21 @@ A Dockerfile is similar to a Makefile.
|
||||||
**CMD** executes nothing at build time, but specifies the intended command for
|
**CMD** executes nothing at build time, but specifies the intended command for
|
||||||
the image.
|
the image.
|
||||||
|
|
||||||
|
**LABEL**
|
||||||
|
-- `LABEL <key>[=<value>] [<key>[=<value>] ...]`
|
||||||
|
The **LABEL** instruction adds metadata to an image. A **LABEL** is a
|
||||||
|
key-value pair. To include spaces within a **LABEL** value, use quotes and
|
||||||
|
blackslashes as you would in command-line parsing.
|
||||||
|
|
||||||
|
```
|
||||||
|
LABEL "com.example.vendor"="ACME Incorporated"
|
||||||
|
```
|
||||||
|
|
||||||
|
An image can have more than one label. To specify multiple labels, separate each
|
||||||
|
key-value pair by a space.
|
||||||
|
|
||||||
|
To display an image's labels, use the `docker inspect` command.
|
||||||
|
|
||||||
**EXPOSE**
|
**EXPOSE**
|
||||||
-- `EXPOSE <port> [<port>...]`
|
-- `EXPOSE <port> [<port>...]`
|
||||||
The **EXPOSE** instruction informs Docker that the container listens on the
|
The **EXPOSE** instruction informs Docker that the container listens on the
|
||||||
|
|
|
@ -24,6 +24,8 @@ docker-create - Create a new container
|
||||||
[**--help**]
|
[**--help**]
|
||||||
[**-i**|**--interactive**[=*false*]]
|
[**-i**|**--interactive**[=*false*]]
|
||||||
[**--ipc**[=*IPC*]]
|
[**--ipc**[=*IPC*]]
|
||||||
|
[**-l**|**--label**[=*[]*]]
|
||||||
|
[**--label-file**[=*[]*]]
|
||||||
[**--link**[=*[]*]]
|
[**--link**[=*[]*]]
|
||||||
[**--lxc-conf**[=*[]*]]
|
[**--lxc-conf**[=*[]*]]
|
||||||
[**-m**|**--memory**[=*MEMORY*]]
|
[**-m**|**--memory**[=*MEMORY*]]
|
||||||
|
@ -102,6 +104,12 @@ IMAGE [COMMAND] [ARG...]
|
||||||
'container:<name|id>': reuses another container shared memory, semaphores and message queues
|
'container:<name|id>': reuses another container shared memory, semaphores and message queues
|
||||||
'host': use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure.
|
'host': use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure.
|
||||||
|
|
||||||
|
**-l**, **--label**=[]
|
||||||
|
Set metadata on the container (e.g., --label=com.example.key=value)
|
||||||
|
|
||||||
|
**--label-file**=[]
|
||||||
|
Read in a file of labels (EOL delimited)
|
||||||
|
|
||||||
**--link**=[]
|
**--link**=[]
|
||||||
Add link to another container in the form of <name or id>:alias
|
Add link to another container in the form of <name or id>:alias
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ versions.
|
||||||
Show all images (by default filter out the intermediate image layers). The default is *false*.
|
Show all images (by default filter out the intermediate image layers). The default is *false*.
|
||||||
|
|
||||||
**-f**, **--filter**=[]
|
**-f**, **--filter**=[]
|
||||||
Provide filter values (i.e., 'dangling=true')
|
Filters the output. The dangling=true filter finds unused images. While label=com.foo=amd64 filters for images with a com.foo value of amd64. The label=com.foo filter finds images with the label com.foo of any value.
|
||||||
|
|
||||||
**--help**
|
**--help**
|
||||||
Print usage statement
|
Print usage statement
|
||||||
|
|
|
@ -83,6 +83,11 @@ To get information on a container use it's ID or instance name:
|
||||||
"Ghost": false
|
"Ghost": false
|
||||||
},
|
},
|
||||||
"Image": "df53773a4390e25936f9fd3739e0c0e60a62d024ea7b669282b27e65ae8458e6",
|
"Image": "df53773a4390e25936f9fd3739e0c0e60a62d024ea7b669282b27e65ae8458e6",
|
||||||
|
"Labels": {
|
||||||
|
"com.example.vendor": "Acme",
|
||||||
|
"com.example.license": "GPL",
|
||||||
|
"com.example.version": "1.0"
|
||||||
|
},
|
||||||
"NetworkSettings": {
|
"NetworkSettings": {
|
||||||
"IPAddress": "172.17.0.2",
|
"IPAddress": "172.17.0.2",
|
||||||
"IPPrefixLen": 16,
|
"IPPrefixLen": 16,
|
||||||
|
|
|
@ -36,6 +36,7 @@ the running containers.
|
||||||
**-f**, **--filter**=[]
|
**-f**, **--filter**=[]
|
||||||
Provide filter values. Valid filters:
|
Provide filter values. Valid filters:
|
||||||
exited=<int> - containers with exit code of <int>
|
exited=<int> - containers with exit code of <int>
|
||||||
|
label=<key> or label=<key>=<value>
|
||||||
status=(restarting|running|paused|exited)
|
status=(restarting|running|paused|exited)
|
||||||
name=<string> - container's name
|
name=<string> - container's name
|
||||||
id=<ID> - container's ID
|
id=<ID> - container's ID
|
||||||
|
|
|
@ -25,6 +25,8 @@ docker-run - Run a command in a new container
|
||||||
[**--help**]
|
[**--help**]
|
||||||
[**-i**|**--interactive**[=*false*]]
|
[**-i**|**--interactive**[=*false*]]
|
||||||
[**--ipc**[=*IPC*]]
|
[**--ipc**[=*IPC*]]
|
||||||
|
[**-l**|**--label**[=*[]*]]
|
||||||
|
[**--label-file**[=*[]*]]
|
||||||
[**--link**[=*[]*]]
|
[**--link**[=*[]*]]
|
||||||
[**--lxc-conf**[=*[]*]]
|
[**--lxc-conf**[=*[]*]]
|
||||||
[**-m**|**--memory**[=*MEMORY*]]
|
[**-m**|**--memory**[=*MEMORY*]]
|
||||||
|
@ -197,6 +199,12 @@ ENTRYPOINT.
|
||||||
'container:<name|id>': reuses another container shared memory, semaphores and message queues
|
'container:<name|id>': reuses another container shared memory, semaphores and message queues
|
||||||
'host': use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure.
|
'host': use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure.
|
||||||
|
|
||||||
|
**-l**, **--label**=[]
|
||||||
|
Set metadata on the container (e.g., --label com.example.key=value)
|
||||||
|
|
||||||
|
**--label-file**=[]
|
||||||
|
Read in a line delimited file of labels
|
||||||
|
|
||||||
**--link**=[]
|
**--link**=[]
|
||||||
Add link to another container in the form of <name or id>:alias
|
Add link to another container in the form of <name or id>:alias
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@ pages:
|
||||||
- ['userguide/dockerimages.md', 'User Guide', 'Working with Docker Images' ]
|
- ['userguide/dockerimages.md', 'User Guide', 'Working with Docker Images' ]
|
||||||
- ['userguide/dockerlinks.md', 'User Guide', 'Linking containers together' ]
|
- ['userguide/dockerlinks.md', 'User Guide', 'Linking containers together' ]
|
||||||
- ['userguide/dockervolumes.md', 'User Guide', 'Managing data in containers' ]
|
- ['userguide/dockervolumes.md', 'User Guide', 'Managing data in containers' ]
|
||||||
|
- ['userguide/labels-custom-metadata.md', 'User Guide', 'Labels - custom metadata in Docker' ]
|
||||||
- ['userguide/dockerrepos.md', 'User Guide', 'Working with Docker Hub' ]
|
- ['userguide/dockerrepos.md', 'User Guide', 'Working with Docker Hub' ]
|
||||||
- ['userguide/level1.md', '**HIDDEN**' ]
|
- ['userguide/level1.md', '**HIDDEN**' ]
|
||||||
- ['userguide/level2.md', '**HIDDEN**' ]
|
- ['userguide/level2.md', '**HIDDEN**' ]
|
||||||
|
|
|
@ -71,15 +71,32 @@ This endpoint now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`.
|
||||||
|
|
||||||
### What's new
|
### What's new
|
||||||
|
|
||||||
|
The build supports `LABEL` command. Use this to add metadata
|
||||||
|
to an image. For example you could add data describing the content of an image.
|
||||||
|
|
||||||
|
`LABEL "com.example.vendor"="ACME Incorporated"`
|
||||||
|
|
||||||
|
**New!**
|
||||||
`POST /containers/(id)/attach` and `POST /exec/(id)/start`
|
`POST /containers/(id)/attach` and `POST /exec/(id)/start`
|
||||||
|
|
||||||
**New!**
|
**New!**
|
||||||
Docker client now hints potential proxies about connection hijacking using HTTP Upgrade headers.
|
Docker client now hints potential proxies about connection hijacking using HTTP Upgrade headers.
|
||||||
|
|
||||||
|
`POST /containers/create`
|
||||||
|
|
||||||
|
**New!**
|
||||||
|
You can set labels on container create describing the container.
|
||||||
|
|
||||||
|
`GET /containers/json`
|
||||||
|
|
||||||
|
**New!**
|
||||||
|
The endpoint returns the labels associated with the containers (`Labels`).
|
||||||
|
|
||||||
`GET /containers/(id)/json`
|
`GET /containers/(id)/json`
|
||||||
|
|
||||||
**New!**
|
**New!**
|
||||||
This endpoint now returns the list current execs associated with the container (`ExecIDs`).
|
This endpoint now returns the list current execs associated with the container (`ExecIDs`).
|
||||||
|
This endpoint now returns the container labels (`Config.Labels`).
|
||||||
|
|
||||||
`POST /containers/(id)/rename`
|
`POST /containers/(id)/rename`
|
||||||
|
|
||||||
|
@ -98,6 +115,12 @@ root filesystem as read only.
|
||||||
**New!**
|
**New!**
|
||||||
This endpoint returns a live stream of a container's resource usage statistics.
|
This endpoint returns a live stream of a container's resource usage statistics.
|
||||||
|
|
||||||
|
`GET /images/json`
|
||||||
|
|
||||||
|
**New!**
|
||||||
|
This endpoint now returns the labels associated with each image (`Labels`).
|
||||||
|
|
||||||
|
|
||||||
## v1.16
|
## v1.16
|
||||||
|
|
||||||
### Full Documentation
|
### Full Documentation
|
||||||
|
|
|
@ -125,6 +125,11 @@ Create a container
|
||||||
],
|
],
|
||||||
"Entrypoint": "",
|
"Entrypoint": "",
|
||||||
"Image": "ubuntu",
|
"Image": "ubuntu",
|
||||||
|
"Labels": {
|
||||||
|
"com.example.vendor": "Acme",
|
||||||
|
"com.example.license": "GPL",
|
||||||
|
"com.example.version": "1.0"
|
||||||
|
},
|
||||||
"Volumes": {
|
"Volumes": {
|
||||||
"/tmp": {}
|
"/tmp": {}
|
||||||
},
|
},
|
||||||
|
@ -191,6 +196,7 @@ Json Parameters:
|
||||||
- **OpenStdin** - Boolean value, opens stdin,
|
- **OpenStdin** - Boolean value, opens stdin,
|
||||||
- **StdinOnce** - Boolean value, close stdin after the 1 attached client disconnects.
|
- **StdinOnce** - Boolean value, close stdin after the 1 attached client disconnects.
|
||||||
- **Env** - A list of environment variables in the form of `VAR=value`
|
- **Env** - A list of environment variables in the form of `VAR=value`
|
||||||
|
- **Labels** - Adds a map of labels that to a container. To specify a map: `{"key":"value"[,"key2":"value2"]}`
|
||||||
- **Cmd** - Command to run specified as a string or an array of strings.
|
- **Cmd** - Command to run specified as a string or an array of strings.
|
||||||
- **Entrypoint** - Set the entrypoint for the container a a string or an array
|
- **Entrypoint** - Set the entrypoint for the container a a string or an array
|
||||||
of strings
|
of strings
|
||||||
|
@ -301,6 +307,11 @@ Return low-level information on the container `id`
|
||||||
"ExposedPorts": null,
|
"ExposedPorts": null,
|
||||||
"Hostname": "ba033ac44011",
|
"Hostname": "ba033ac44011",
|
||||||
"Image": "ubuntu",
|
"Image": "ubuntu",
|
||||||
|
"Labels": {
|
||||||
|
"com.example.vendor": "Acme",
|
||||||
|
"com.example.license": "GPL",
|
||||||
|
"com.example.version": "1.0"
|
||||||
|
},
|
||||||
"MacAddress": "",
|
"MacAddress": "",
|
||||||
"NetworkDisabled": false,
|
"NetworkDisabled": false,
|
||||||
"OnBuild": null,
|
"OnBuild": null,
|
||||||
|
@ -1185,6 +1196,11 @@ Return low-level information on the image `name`
|
||||||
"Cmd": ["/bin/bash"],
|
"Cmd": ["/bin/bash"],
|
||||||
"Dns": null,
|
"Dns": null,
|
||||||
"Image": "ubuntu",
|
"Image": "ubuntu",
|
||||||
|
"Labels": {
|
||||||
|
"com.example.vendor": "Acme",
|
||||||
|
"com.example.license": "GPL",
|
||||||
|
"com.example.version": "1.0"
|
||||||
|
},
|
||||||
"Volumes": null,
|
"Volumes": null,
|
||||||
"VolumesFrom": "",
|
"VolumesFrom": "",
|
||||||
"WorkingDir": ""
|
"WorkingDir": ""
|
||||||
|
|
|
@ -328,6 +328,27 @@ default specified in `CMD`.
|
||||||
> the result; `CMD` does not execute anything at build time, but specifies
|
> the result; `CMD` does not execute anything at build time, but specifies
|
||||||
> the intended command for the image.
|
> the intended command for the image.
|
||||||
|
|
||||||
|
## LABEL
|
||||||
|
|
||||||
|
LABEL <key>=<value> <key>=<value> <key>=<value> ...
|
||||||
|
|
||||||
|
The `LABEL` instruction adds metadata to an image. A `LABEL` is a
|
||||||
|
key-value pair. To include spaces within a `LABEL` value, use quotes and
|
||||||
|
blackslashes as you would in command-line parsing.
|
||||||
|
|
||||||
|
LABEL "com.example.vendor"="ACME Incorporated"
|
||||||
|
|
||||||
|
An image can have more than one label. To specify multiple labels, separate each
|
||||||
|
key-value pair by an EOL.
|
||||||
|
|
||||||
|
LABEL com.example.label-without-value
|
||||||
|
LABEL com.example.label-with-value="foo"
|
||||||
|
LABEL version="1.0"
|
||||||
|
LABEL description="This text illustrates \
|
||||||
|
that label-values can span multiple lines."
|
||||||
|
|
||||||
|
To view an image's labels, use the `docker inspect` command.
|
||||||
|
|
||||||
## EXPOSE
|
## EXPOSE
|
||||||
|
|
||||||
EXPOSE <port> [<port>...]
|
EXPOSE <port> [<port>...]
|
||||||
|
@ -907,6 +928,7 @@ For example you might add something like this:
|
||||||
FROM ubuntu
|
FROM ubuntu
|
||||||
MAINTAINER Victor Vieux <victor@docker.com>
|
MAINTAINER Victor Vieux <victor@docker.com>
|
||||||
|
|
||||||
|
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
|
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
|
||||||
|
|
||||||
# Firefox over VNC
|
# Firefox over VNC
|
||||||
|
|
|
@ -814,6 +814,8 @@ Creates a new container.
|
||||||
-h, --hostname="" Container host name
|
-h, --hostname="" Container host name
|
||||||
-i, --interactive=false Keep STDIN open even if not attached
|
-i, --interactive=false Keep STDIN open even if not attached
|
||||||
--ipc="" IPC namespace to use
|
--ipc="" IPC namespace to use
|
||||||
|
-l, --label=[] Set metadata on the container (e.g., --label=com.example.key=value)
|
||||||
|
--label-file=[] Read in a line delimited file of labels
|
||||||
--link=[] Add link to another container
|
--link=[] Add link to another container
|
||||||
--lxc-conf=[] Add custom lxc options
|
--lxc-conf=[] Add custom lxc options
|
||||||
-m, --memory="" Memory limit
|
-m, --memory="" Memory limit
|
||||||
|
@ -1166,6 +1168,7 @@ than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "b
|
||||||
|
|
||||||
Current filters:
|
Current filters:
|
||||||
* dangling (boolean - true or false)
|
* dangling (boolean - true or false)
|
||||||
|
* label (`label=<key>` or `label=<key>=<value>`)
|
||||||
|
|
||||||
##### Untagged images
|
##### Untagged images
|
||||||
|
|
||||||
|
@ -1685,6 +1688,8 @@ removed before the image is removed.
|
||||||
--link=[] Add link to another container
|
--link=[] Add link to another container
|
||||||
--lxc-conf=[] Add custom lxc options
|
--lxc-conf=[] Add custom lxc options
|
||||||
-m, --memory="" Memory limit
|
-m, --memory="" Memory limit
|
||||||
|
-l, --label=[] Set metadata on the container (e.g., --label=com.example.key=value)
|
||||||
|
--label-file=[] Read in a file of labels (EOL delimited)
|
||||||
--mac-address="" Container MAC address (e.g. 92:d0:c6:0a:29:33)
|
--mac-address="" Container MAC address (e.g. 92:d0:c6:0a:29:33)
|
||||||
--memory-swap="" Total memory (memory + swap), '-1' to disable swap
|
--memory-swap="" Total memory (memory + swap), '-1' to disable swap
|
||||||
--name="" Assign a name to the container
|
--name="" Assign a name to the container
|
||||||
|
@ -1855,8 +1860,39 @@ An example of a file passed with `--env-file`
|
||||||
|
|
||||||
$ sudo docker run --name console -t -i ubuntu bash
|
$ sudo docker run --name console -t -i ubuntu bash
|
||||||
|
|
||||||
This will create and run a new container with the container name being
|
A label is a a `key=value` pair that applies metadata to a container. To label a container with two labels:
|
||||||
`console`.
|
|
||||||
|
$ sudo docker run -l my-label --label com.example.foo=bar ubuntu bash
|
||||||
|
|
||||||
|
The `my-label` key doesn't specify so the label defaults to an empty
|
||||||
|
string(`""`). To add multiple labels, repeat the label flag (`-l` or
|
||||||
|
`--label`).
|
||||||
|
|
||||||
|
The `key=value` must be unique. If you specify the same key multiple times
|
||||||
|
with different values, each subsequent value overwrites the previous. Docker
|
||||||
|
applies the last `key=value` you supply.
|
||||||
|
|
||||||
|
Use the `--label-file` flag to load multiple labels from a file. Delimit each
|
||||||
|
label in the file with an EOL mark. The example below loads labels from a
|
||||||
|
labels file in the current directory;
|
||||||
|
|
||||||
|
$ sudo docker run --label-file ./labels ubuntu bash
|
||||||
|
|
||||||
|
The label-file format is similar to the format for loading environment variables
|
||||||
|
(see `--env-file` above). The following example illustrates a label-file format;
|
||||||
|
|
||||||
|
com.example.label1="a label"
|
||||||
|
|
||||||
|
# this is a comment
|
||||||
|
com.example.label2=another\ label
|
||||||
|
com.example.label3
|
||||||
|
|
||||||
|
You can load multiple label-files by supplying the `--label-file` flag multiple
|
||||||
|
times.
|
||||||
|
|
||||||
|
For additional information on working with labels, see
|
||||||
|
[*Labels - custom metadata in Docker*](/userguide/labels-custom-metadata/) in
|
||||||
|
the Docker User Guide.
|
||||||
|
|
||||||
$ sudo docker run --link /redis:redis --name console ubuntu bash
|
$ sudo docker run --link /redis:redis --name console ubuntu bash
|
||||||
|
|
||||||
|
|
194
docs/sources/userguide/labels-custom-metadata.md
Normal file
194
docs/sources/userguide/labels-custom-metadata.md
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
page_title: Labels - custom metadata in Docker
|
||||||
|
page_description: Learn how to work with custom metadata in Docker, using labels.
|
||||||
|
page_keywords: Usage, user guide, labels, metadata, docker, documentation, examples, annotating
|
||||||
|
|
||||||
|
## Labels - custom metadata in Docker
|
||||||
|
|
||||||
|
You can add metadata to your images, containers, and daemons via
|
||||||
|
labels. Metadata can serve a wide range of uses. Use them to add notes or
|
||||||
|
licensing information to an image or to identify a host.
|
||||||
|
|
||||||
|
A label is a `<key>` / `<value>` pair. Docker stores the values as *strings*.
|
||||||
|
You can specify multiple labels but each `<key>` / `<value>` must be unique. If
|
||||||
|
you specify the same `key` multiple times with different values, each subsequent
|
||||||
|
value overwrites the previous. Docker applies the last `key=value` you supply.
|
||||||
|
|
||||||
|
>**note:** Support for daemon-labels was added in Docker 1.4.1. Labels on
|
||||||
|
>containers and images are new in Docker 1.6.0
|
||||||
|
|
||||||
|
### Naming your labels - namespaces
|
||||||
|
|
||||||
|
Docker puts no hard restrictions on the label `key` you. However, labels can
|
||||||
|
conflict. For example, you can categorize your images by using a chip "architecture"
|
||||||
|
label:
|
||||||
|
|
||||||
|
LABEL architecture="amd64"
|
||||||
|
|
||||||
|
LABEL architecture="ARMv7"
|
||||||
|
|
||||||
|
But a user can label images by building architectural style:
|
||||||
|
|
||||||
|
LABEL architecture="Art Nouveau"
|
||||||
|
|
||||||
|
To prevent such conflicts, Docker namespaces label keys using a reverse domain
|
||||||
|
notation. This notation has the following guidelines:
|
||||||
|
|
||||||
|
|
||||||
|
- All (third-party) tools should prefix their keys with the
|
||||||
|
reverse DNS notation of a domain controlled by the author. For
|
||||||
|
example, `com.example.some-label`.
|
||||||
|
|
||||||
|
- The `com.docker.*`, `io.docker.*` and `com.dockerproject.*` namespaces are
|
||||||
|
reserved for Docker's internal use.
|
||||||
|
|
||||||
|
- Keys should only consist of lower-cased alphanumeric characters,
|
||||||
|
dots and dashes (for example, `[a-z0-9-.]`)
|
||||||
|
|
||||||
|
- Keys should start *and* end with an alpha numeric character
|
||||||
|
|
||||||
|
- Keys may not contain consecutive dots or dashes.
|
||||||
|
|
||||||
|
- Keys *without* namespace (dots) are reserved for CLI use. This allows end-
|
||||||
|
users to add metadata to their containers and images, without having to type
|
||||||
|
cumbersome namespaces on the command-line.
|
||||||
|
|
||||||
|
|
||||||
|
These are guidelines and are not enforced. Docker does not *enforce* them.
|
||||||
|
Failing following these guidelines can result in conflicting labels. If you're
|
||||||
|
building a tool that uses labels, you *should* use namespaces for your label keys.
|
||||||
|
|
||||||
|
|
||||||
|
### Storing structured data in labels
|
||||||
|
|
||||||
|
Label values can contain any data type as long as the value can be stored as a
|
||||||
|
string. For example, consider this JSON:
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"Description": "A containerized foobar",
|
||||||
|
"Usage": "docker run --rm example/foobar [args]",
|
||||||
|
"License": "GPL",
|
||||||
|
"Version": "0.0.1-beta",
|
||||||
|
"aBoolean": true,
|
||||||
|
"aNumber" : 0.01234,
|
||||||
|
"aNestedArray": ["a", "b", "c"]
|
||||||
|
}
|
||||||
|
|
||||||
|
You can store this struct in a label by serializing it to a string first:
|
||||||
|
|
||||||
|
LABEL com.example.image-specs="{\"Description\":\"A containerized foobar\",\"Usage\":\"docker run --rm example\\/foobar [args]\",\"License\":\"GPL\",\"Version\":\"0.0.1-beta\",\"aBoolean\":true,\"aNumber\":0.01234,\"aNestedArray\":[\"a\",\"b\",\"c\"]}"
|
||||||
|
|
||||||
|
While it is *possible* to store structured data in label values, Docker treats this
|
||||||
|
data as a 'regular' string. This means that Docker doesn't offer ways to query
|
||||||
|
(filter) based on nested properties.
|
||||||
|
|
||||||
|
If your tool needs to filter on nested properties, the tool itself should
|
||||||
|
implement this.
|
||||||
|
|
||||||
|
|
||||||
|
### Adding labels to images; the `LABEL` instruction
|
||||||
|
|
||||||
|
Adding labels to an image:
|
||||||
|
|
||||||
|
|
||||||
|
LABEL [<namespace>.]<key>[=<value>] ...
|
||||||
|
|
||||||
|
The `LABEL` instruction adds a label to your image, optionally setting its value.
|
||||||
|
Use surrounding quotes or backslashes for labels that contain
|
||||||
|
white space character:
|
||||||
|
|
||||||
|
LABEL vendor=ACME\ Incorporated
|
||||||
|
LABEL com.example.version.is-beta
|
||||||
|
LABEL com.example.version="0.0.1-beta"
|
||||||
|
LABEL com.example.release-date="2015-02-12"
|
||||||
|
|
||||||
|
The `LABEL` instruction supports setting multiple labels in a single instruction
|
||||||
|
using this notation;
|
||||||
|
|
||||||
|
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"
|
||||||
|
|
||||||
|
Wrapping is allowed by using a backslash (`\`) as continuation marker:
|
||||||
|
|
||||||
|
LABEL vendor=ACME\ Incorporated \
|
||||||
|
com.example.is-beta \
|
||||||
|
com.example.version="0.0.1-beta" \
|
||||||
|
com.example.release-date="2015-02-12"
|
||||||
|
|
||||||
|
Docker recommends combining labels in a single `LABEL` instruction instead of
|
||||||
|
using a `LABEL` instruction for each label. Each instruction in a Dockerfile
|
||||||
|
produces a new layer that can result in an inefficient image if you use many
|
||||||
|
labels.
|
||||||
|
|
||||||
|
You can view the labels via the `docker inspect` command:
|
||||||
|
|
||||||
|
$ docker inspect 4fa6e0f0c678
|
||||||
|
|
||||||
|
...
|
||||||
|
"Labels": {
|
||||||
|
"vendor": "ACME Incorporated",
|
||||||
|
"com.example.is-beta": "",
|
||||||
|
"com.example.version": "0.0.1-beta",
|
||||||
|
"com.example.release-date": "2015-02-12"
|
||||||
|
}
|
||||||
|
...
|
||||||
|
|
||||||
|
$ docker inspect -f "{{json .Labels }}" 4fa6e0f0c678
|
||||||
|
|
||||||
|
{"Vendor":"ACME Incorporated","com.example.is-beta":"","com.example.version":"0.0.1-beta","com.example.release-date":"2015-02-12"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Querying labels
|
||||||
|
|
||||||
|
Besides storing metadata, you can filter images and labels by label. To list all
|
||||||
|
running containers that have a `com.example.is-beta` label:
|
||||||
|
|
||||||
|
# List all running containers that have a `com.example.is-beta` label
|
||||||
|
$ docker ps --filter "label=com.example.is-beta"
|
||||||
|
|
||||||
|
List all running containers with a `color` label of `blue`:
|
||||||
|
|
||||||
|
$ docker ps --filter "label=color=blue"
|
||||||
|
|
||||||
|
List all images with `vendor` `ACME`:
|
||||||
|
|
||||||
|
$ docker images --filter "label=vendor=ACME"
|
||||||
|
|
||||||
|
|
||||||
|
### Daemon labels
|
||||||
|
|
||||||
|
|
||||||
|
docker -d \
|
||||||
|
--dns 8.8.8.8 \
|
||||||
|
--dns 8.8.4.4 \
|
||||||
|
-H unix:///var/run/docker.sock \
|
||||||
|
--label com.example.environment="production" \
|
||||||
|
--label com.example.storage="ssd"
|
||||||
|
|
||||||
|
These labels appear as part of the `docker info` output for the daemon:
|
||||||
|
|
||||||
|
docker -D info
|
||||||
|
Containers: 12
|
||||||
|
Images: 672
|
||||||
|
Storage Driver: aufs
|
||||||
|
Root Dir: /var/lib/docker/aufs
|
||||||
|
Backing Filesystem: extfs
|
||||||
|
Dirs: 697
|
||||||
|
Execution Driver: native-0.2
|
||||||
|
Kernel Version: 3.13.0-32-generic
|
||||||
|
Operating System: Ubuntu 14.04.1 LTS
|
||||||
|
CPUs: 1
|
||||||
|
Total Memory: 994.1 MiB
|
||||||
|
Name: docker.example.com
|
||||||
|
ID: RC3P:JTCT:32YS:XYSB:YUBG:VFED:AAJZ:W3YW:76XO:D7NN:TEVU:UCRW
|
||||||
|
Debug mode (server): false
|
||||||
|
Debug mode (client): true
|
||||||
|
Fds: 11
|
||||||
|
Goroutines: 14
|
||||||
|
EventsListeners: 0
|
||||||
|
Init Path: /usr/bin/docker
|
||||||
|
Docker Root Dir: /var/lib/docker
|
||||||
|
WARNING: No swap limit support
|
||||||
|
Labels:
|
||||||
|
com.example.environment=production
|
||||||
|
com.example.storage=ssd
|
|
@ -11,13 +11,17 @@ import (
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
"github.com/docker/docker/pkg/parsers/filters"
|
||||||
)
|
)
|
||||||
|
|
||||||
var acceptedImageFilterTags = map[string]struct{}{"dangling": {}}
|
var acceptedImageFilterTags = map[string]struct{}{
|
||||||
|
"dangling": {},
|
||||||
|
"label": {},
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
||||||
var (
|
var (
|
||||||
allImages map[string]*image.Image
|
allImages map[string]*image.Image
|
||||||
err error
|
err error
|
||||||
filt_tagged = true
|
filt_tagged = true
|
||||||
|
filt_label = false
|
||||||
)
|
)
|
||||||
|
|
||||||
imageFilters, err := filters.FromParam(job.Getenv("filters"))
|
imageFilters, err := filters.FromParam(job.Getenv("filters"))
|
||||||
|
@ -38,6 +42,8 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, filt_label = imageFilters["label"]
|
||||||
|
|
||||||
if job.GetenvBool("all") && filt_tagged {
|
if job.GetenvBool("all") && filt_tagged {
|
||||||
allImages, err = s.graph.Map()
|
allImages, err = s.graph.Map()
|
||||||
} else {
|
} else {
|
||||||
|
@ -68,6 +74,9 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
||||||
} else {
|
} else {
|
||||||
// get the boolean list for if only the untagged images are requested
|
// get the boolean list for if only the untagged images are requested
|
||||||
delete(allImages, id)
|
delete(allImages, id)
|
||||||
|
if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if filt_tagged {
|
if filt_tagged {
|
||||||
out := &engine.Env{}
|
out := &engine.Env{}
|
||||||
out.SetJson("ParentId", image.Parent)
|
out.SetJson("ParentId", image.Parent)
|
||||||
|
@ -76,6 +85,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
||||||
out.SetInt64("Created", image.Created.Unix())
|
out.SetInt64("Created", image.Created.Unix())
|
||||||
out.SetInt64("Size", image.Size)
|
out.SetInt64("Size", image.Size)
|
||||||
out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
||||||
|
out.SetJson("Labels", image.ContainerConfig.Labels)
|
||||||
lookup[id] = out
|
lookup[id] = out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,8 +100,11 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display images which aren't part of a repository/tag
|
// Display images which aren't part of a repository/tag
|
||||||
if job.Getenv("filter") == "" {
|
if job.Getenv("filter") == "" || filt_label {
|
||||||
for _, image := range allImages {
|
for _, image := range allImages {
|
||||||
|
if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
out := &engine.Env{}
|
out := &engine.Env{}
|
||||||
out.SetJson("ParentId", image.Parent)
|
out.SetJson("ParentId", image.Parent)
|
||||||
out.SetList("RepoTags", []string{"<none>:<none>"})
|
out.SetList("RepoTags", []string{"<none>:<none>"})
|
||||||
|
@ -99,6 +112,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
||||||
out.SetInt64("Created", image.Created.Unix())
|
out.SetInt64("Created", image.Created.Unix())
|
||||||
out.SetInt64("Size", image.Size)
|
out.SetInt64("Size", image.Size)
|
||||||
out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
||||||
|
out.SetJson("Labels", image.ContainerConfig.Labels)
|
||||||
outs.Add(out)
|
outs.Add(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4541,6 +4541,28 @@ func TestBuildWithTabs(t *testing.T) {
|
||||||
logDone("build - with tabs")
|
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) {
|
func TestBuildStderr(t *testing.T) {
|
||||||
// This test just makes sure that no non-error output goes
|
// This test just makes sure that no non-error output goes
|
||||||
// to stderr
|
// to stderr
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -249,3 +250,57 @@ func TestCreateVolumesCreated(t *testing.T) {
|
||||||
|
|
||||||
logDone("create - volumes are created")
|
logDone("create - volumes are created")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateLabels(t *testing.T) {
|
||||||
|
name := "test_create_labels"
|
||||||
|
expected := map[string]string{"k1": "v1", "k2": "v2"}
|
||||||
|
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k1=v1", "--label", "k2=v2", "busybox")); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := make(map[string]string)
|
||||||
|
err := inspectFieldAndMarshall(name, "Config.Labels", &actual)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Fatalf("Expected %s got %s", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAllContainers()
|
||||||
|
|
||||||
|
logDone("create - labels")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateLabelFromImage(t *testing.T) {
|
||||||
|
imageName := "testcreatebuildlabel"
|
||||||
|
defer deleteImages(imageName)
|
||||||
|
_, err := buildImage(imageName,
|
||||||
|
`FROM busybox
|
||||||
|
LABEL k1=v1 k2=v2`,
|
||||||
|
true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := "test_create_labels_from_image"
|
||||||
|
expected := map[string]string{"k2": "x", "k3": "v3", "k1": "v1"}
|
||||||
|
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k2=x", "--label", "k3=v3", imageName)); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := make(map[string]string)
|
||||||
|
err = inspectFieldAndMarshall(name, "Config.Labels", &actual)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Fatalf("Expected %s got %s", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAllContainers()
|
||||||
|
|
||||||
|
logDone("create - labels from image")
|
||||||
|
}
|
||||||
|
|
|
@ -77,6 +77,60 @@ func TestImagesErrorWithInvalidFilterNameTest(t *testing.T) {
|
||||||
logDone("images - invalid filter name check working")
|
logDone("images - invalid filter name check working")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImagesFilterLabel(t *testing.T) {
|
||||||
|
imageName1 := "images_filter_test1"
|
||||||
|
imageName2 := "images_filter_test2"
|
||||||
|
imageName3 := "images_filter_test3"
|
||||||
|
defer deleteAllContainers()
|
||||||
|
defer deleteImages(imageName1)
|
||||||
|
defer deleteImages(imageName2)
|
||||||
|
defer deleteImages(imageName3)
|
||||||
|
image1ID, err := buildImage(imageName1,
|
||||||
|
`FROM scratch
|
||||||
|
LABEL match me`, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
image2ID, err := buildImage(imageName2,
|
||||||
|
`FROM scratch
|
||||||
|
LABEL match="me too"`, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
image3ID, err := buildImage(imageName3,
|
||||||
|
`FROM scratch
|
||||||
|
LABEL nomatch me`, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match")
|
||||||
|
out, _, err := runCommandWithOutput(cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
out = strings.TrimSpace(out)
|
||||||
|
|
||||||
|
if (!strings.Contains(out, image1ID) && !strings.Contains(out, image2ID)) || strings.Contains(out, image3ID) {
|
||||||
|
t.Fatalf("Expected ids %s,%s got %s", image1ID, image2ID, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match=me too")
|
||||||
|
out, _, err = runCommandWithOutput(cmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
out = strings.TrimSpace(out)
|
||||||
|
|
||||||
|
if out != image2ID {
|
||||||
|
t.Fatalf("Expected %s got %s", image2ID, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
logDone("images - filter label")
|
||||||
|
}
|
||||||
|
|
||||||
func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) {
|
func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) {
|
||||||
imageName := "images_filter_test"
|
imageName := "images_filter_test"
|
||||||
defer deleteAllContainers()
|
defer deleteAllContainers()
|
||||||
|
|
|
@ -412,6 +412,74 @@ func TestPsListContainersFilterName(t *testing.T) {
|
||||||
logDone("ps - test ps filter name")
|
logDone("ps - test ps filter name")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPsListContainersFilterLabel(t *testing.T) {
|
||||||
|
// start container
|
||||||
|
runCmd := exec.Command(dockerBinary, "run", "-d", "-l", "match=me", "-l", "second=tag", "busybox")
|
||||||
|
out, _, err := runCommandWithOutput(runCmd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
firstID := stripTrailingCharacters(out)
|
||||||
|
|
||||||
|
// start another container
|
||||||
|
runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "match=me too", "busybox")
|
||||||
|
if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
secondID := stripTrailingCharacters(out)
|
||||||
|
|
||||||
|
// start third container
|
||||||
|
runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "nomatch=me", "busybox")
|
||||||
|
if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
thirdID := stripTrailingCharacters(out)
|
||||||
|
|
||||||
|
// filter containers by exact match
|
||||||
|
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me")
|
||||||
|
if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
containerOut := strings.TrimSpace(out)
|
||||||
|
if containerOut != firstID {
|
||||||
|
t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter containers by two labels
|
||||||
|
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me", "--filter=label=second=tag")
|
||||||
|
if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
containerOut = strings.TrimSpace(out)
|
||||||
|
if containerOut != firstID {
|
||||||
|
t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter containers by two labels, but expect not found because of AND behavior
|
||||||
|
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me", "--filter=label=second=tag-no")
|
||||||
|
if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
containerOut = strings.TrimSpace(out)
|
||||||
|
if containerOut != "" {
|
||||||
|
t.Fatalf("Expected nothing, got %s for exited filter, output: %q", containerOut, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter containers by exact key
|
||||||
|
runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match")
|
||||||
|
if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
||||||
|
t.Fatal(out, err)
|
||||||
|
}
|
||||||
|
containerOut = strings.TrimSpace(out)
|
||||||
|
if (!strings.Contains(containerOut, firstID) || !strings.Contains(containerOut, secondID)) || strings.Contains(containerOut, thirdID) {
|
||||||
|
t.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAllContainers()
|
||||||
|
|
||||||
|
logDone("ps - test ps filter label")
|
||||||
|
}
|
||||||
|
|
||||||
func TestPsListContainersFilterExited(t *testing.T) {
|
func TestPsListContainersFilterExited(t *testing.T) {
|
||||||
defer deleteAllContainers()
|
defer deleteAllContainers()
|
||||||
|
|
||||||
|
|
|
@ -724,6 +724,15 @@ COPY . /static`); err != nil {
|
||||||
ctx: ctx}, nil
|
ctx: ctx}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func inspectFieldAndMarshall(name, field string, output interface{}) error {
|
||||||
|
str, err := inspectFieldJSON(name, field)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal([]byte(str), output)
|
||||||
|
}
|
||||||
|
|
||||||
func inspectFilter(name, filter string) (string, error) {
|
func inspectFilter(name, filter string) (string, error) {
|
||||||
format := fmt.Sprintf("{{%s}}", filter)
|
format := fmt.Sprintf("{{%s}}", filter)
|
||||||
inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name)
|
inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name)
|
||||||
|
|
|
@ -65,6 +65,38 @@ func FromParam(p string) (Args, error) {
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (filters Args) MatchKVList(field string, sources map[string]string) bool {
|
||||||
|
fieldValues := filters[field]
|
||||||
|
|
||||||
|
//do not filter if there is no filter set or cannot determine filter
|
||||||
|
if len(fieldValues) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if sources == nil || len(sources) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
outer:
|
||||||
|
for _, name2match := range fieldValues {
|
||||||
|
testKV := strings.SplitN(name2match, "=", 2)
|
||||||
|
|
||||||
|
for k, v := range sources {
|
||||||
|
if len(testKV) == 1 {
|
||||||
|
if k == testKV[0] {
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
} else if k == testKV[0] && v == testKV[1] {
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (filters Args) Match(field, source string) bool {
|
func (filters Args) Match(field, source string) bool {
|
||||||
fieldValues := filters[field]
|
fieldValues := filters[field]
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,8 @@ type Config struct {
|
||||||
NetworkDisabled bool
|
NetworkDisabled bool
|
||||||
MacAddress string
|
MacAddress string
|
||||||
OnBuild []string
|
OnBuild []string
|
||||||
|
SecurityOpt []string
|
||||||
|
Labels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContainerConfigFromJob(job *engine.Job) *Config {
|
func ContainerConfigFromJob(job *engine.Job) *Config {
|
||||||
|
@ -66,6 +68,9 @@ func ContainerConfigFromJob(job *engine.Job) *Config {
|
||||||
if Cmd := job.GetenvList("Cmd"); Cmd != nil {
|
if Cmd := job.GetenvList("Cmd"); Cmd != nil {
|
||||||
config.Cmd = Cmd
|
config.Cmd = Cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
job.GetenvJson("Labels", &config.Labels)
|
||||||
|
|
||||||
if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil {
|
if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil {
|
||||||
config.Entrypoint = Entrypoint
|
config.Entrypoint = Entrypoint
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.Entrypoint) == 0 {
|
||||||
if len(userConf.Cmd) == 0 {
|
if len(userConf.Cmd) == 0 {
|
||||||
userConf.Cmd = imageConf.Cmd
|
userConf.Cmd = imageConf.Cmd
|
||||||
|
|
|
@ -31,6 +31,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
|
||||||
flVolumes = opts.NewListOpts(opts.ValidatePath)
|
flVolumes = opts.NewListOpts(opts.ValidatePath)
|
||||||
flLinks = opts.NewListOpts(opts.ValidateLink)
|
flLinks = opts.NewListOpts(opts.ValidateLink)
|
||||||
flEnv = opts.NewListOpts(opts.ValidateEnv)
|
flEnv = opts.NewListOpts(opts.ValidateEnv)
|
||||||
|
flLabels = opts.NewListOpts(opts.ValidateEnv)
|
||||||
flDevices = opts.NewListOpts(opts.ValidatePath)
|
flDevices = opts.NewListOpts(opts.ValidatePath)
|
||||||
|
|
||||||
ulimits = make(map[string]*ulimit.Ulimit)
|
ulimits = make(map[string]*ulimit.Ulimit)
|
||||||
|
@ -47,6 +48,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
|
||||||
flCapAdd = opts.NewListOpts(nil)
|
flCapAdd = opts.NewListOpts(nil)
|
||||||
flCapDrop = opts.NewListOpts(nil)
|
flCapDrop = opts.NewListOpts(nil)
|
||||||
flSecurityOpt = opts.NewListOpts(nil)
|
flSecurityOpt = opts.NewListOpts(nil)
|
||||||
|
flLabelsFile = opts.NewListOpts(nil)
|
||||||
|
|
||||||
flNetwork = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
|
flNetwork = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
|
||||||
flPrivileged = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
|
flPrivileged = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
|
||||||
|
@ -74,6 +76,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
|
||||||
cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
|
cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
|
||||||
cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container")
|
cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container")
|
||||||
cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
|
cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
|
||||||
|
cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
|
||||||
|
cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
|
||||||
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
|
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
|
||||||
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
|
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
|
||||||
cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
|
cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
|
||||||
|
@ -243,16 +247,16 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect all the environment variables for the container
|
// collect all the environment variables for the container
|
||||||
envVariables := []string{}
|
envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
|
||||||
for _, ef := range flEnvFile.GetAll() {
|
if err != nil {
|
||||||
parsedVars, err := opts.ParseEnvFile(ef)
|
return nil, nil, cmd, err
|
||||||
if err != nil {
|
}
|
||||||
return nil, nil, cmd, err
|
|
||||||
}
|
// collect all the labels for the container
|
||||||
envVariables = append(envVariables, parsedVars...)
|
labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, cmd, err
|
||||||
}
|
}
|
||||||
// parse the '-e' and '--env' after, to allow override
|
|
||||||
envVariables = append(envVariables, flEnv.GetAll()...)
|
|
||||||
|
|
||||||
ipcMode := IpcMode(*flIpcMode)
|
ipcMode := IpcMode(*flIpcMode)
|
||||||
if !ipcMode.Valid() {
|
if !ipcMode.Valid() {
|
||||||
|
@ -297,6 +301,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
|
||||||
MacAddress: *flMacAddress,
|
MacAddress: *flMacAddress,
|
||||||
Entrypoint: entrypoint,
|
Entrypoint: entrypoint,
|
||||||
WorkingDir: *flWorkingDir,
|
WorkingDir: *flWorkingDir,
|
||||||
|
Labels: convertKVStringsToMap(labels),
|
||||||
}
|
}
|
||||||
|
|
||||||
hostConfig := &HostConfig{
|
hostConfig := &HostConfig{
|
||||||
|
@ -334,6 +339,37 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
|
||||||
return config, hostConfig, cmd, nil
|
return config, hostConfig, cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reads a file of line terminated key=value pairs and override that with override parameter
|
||||||
|
func readKVStrings(files []string, override []string) ([]string, error) {
|
||||||
|
envVariables := []string{}
|
||||||
|
for _, ef := range files {
|
||||||
|
parsedVars, err := opts.ParseEnvFile(ef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
envVariables = append(envVariables, parsedVars...)
|
||||||
|
}
|
||||||
|
// parse the '-e' and '--env' after, to allow override
|
||||||
|
envVariables = append(envVariables, override...)
|
||||||
|
|
||||||
|
return envVariables, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// converts ["key=value"] to {"key":"value"}
|
||||||
|
func convertKVStringsToMap(values []string) map[string]string {
|
||||||
|
result := make(map[string]string, len(values))
|
||||||
|
for _, value := range values {
|
||||||
|
kv := strings.SplitN(value, "=", 2)
|
||||||
|
if len(kv) == 1 {
|
||||||
|
result[kv[0]] = ""
|
||||||
|
} else {
|
||||||
|
result[kv[0]] = kv[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// parseRestartPolicy returns the parsed policy or an error indicating what is incorrect
|
// parseRestartPolicy returns the parsed policy or an error indicating what is incorrect
|
||||||
func parseRestartPolicy(policy string) (RestartPolicy, error) {
|
func parseRestartPolicy(policy string) (RestartPolicy, error) {
|
||||||
p := RestartPolicy{}
|
p := RestartPolicy{}
|
||||||
|
|
Loading…
Add table
Reference in a new issue