Signed-off-by: Darren Shepherd <darren@rancher.com>
| ... | ... |
@@ -90,6 +90,10 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
|
| 90 | 90 |
return nil |
| 91 | 91 |
} |
| 92 | 92 |
|
| 93 |
+ if !psFilters.MatchKVList("label", container.Config.Labels) {
|
|
| 94 |
+ return nil |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 93 | 97 |
if before != "" && !foundBefore {
|
| 94 | 98 |
if container.ID == beforeCont.ID {
|
| 95 | 99 |
foundBefore = true |
| ... | ... |
@@ -157,6 +161,7 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
|
| 157 | 157 |
out.SetInt64("SizeRw", sizeRw)
|
| 158 | 158 |
out.SetInt64("SizeRootFs", sizeRootFs)
|
| 159 | 159 |
} |
| 160 |
+ out.SetJson("Labels", container.Config.Labels)
|
|
| 160 | 161 |
outs.Add(out) |
| 161 | 162 |
return nil |
| 162 | 163 |
} |
| ... | ... |
@@ -11,13 +11,17 @@ import ( |
| 11 | 11 |
"github.com/docker/docker/pkg/parsers/filters" |
| 12 | 12 |
) |
| 13 | 13 |
|
| 14 |
-var acceptedImageFilterTags = map[string]struct{}{"dangling": {}}
|
|
| 14 |
+var acceptedImageFilterTags = map[string]struct{}{
|
|
| 15 |
+ "dangling": {},
|
|
| 16 |
+ "label": {},
|
|
| 17 |
+} |
|
| 15 | 18 |
|
| 16 | 19 |
func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
| 17 | 20 |
var ( |
| 18 | 21 |
allImages map[string]*image.Image |
| 19 | 22 |
err error |
| 20 | 23 |
filt_tagged = true |
| 24 |
+ filt_label = false |
|
| 21 | 25 |
) |
| 22 | 26 |
|
| 23 | 27 |
imageFilters, err := filters.FromParam(job.Getenv("filters"))
|
| ... | ... |
@@ -38,6 +42,8 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
| 38 | 38 |
} |
| 39 | 39 |
} |
| 40 | 40 |
|
| 41 |
+ _, filt_label = imageFilters["label"] |
|
| 42 |
+ |
|
| 41 | 43 |
if job.GetenvBool("all") && filt_tagged {
|
| 42 | 44 |
allImages, err = s.graph.Map() |
| 43 | 45 |
} else {
|
| ... | ... |
@@ -68,6 +74,9 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
| 68 | 68 |
} else {
|
| 69 | 69 |
// get the boolean list for if only the untagged images are requested |
| 70 | 70 |
delete(allImages, id) |
| 71 |
+ if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
|
|
| 72 |
+ continue |
|
| 73 |
+ } |
|
| 71 | 74 |
if filt_tagged {
|
| 72 | 75 |
out := &engine.Env{}
|
| 73 | 76 |
out.SetJson("ParentId", image.Parent)
|
| ... | ... |
@@ -76,6 +85,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
| 76 | 76 |
out.SetInt64("Created", image.Created.Unix())
|
| 77 | 77 |
out.SetInt64("Size", image.Size)
|
| 78 | 78 |
out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
| 79 |
+ out.SetJson("Labels", image.ContainerConfig.Labels)
|
|
| 79 | 80 |
lookup[id] = out |
| 80 | 81 |
} |
| 81 | 82 |
} |
| ... | ... |
@@ -90,8 +100,11 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
| 90 | 90 |
} |
| 91 | 91 |
|
| 92 | 92 |
// Display images which aren't part of a repository/tag |
| 93 |
- if job.Getenv("filter") == "" {
|
|
| 93 |
+ if job.Getenv("filter") == "" || filt_label {
|
|
| 94 | 94 |
for _, image := range allImages {
|
| 95 |
+ if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
|
|
| 96 |
+ continue |
|
| 97 |
+ } |
|
| 95 | 98 |
out := &engine.Env{}
|
| 96 | 99 |
out.SetJson("ParentId", image.Parent)
|
| 97 | 100 |
out.SetList("RepoTags", []string{"<none>:<none>"})
|
| ... | ... |
@@ -99,6 +112,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
| 99 | 99 |
out.SetInt64("Created", image.Created.Unix())
|
| 100 | 100 |
out.SetInt64("Size", image.Size)
|
| 101 | 101 |
out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
| 102 |
+ out.SetJson("Labels", image.ContainerConfig.Labels)
|
|
| 102 | 103 |
outs.Add(out) |
| 103 | 104 |
} |
| 104 | 105 |
} |
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"os" |
| 6 | 6 |
"os/exec" |
| 7 |
+ "reflect" |
|
| 7 | 8 |
"testing" |
| 8 | 9 |
"time" |
| 9 | 10 |
|
| ... | ... |
@@ -249,3 +250,57 @@ func TestCreateVolumesCreated(t *testing.T) {
|
| 249 | 249 |
|
| 250 | 250 |
logDone("create - volumes are created")
|
| 251 | 251 |
} |
| 252 |
+ |
|
| 253 |
+func TestCreateLabels(t *testing.T) {
|
|
| 254 |
+ name := "test_create_labels" |
|
| 255 |
+ expected := map[string]string{"k1": "v1", "k2": "v2"}
|
|
| 256 |
+ if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k1=v1", "--label", "k2=v2", "busybox")); err != nil {
|
|
| 257 |
+ t.Fatal(out, err) |
|
| 258 |
+ } |
|
| 259 |
+ |
|
| 260 |
+ actual := make(map[string]string) |
|
| 261 |
+ err := inspectFieldAndMarshall(name, "Config.Labels", &actual) |
|
| 262 |
+ if err != nil {
|
|
| 263 |
+ t.Fatal(err) |
|
| 264 |
+ } |
|
| 265 |
+ |
|
| 266 |
+ if !reflect.DeepEqual(expected, actual) {
|
|
| 267 |
+ t.Fatalf("Expected %s got %s", expected, actual)
|
|
| 268 |
+ } |
|
| 269 |
+ |
|
| 270 |
+ deleteAllContainers() |
|
| 271 |
+ |
|
| 272 |
+ logDone("create - labels")
|
|
| 273 |
+} |
|
| 274 |
+ |
|
| 275 |
+func TestCreateLabelFromImage(t *testing.T) {
|
|
| 276 |
+ imageName := "testcreatebuildlabel" |
|
| 277 |
+ defer deleteImages(imageName) |
|
| 278 |
+ _, err := buildImage(imageName, |
|
| 279 |
+ `FROM busybox |
|
| 280 |
+ LABEL k1=v1 k2=v2`, |
|
| 281 |
+ true) |
|
| 282 |
+ if err != nil {
|
|
| 283 |
+ t.Fatal(err) |
|
| 284 |
+ } |
|
| 285 |
+ |
|
| 286 |
+ name := "test_create_labels_from_image" |
|
| 287 |
+ expected := map[string]string{"k2": "x", "k3": "v3", "k1": "v1"}
|
|
| 288 |
+ if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k2=x", "--label", "k3=v3", imageName)); err != nil {
|
|
| 289 |
+ t.Fatal(out, err) |
|
| 290 |
+ } |
|
| 291 |
+ |
|
| 292 |
+ actual := make(map[string]string) |
|
| 293 |
+ err = inspectFieldAndMarshall(name, "Config.Labels", &actual) |
|
| 294 |
+ if err != nil {
|
|
| 295 |
+ t.Fatal(err) |
|
| 296 |
+ } |
|
| 297 |
+ |
|
| 298 |
+ if !reflect.DeepEqual(expected, actual) {
|
|
| 299 |
+ t.Fatalf("Expected %s got %s", expected, actual)
|
|
| 300 |
+ } |
|
| 301 |
+ |
|
| 302 |
+ deleteAllContainers() |
|
| 303 |
+ |
|
| 304 |
+ logDone("create - labels from image")
|
|
| 305 |
+} |
| ... | ... |
@@ -77,6 +77,60 @@ func TestImagesErrorWithInvalidFilterNameTest(t *testing.T) {
|
| 77 | 77 |
logDone("images - invalid filter name check working")
|
| 78 | 78 |
} |
| 79 | 79 |
|
| 80 |
+func TestImagesFilterLabel(t *testing.T) {
|
|
| 81 |
+ imageName1 := "images_filter_test1" |
|
| 82 |
+ imageName2 := "images_filter_test2" |
|
| 83 |
+ imageName3 := "images_filter_test3" |
|
| 84 |
+ defer deleteAllContainers() |
|
| 85 |
+ defer deleteImages(imageName1) |
|
| 86 |
+ defer deleteImages(imageName2) |
|
| 87 |
+ defer deleteImages(imageName3) |
|
| 88 |
+ image1ID, err := buildImage(imageName1, |
|
| 89 |
+ `FROM scratch |
|
| 90 |
+ LABEL match me`, true) |
|
| 91 |
+ if err != nil {
|
|
| 92 |
+ t.Fatal(err) |
|
| 93 |
+ } |
|
| 94 |
+ |
|
| 95 |
+ image2ID, err := buildImage(imageName2, |
|
| 96 |
+ `FROM scratch |
|
| 97 |
+ LABEL match="me too"`, true) |
|
| 98 |
+ if err != nil {
|
|
| 99 |
+ t.Fatal(err) |
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ image3ID, err := buildImage(imageName3, |
|
| 103 |
+ `FROM scratch |
|
| 104 |
+ LABEL nomatch me`, true) |
|
| 105 |
+ if err != nil {
|
|
| 106 |
+ t.Fatal(err) |
|
| 107 |
+ } |
|
| 108 |
+ |
|
| 109 |
+ cmd := exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match") |
|
| 110 |
+ out, _, err := runCommandWithOutput(cmd) |
|
| 111 |
+ if err != nil {
|
|
| 112 |
+ t.Fatal(out, err) |
|
| 113 |
+ } |
|
| 114 |
+ out = strings.TrimSpace(out) |
|
| 115 |
+ |
|
| 116 |
+ if (!strings.Contains(out, image1ID) && !strings.Contains(out, image2ID)) || strings.Contains(out, image3ID) {
|
|
| 117 |
+ t.Fatalf("Expected ids %s,%s got %s", image1ID, image2ID, out)
|
|
| 118 |
+ } |
|
| 119 |
+ |
|
| 120 |
+ cmd = exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match=me too") |
|
| 121 |
+ out, _, err = runCommandWithOutput(cmd) |
|
| 122 |
+ if err != nil {
|
|
| 123 |
+ t.Fatal(out, err) |
|
| 124 |
+ } |
|
| 125 |
+ out = strings.TrimSpace(out) |
|
| 126 |
+ |
|
| 127 |
+ if out != image2ID {
|
|
| 128 |
+ t.Fatalf("Expected %s got %s", image2ID, out)
|
|
| 129 |
+ } |
|
| 130 |
+ |
|
| 131 |
+ logDone("images - filter label")
|
|
| 132 |
+} |
|
| 133 |
+ |
|
| 80 | 134 |
func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) {
|
| 81 | 135 |
imageName := "images_filter_test" |
| 82 | 136 |
defer deleteAllContainers() |
| ... | ... |
@@ -412,6 +412,54 @@ func TestPsListContainersFilterName(t *testing.T) {
|
| 412 | 412 |
logDone("ps - test ps filter name")
|
| 413 | 413 |
} |
| 414 | 414 |
|
| 415 |
+func TestPsListContainersFilterLabel(t *testing.T) {
|
|
| 416 |
+ // start container |
|
| 417 |
+ runCmd := exec.Command(dockerBinary, "run", "-d", "-l", "match=me", "busybox") |
|
| 418 |
+ out, _, err := runCommandWithOutput(runCmd) |
|
| 419 |
+ if err != nil {
|
|
| 420 |
+ t.Fatal(out, err) |
|
| 421 |
+ } |
|
| 422 |
+ firstID := stripTrailingCharacters(out) |
|
| 423 |
+ |
|
| 424 |
+ // start another container |
|
| 425 |
+ runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "match=me too", "busybox") |
|
| 426 |
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
|
| 427 |
+ t.Fatal(out, err) |
|
| 428 |
+ } |
|
| 429 |
+ secondID := stripTrailingCharacters(out) |
|
| 430 |
+ |
|
| 431 |
+ // start third container |
|
| 432 |
+ runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "nomatch=me", "busybox") |
|
| 433 |
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
|
| 434 |
+ t.Fatal(out, err) |
|
| 435 |
+ } |
|
| 436 |
+ thirdID := stripTrailingCharacters(out) |
|
| 437 |
+ |
|
| 438 |
+ // filter containers by exact match |
|
| 439 |
+ runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me") |
|
| 440 |
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
|
| 441 |
+ t.Fatal(out, err) |
|
| 442 |
+ } |
|
| 443 |
+ containerOut := strings.TrimSpace(out) |
|
| 444 |
+ if containerOut != firstID {
|
|
| 445 |
+ t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out)
|
|
| 446 |
+ } |
|
| 447 |
+ |
|
| 448 |
+ // filter containers by exact key |
|
| 449 |
+ runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match") |
|
| 450 |
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
|
|
| 451 |
+ t.Fatal(out, err) |
|
| 452 |
+ } |
|
| 453 |
+ containerOut = strings.TrimSpace(out) |
|
| 454 |
+ if (!strings.Contains(containerOut, firstID) || !strings.Contains(containerOut, secondID)) || strings.Contains(containerOut, thirdID) {
|
|
| 455 |
+ t.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out)
|
|
| 456 |
+ } |
|
| 457 |
+ |
|
| 458 |
+ deleteAllContainers() |
|
| 459 |
+ |
|
| 460 |
+ logDone("ps - test ps filter label")
|
|
| 461 |
+} |
|
| 462 |
+ |
|
| 415 | 463 |
func TestPsListContainersFilterExited(t *testing.T) {
|
| 416 | 464 |
defer deleteAllContainers() |
| 417 | 465 |
|
| ... | ... |
@@ -724,6 +724,15 @@ COPY . /static`); err != nil {
|
| 724 | 724 |
ctx: ctx}, nil |
| 725 | 725 |
} |
| 726 | 726 |
|
| 727 |
+func inspectFieldAndMarshall(name, field string, output interface{}) error {
|
|
| 728 |
+ str, err := inspectFieldJSON(name, field) |
|
| 729 |
+ if err != nil {
|
|
| 730 |
+ return err |
|
| 731 |
+ } |
|
| 732 |
+ |
|
| 733 |
+ return json.Unmarshal([]byte(str), output) |
|
| 734 |
+} |
|
| 735 |
+ |
|
| 727 | 736 |
func inspectFilter(name, filter string) (string, error) {
|
| 728 | 737 |
format := fmt.Sprintf("{{%s}}", filter)
|
| 729 | 738 |
inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name) |
| ... | ... |
@@ -65,6 +65,38 @@ func FromParam(p string) (Args, error) {
|
| 65 | 65 |
return args, nil |
| 66 | 66 |
} |
| 67 | 67 |
|
| 68 |
+func (filters Args) MatchKVList(field string, sources map[string]string) bool {
|
|
| 69 |
+ fieldValues := filters[field] |
|
| 70 |
+ |
|
| 71 |
+ //do not filter if there is no filter set or cannot determine filter |
|
| 72 |
+ if len(fieldValues) == 0 {
|
|
| 73 |
+ return true |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ if sources == nil || len(sources) == 0 {
|
|
| 77 |
+ return false |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+outer: |
|
| 81 |
+ for _, name2match := range fieldValues {
|
|
| 82 |
+ testKV := strings.SplitN(name2match, "=", 2) |
|
| 83 |
+ |
|
| 84 |
+ for k, v := range sources {
|
|
| 85 |
+ if len(testKV) == 1 {
|
|
| 86 |
+ if k == testKV[0] {
|
|
| 87 |
+ continue outer |
|
| 88 |
+ } |
|
| 89 |
+ } else if k == testKV[0] && v == testKV[1] {
|
|
| 90 |
+ continue outer |
|
| 91 |
+ } |
|
| 92 |
+ } |
|
| 93 |
+ |
|
| 94 |
+ return false |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ return true |
|
| 98 |
+} |
|
| 99 |
+ |
|
| 68 | 100 |
func (filters Args) Match(field, source string) bool {
|
| 69 | 101 |
fieldValues := filters[field] |
| 70 | 102 |
|
| ... | ... |
@@ -31,6 +31,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe |
| 31 | 31 |
flVolumes = opts.NewListOpts(opts.ValidatePath) |
| 32 | 32 |
flLinks = opts.NewListOpts(opts.ValidateLink) |
| 33 | 33 |
flEnv = opts.NewListOpts(opts.ValidateEnv) |
| 34 |
+ flLabels = opts.NewListOpts(opts.ValidateEnv) |
|
| 34 | 35 |
flDevices = opts.NewListOpts(opts.ValidatePath) |
| 35 | 36 |
|
| 36 | 37 |
ulimits = make(map[string]*ulimit.Ulimit) |
| ... | ... |
@@ -47,6 +48,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe |
| 47 | 47 |
flCapAdd = opts.NewListOpts(nil) |
| 48 | 48 |
flCapDrop = opts.NewListOpts(nil) |
| 49 | 49 |
flSecurityOpt = opts.NewListOpts(nil) |
| 50 |
+ flLabelsFile = opts.NewListOpts(nil) |
|
| 50 | 51 |
|
| 51 | 52 |
flNetwork = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
|
| 52 | 53 |
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 |
| 74 | 74 |
cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
|
| 75 | 75 |
cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container")
|
| 76 | 76 |
cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
|
| 77 |
+ cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container, for example com.example.key=value")
|
|
| 78 |
+ cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
|
|
| 77 | 79 |
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
|
| 78 | 80 |
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
|
| 79 | 81 |
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 |
| 243 | 243 |
} |
| 244 | 244 |
|
| 245 | 245 |
// collect all the environment variables for the container |
| 246 |
- envVariables := []string{}
|
|
| 247 |
- for _, ef := range flEnvFile.GetAll() {
|
|
| 248 |
- parsedVars, err := opts.ParseEnvFile(ef) |
|
| 249 |
- if err != nil {
|
|
| 250 |
- return nil, nil, cmd, err |
|
| 251 |
- } |
|
| 252 |
- envVariables = append(envVariables, parsedVars...) |
|
| 246 |
+ envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll()) |
|
| 247 |
+ if err != nil {
|
|
| 248 |
+ return nil, nil, cmd, err |
|
| 249 |
+ } |
|
| 250 |
+ |
|
| 251 |
+ // collect all the labels for the container |
|
| 252 |
+ labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll()) |
|
| 253 |
+ if err != nil {
|
|
| 254 |
+ return nil, nil, cmd, err |
|
| 253 | 255 |
} |
| 254 |
- // parse the '-e' and '--env' after, to allow override |
|
| 255 |
- envVariables = append(envVariables, flEnv.GetAll()...) |
|
| 256 | 256 |
|
| 257 | 257 |
ipcMode := IpcMode(*flIpcMode) |
| 258 | 258 |
if !ipcMode.Valid() {
|
| ... | ... |
@@ -297,6 +301,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe |
| 297 | 297 |
MacAddress: *flMacAddress, |
| 298 | 298 |
Entrypoint: entrypoint, |
| 299 | 299 |
WorkingDir: *flWorkingDir, |
| 300 |
+ Labels: convertKVStringsToMap(labels), |
|
| 300 | 301 |
} |
| 301 | 302 |
|
| 302 | 303 |
hostConfig := &HostConfig{
|
| ... | ... |
@@ -330,6 +335,37 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe |
| 330 | 330 |
return config, hostConfig, cmd, nil |
| 331 | 331 |
} |
| 332 | 332 |
|
| 333 |
+// reads a file of line terminated key=value pairs and override that with override parameter |
|
| 334 |
+func readKVStrings(files []string, override []string) ([]string, error) {
|
|
| 335 |
+ envVariables := []string{}
|
|
| 336 |
+ for _, ef := range files {
|
|
| 337 |
+ parsedVars, err := opts.ParseEnvFile(ef) |
|
| 338 |
+ if err != nil {
|
|
| 339 |
+ return nil, err |
|
| 340 |
+ } |
|
| 341 |
+ envVariables = append(envVariables, parsedVars...) |
|
| 342 |
+ } |
|
| 343 |
+ // parse the '-e' and '--env' after, to allow override |
|
| 344 |
+ envVariables = append(envVariables, override...) |
|
| 345 |
+ |
|
| 346 |
+ return envVariables, nil |
|
| 347 |
+} |
|
| 348 |
+ |
|
| 349 |
+// converts ["key=value"] to {"key":"value"}
|
|
| 350 |
+func convertKVStringsToMap(values []string) map[string]string {
|
|
| 351 |
+ result := make(map[string]string, len(values)) |
|
| 352 |
+ for _, value := range values {
|
|
| 353 |
+ kv := strings.SplitN(value, "=", 2) |
|
| 354 |
+ if len(kv) == 1 {
|
|
| 355 |
+ result[kv[0]] = "" |
|
| 356 |
+ } else {
|
|
| 357 |
+ result[kv[0]] = kv[1] |
|
| 358 |
+ } |
|
| 359 |
+ } |
|
| 360 |
+ |
|
| 361 |
+ return result |
|
| 362 |
+} |
|
| 363 |
+ |
|
| 333 | 364 |
// parseRestartPolicy returns the parsed policy or an error indicating what is incorrect |
| 334 | 365 |
func parseRestartPolicy(policy string) (RestartPolicy, error) {
|
| 335 | 366 |
p := RestartPolicy{}
|