From e32b3211ae79c46178388d29adaa01a3b599377a Mon Sep 17 00:00:00 2001 From: Victor Costan Date: Sat, 17 Oct 2015 04:45:29 -0400 Subject: [PATCH] Swarm filters support in image building. When building an image (POST /build), swarm will extract filters from buildargs. This is similar to how container creation (POST /containers/create) extracts filters from environment variables. Signed-off-by: Victor Costan --- Godeps/Godeps.json | 2 +- .../samalba/dockerclient/dockerclient.go | 7 ++++ .../github.com/samalba/dockerclient/types.go | 1 + api/handlers.go | 6 +++ cluster/swarm/cluster.go | 6 ++- cluster/swarm/utils.go | 31 ++++++++++++++ cluster/swarm/utils_test.go | 21 ++++++++++ test/integration/api/build.bats | 12 ++++++ test/integration/build_with_filters.bats | 41 +++++++++++++++++++ .../testdata/build_with_args/Dockerfile | 4 ++ 10 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 cluster/swarm/utils.go create mode 100644 cluster/swarm/utils_test.go create mode 100644 test/integration/build_with_filters.bats create mode 100644 test/integration/testdata/build_with_args/Dockerfile diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 5a4dfca26a..992691e6ad 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -118,7 +118,7 @@ }, { "ImportPath": "github.com/samalba/dockerclient", - "Rev": "9ca0fce3c63569ee27457cd8bcf1f4f08e6d9459" + "Rev": "a3241e22d7230854778614ebca9c7f869776c5f0" }, { "ImportPath": "github.com/samuel/go-zookeeper/zk", diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go index a45aa2c274..399129d69a 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go @@ -760,6 +760,13 @@ func (client *DockerClient) BuildImage(image *BuildImage) (io.ReadCloser, error) v.Set("cpusetcpus", image.CpuSetCpus) v.Set("cpusetmems", image.CpuSetMems) v.Set("cgroupparent", image.CgroupParent) + if image.BuildArgs != nil { + buildArgsJSON, err := json.Marshal(image.BuildArgs) + if err != nil { + return nil, err + } + v.Set("buildargs", string(buildArgsJSON)) + } headers := make(map[string]string) if image.Config != nil { diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go index 96c04e24e5..8707bfec42 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go @@ -450,6 +450,7 @@ type BuildImage struct { CpuSetCpus string CpuSetMems string CgroupParent string + BuildArgs map[string]string } type Volume struct { diff --git a/api/handlers.go b/api/handlers.go index a7cca2b38a..55c2bf5142 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -928,6 +928,12 @@ func postBuild(c *context, w http.ResponseWriter, r *http.Request) { CpuSetMems: r.Form.Get("cpusetmems"), CgroupParent: r.Form.Get("cgroupparent"), Context: r.Body, + BuildArgs: make(map[string]string), + } + + buildArgsJSON := r.Form.Get("buildargs") + if buildArgsJSON != "" { + json.Unmarshal([]byte(buildArgsJSON), &buildImage.BuildArgs) } authEncoded := r.Header.Get("X-Registry-Auth") diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index 2b518107aa..8449775c74 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -771,10 +771,12 @@ func (c *Cluster) BuildImage(buildImage *dockerclient.BuildImage, out io.Writer) c.scheduler.Lock() // get an engine - config := &cluster.ContainerConfig{dockerclient.ContainerConfig{ + config := cluster.BuildContainerConfig(dockerclient.ContainerConfig{ CpuShares: buildImage.CpuShares, Memory: buildImage.Memory, - }} + Env: convertMapToKVStrings(buildImage.BuildArgs), + }) + buildImage.BuildArgs = convertKVStringsToMap(config.Env) nodes, err := c.scheduler.SelectNodesForContainer(c.listNodes(), config) c.scheduler.Unlock() if err != nil { diff --git a/cluster/swarm/utils.go b/cluster/swarm/utils.go new file mode 100644 index 0000000000..6d971ecaca --- /dev/null +++ b/cluster/swarm/utils.go @@ -0,0 +1,31 @@ +package swarm + +import ( + "strings" +) + +// convertKVStringsToMap 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 +} + +// convertMapToKVStrings converts {"key": "value"} to ["key=value"] +func convertMapToKVStrings(values map[string]string) []string { + result := make([]string, len(values)) + i := 0 + for key, value := range values { + result[i] = key + "=" + value + i++ + } + return result +} diff --git a/cluster/swarm/utils_test.go b/cluster/swarm/utils_test.go new file mode 100644 index 0000000000..9ab4631035 --- /dev/null +++ b/cluster/swarm/utils_test.go @@ -0,0 +1,21 @@ +package swarm + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConvertKVStringsToMap(t *testing.T) { + result := convertKVStringsToMap([]string{"HELLO=WORLD", "a=b=c=d", "e"}) + expected := map[string]string{"HELLO": "WORLD", "a": "b=c=d", "e": ""} + assert.Equal(t, expected, result) +} + +func TestConvertMapToKVStrings(t *testing.T) { + result := convertMapToKVStrings(map[string]string{"HELLO": "WORLD", "a": "b=c=d", "e": ""}) + sort.Strings(result) + expected := []string{"HELLO=WORLD", "a=b=c=d", "e="} + assert.Equal(t, expected, result) +} diff --git a/test/integration/api/build.bats b/test/integration/api/build.bats index 37e415066b..e7d50bf2c7 100644 --- a/test/integration/api/build.bats +++ b/test/integration/api/build.bats @@ -21,3 +21,15 @@ function teardown() { [ "$status" -eq 0 ] [ "${#lines[@]}" -eq 1 ] } + +@test "docker build with arg" { + start_docker_with_busybox 2 + swarm_manage + + run docker_swarm build -t test_args --build-arg="greeting=Hello Args" $TESTDATA/build_with_args + [ "$status" -eq 0 ] + + run docker_swarm run --rm test_args + [ "$status" -eq 0 ] + [[ "$output" == "Hello Args" ]] +} diff --git a/test/integration/build_with_filters.bats b/test/integration/build_with_filters.bats new file mode 100644 index 0000000000..fb7d92b649 --- /dev/null +++ b/test/integration/build_with_filters.bats @@ -0,0 +1,41 @@ +#!/usr/bin/env bats + +load helpers + +function teardown() { + swarm_manage_cleanup + stop_docker +} + +@test "build with impossible node constraint" { + start_docker 2 + swarm_manage + + run docker_swarm images -q + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] + + run docker_swarm build --build-arg="constraint:node==node-9" $TESTDATA/build + [ "$status" -eq 1 ] + [[ "$output" == *"Error response from daemon: unable to find a node that satisfies node==node-9"* ]] + + run docker_swarm images -q + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test "build with node constraint and buildarg" { + start_docker_with_busybox 2 + swarm_manage + + run docker_swarm build -t test_args --build-arg="greeting=Hello Args" --build-arg="constraint:node==node-1" $TESTDATA/build_with_args + [ "$status" -eq 0 ] + + run docker_swarm run --name c1 test_args + [ "$status" -eq 0 ] + [[ "$output" == "Hello Args" ]] + + run docker_swarm inspect c1 + [ "$status" -eq 0 ] + [[ "${output}" == *'"Name": "node-1"'* ]] +} diff --git a/test/integration/testdata/build_with_args/Dockerfile b/test/integration/testdata/build_with_args/Dockerfile new file mode 100644 index 0000000000..21723a3047 --- /dev/null +++ b/test/integration/testdata/build_with_args/Dockerfile @@ -0,0 +1,4 @@ +FROM busybox:latest +ARG greeting=hello +ENV GREETING=$greeting +CMD ["/bin/sh", "-c", "echo -n \"$GREETING\""]