diff --git a/api/client/build.go b/api/client/build.go index cf0764de35..300d0cd9d9 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -62,6 +62,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error { cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables") isolation := cmd.String([]string{"-isolation"}, "", "Container isolation technology") + flLabels := opts.NewListOpts(nil) + cmd.Var(&flLabels, []string{"-label"}, "Set metadata for an image") + ulimits := make(map[string]*units.Ulimit) flUlimits := runconfigopts.NewUlimitOpt(&ulimits) cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options") @@ -230,6 +233,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { Ulimits: flUlimits.GetList(), BuildArgs: runconfigopts.ConvertKVStringsToMap(flBuildArg.GetAll()), AuthConfigs: cli.retrieveAuthConfigs(), + Labels: runconfigopts.ConvertKVStringsToMap(flLabels.GetAll()), } response, err := cli.client.ImageBuild(context.Background(), options) diff --git a/api/client/network.go b/api/client/network.go index f2b1698f08..4bbc715403 100644 --- a/api/client/network.go +++ b/api/client/network.go @@ -44,6 +44,7 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error { flIpamGateway := opts.NewListOpts(nil) flIpamAux := opts.NewMapOpts(nil, nil) flIpamOpt := opts.NewMapOpts(nil, nil) + flLabels := opts.NewListOpts(nil) cmd.Var(&flIpamSubnet, []string{"-subnet"}, "subnet in CIDR format that represents a network segment") cmd.Var(&flIpamIPRange, []string{"-ip-range"}, "allocate container ip from a sub-range") @@ -51,6 +52,7 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error { cmd.Var(flIpamAux, []string{"-aux-address"}, "auxiliary ipv4 or ipv6 addresses used by Network driver") cmd.Var(flOpts, []string{"o", "-opt"}, "set driver specific options") cmd.Var(flIpamOpt, []string{"-ipam-opt"}, "set IPAM driver specific options") + cmd.Var(&flLabels, []string{"-label"}, "set metadata on a network") flInternal := cmd.Bool([]string{"-internal"}, false, "restricts external access to the network") flIPv6 := cmd.Bool([]string{"-ipv6"}, false, "enable IPv6 networking") @@ -82,6 +84,7 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error { CheckDuplicate: true, Internal: *flInternal, EnableIPv6: *flIPv6, + Labels: runconfigopts.ConvertKVStringsToMap(flLabels.GetAll()), } resp, err := cli.client.NetworkCreate(context.Background(), nc) diff --git a/api/client/volume.go b/api/client/volume.go index 07290f1934..37e623fbd3 100644 --- a/api/client/volume.go +++ b/api/client/volume.go @@ -10,6 +10,7 @@ import ( Cli "github.com/docker/docker/cli" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" + runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/filters" ) @@ -128,6 +129,9 @@ func (cli *DockerCli) CmdVolumeCreate(args ...string) error { flDriverOpts := opts.NewMapOpts(nil, nil) cmd.Var(flDriverOpts, []string{"o", "-opt"}, "Set driver specific options") + flLabels := opts.NewListOpts(nil) + cmd.Var(&flLabels, []string{"-label"}, "Set metadata for a volume") + cmd.Require(flag.Exact, 0) cmd.ParseFlags(args, true) @@ -135,6 +139,7 @@ func (cli *DockerCli) CmdVolumeCreate(args ...string) error { Driver: *flDriver, DriverOpts: flDriverOpts.GetAll(), Name: *flName, + Labels: runconfigopts.ConvertKVStringsToMap(flLabels.GetAll()), } vol, err := cli.client.VolumeCreate(context.Background(), volReq) diff --git a/api/server/router/build/build_routes.go b/api/server/router/build/build_routes.go index a6ab71f57d..d40472e60f 100644 --- a/api/server/router/build/build_routes.go +++ b/api/server/router/build/build_routes.go @@ -82,6 +82,15 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui } options.BuildArgs = buildArgs } + var labels = map[string]string{} + labelsJSON := r.FormValue("labels") + if labelsJSON != "" { + if err := json.NewDecoder(strings.NewReader(labelsJSON)).Decode(&labels); err != nil { + return nil, err + } + options.Labels = labels + } + return options, nil } diff --git a/api/server/router/network/backend.go b/api/server/router/network/backend.go index 731f92e7de..7bbbca4ddd 100644 --- a/api/server/router/network/backend.go +++ b/api/server/router/network/backend.go @@ -12,7 +12,7 @@ type Backend interface { GetNetworkByName(idName string) (libnetwork.Network, error) GetNetworksByID(partialID string) []libnetwork.Network GetAllNetworks() []libnetwork.Network - CreateNetwork(name, driver string, ipam network.IPAM, options map[string]string, internal bool, enableIPv6 bool) (libnetwork.Network, error) + CreateNetwork(name, driver string, ipam network.IPAM, options map[string]string, labels map[string]string, internal bool, enableIPv6 bool) (libnetwork.Network, error) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error DisconnectContainerFromNetwork(containerName string, network libnetwork.Network, force bool) error DeleteNetwork(name string) error diff --git a/api/server/router/network/network_routes.go b/api/server/router/network/network_routes.go index eb6350a290..f052f54118 100644 --- a/api/server/router/network/network_routes.go +++ b/api/server/router/network/network_routes.go @@ -91,7 +91,7 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID()) } - nw, err = n.backend.CreateNetwork(create.Name, create.Driver, create.IPAM, create.Options, create.Internal, create.EnableIPv6) + nw, err = n.backend.CreateNetwork(create.Name, create.Driver, create.IPAM, create.Options, create.Labels, create.Internal, create.EnableIPv6) if err != nil { return err } @@ -163,16 +163,18 @@ func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource { return r } + info := nw.Info() r.Name = nw.Name() r.ID = nw.ID() - r.Scope = nw.Info().Scope() + r.Scope = info.Scope() r.Driver = nw.Type() - r.EnableIPv6 = nw.Info().IPv6Enabled() - r.Internal = nw.Info().Internal() - r.Options = nw.Info().DriverOptions() + r.EnableIPv6 = info.IPv6Enabled() + r.Internal = info.Internal() + r.Options = info.DriverOptions() r.Containers = make(map[string]types.EndpointResource) - buildIpamResources(r, nw) - r.Internal = nw.Info().Internal() + buildIpamResources(r, info) + r.Internal = info.Internal() + r.Labels = info.Labels() epl := nw.Endpoints() for _, e := range epl { @@ -191,10 +193,10 @@ func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource { return r } -func buildIpamResources(r *types.NetworkResource, nw libnetwork.Network) { - id, opts, ipv4conf, ipv6conf := nw.Info().IpamConfig() +func buildIpamResources(r *types.NetworkResource, nwInfo libnetwork.NetworkInfo) { + id, opts, ipv4conf, ipv6conf := nwInfo.IpamConfig() - ipv4Info, ipv6Info := nw.Info().IpamInfo() + ipv4Info, ipv6Info := nwInfo.IpamInfo() r.IPAM.Driver = id diff --git a/api/server/router/volume/backend.go b/api/server/router/volume/backend.go index ede5dc4d91..fbf5ed27f6 100644 --- a/api/server/router/volume/backend.go +++ b/api/server/router/volume/backend.go @@ -10,7 +10,6 @@ import ( type Backend interface { Volumes(filter string) ([]*types.Volume, []string, error) VolumeInspect(name string) (*types.Volume, error) - VolumeCreate(name, driverName string, - opts map[string]string) (*types.Volume, error) + VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) VolumeRm(name string) error } diff --git a/api/server/router/volume/volume_routes.go b/api/server/router/volume/volume_routes.go index feef77cb57..5aa0d4a7a7 100644 --- a/api/server/router/volume/volume_routes.go +++ b/api/server/router/volume/volume_routes.go @@ -47,7 +47,7 @@ func (v *volumeRouter) postVolumesCreate(ctx context.Context, w http.ResponseWri return err } - volume, err := v.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts) + volume, err := v.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels) if err != nil { return err } diff --git a/builder/dockerfile/builder.go b/builder/dockerfile/builder.go index 0d42971beb..0f35f54c0e 100644 --- a/builder/dockerfile/builder.go +++ b/builder/dockerfile/builder.go @@ -218,6 +218,10 @@ func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context var shortImgID string for i, n := range b.dockerfile.Children { + // we only want to add labels to the last layer + if i == len(b.dockerfile.Children)-1 { + b.addLabels() + } select { case <-b.cancelled: logrus.Debug("Builder: build cancelled!") diff --git a/builder/dockerfile/internals.go b/builder/dockerfile/internals.go index 34fca4abef..92af086e40 100644 --- a/builder/dockerfile/internals.go +++ b/builder/dockerfile/internals.go @@ -37,6 +37,19 @@ import ( "github.com/docker/engine-api/types/strslice" ) +func (b *Builder) addLabels() { + // merge labels + if len(b.options.Labels) > 0 { + logrus.Debugf("[BUILDER] setting labels %v", b.options.Labels) + if b.runConfig.Labels == nil { + b.runConfig.Labels = make(map[string]string) + } + for kL, vL := range b.options.Labels { + b.runConfig.Labels[kL] = vL + } + } +} + func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) error { if b.disableCommit { return nil @@ -45,6 +58,7 @@ func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) e return fmt.Errorf("Please provide a source image with `from` prior to commit") } b.runConfig.Image = b.image + if id == "" { cmd := b.runConfig.Cmd if runtime.GOOS != "windows" { @@ -81,6 +95,7 @@ func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) e if err != nil { return err } + b.image = imageID return nil } diff --git a/daemon/create.go b/daemon/create.go index 425c4344bb..34f0aa2d6c 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -167,12 +167,12 @@ func (daemon *Daemon) setRWLayer(container *container.Container) error { // VolumeCreate creates a volume with the specified name, driver, and opts // This is called directly from the remote API -func (daemon *Daemon) VolumeCreate(name, driverName string, opts map[string]string) (*types.Volume, error) { +func (daemon *Daemon) VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) { if name == "" { name = stringid.GenerateNonCryptoID() } - v, err := daemon.volumes.Create(name, driverName, opts) + v, err := daemon.volumes.Create(name, driverName, opts, labels) if err != nil { if volumestore.IsNameConflict(err) { return nil, fmt.Errorf("A volume named %s already exists. Choose a different volume name.", name) diff --git a/daemon/create_unix.go b/daemon/create_unix.go index aa9b31896c..37c4a911f0 100644 --- a/daemon/create_unix.go +++ b/daemon/create_unix.go @@ -45,7 +45,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain return fmt.Errorf("cannot mount volume over existing file, file exists %s", path) } - v, err := daemon.volumes.CreateWithRef(name, hostConfig.VolumeDriver, container.ID, nil) + v, err := daemon.volumes.CreateWithRef(name, hostConfig.VolumeDriver, container.ID, nil, nil) if err != nil { return err } diff --git a/daemon/create_windows.go b/daemon/create_windows.go index d8fc059fd3..6bb356ad3e 100644 --- a/daemon/create_windows.go +++ b/daemon/create_windows.go @@ -33,7 +33,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain // Create the volume in the volume driver. If it doesn't exist, // a new one will be created. - v, err := daemon.volumes.CreateWithRef(mp.Name, volumeDriver, container.ID, nil) + v, err := daemon.volumes.CreateWithRef(mp.Name, volumeDriver, container.ID, nil, nil) if err != nil { return err } diff --git a/daemon/daemon.go b/daemon/daemon.go index f04a417750..b9b18d770e 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1498,7 +1498,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore, } volumedrivers.Register(volumesDriver, volumesDriver.Name()) - return store.New(), nil + return store.New(config.Root) } // AuthenticateToRegistry checks the validity of credentials in authConfig diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go index 1c34d3ae4c..382aac29e3 100644 --- a/daemon/daemon_test.go +++ b/daemon/daemon_test.go @@ -118,10 +118,14 @@ func TestGetContainer(t *testing.T) { } func initDaemonWithVolumeStore(tmp string) (*Daemon, error) { + var err error daemon := &Daemon{ repository: tmp, root: tmp, - volumes: store.New(), + } + daemon.volumes, err = store.New(tmp) + if err != nil { + return nil, err } volumesDriver, err := local.New(tmp, 0, 0) diff --git a/daemon/network.go b/daemon/network.go index e937391b83..98879915da 100644 --- a/daemon/network.go +++ b/daemon/network.go @@ -91,22 +91,23 @@ func (daemon *Daemon) GetAllNetworks() []libnetwork.Network { } // CreateNetwork creates a network with the given name, driver and other optional parameters -func (daemon *Daemon) CreateNetwork(name, driver string, ipam network.IPAM, netOption map[string]string, internal bool, enableIPv6 bool) (libnetwork.Network, error) { +func (daemon *Daemon) CreateNetwork(name, driver string, ipam network.IPAM, netOption map[string]string, labels map[string]string, internal bool, enableIPv6 bool) (libnetwork.Network, error) { c := daemon.netController if driver == "" { driver = c.Config().Daemon.DefaultDriver } - nwOptions := []libnetwork.NetworkOption{} - v4Conf, v6Conf, err := getIpamConfig(ipam.Config) if err != nil { return nil, err } - nwOptions = append(nwOptions, libnetwork.NetworkOptionIpam(ipam.Driver, "", v4Conf, v6Conf, ipam.Options)) - nwOptions = append(nwOptions, libnetwork.NetworkOptionEnableIPv6(enableIPv6)) - nwOptions = append(nwOptions, libnetwork.NetworkOptionDriverOpts(netOption)) + nwOptions := []libnetwork.NetworkOption{ + libnetwork.NetworkOptionIpam(ipam.Driver, "", v4Conf, v6Conf, ipam.Options), + libnetwork.NetworkOptionEnableIPv6(enableIPv6), + libnetwork.NetworkOptionDriverOpts(netOption), + libnetwork.NetworkOptionLabels(labels), + } if internal { nwOptions = append(nwOptions, libnetwork.NetworkOptionInternalNetwork()) } diff --git a/daemon/volumes.go b/daemon/volumes.go index 80e6cdfa42..37f4e7fa9f 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -24,11 +24,17 @@ type mounts []container.Mount // volumeToAPIType converts a volume.Volume to the type used by the remote API func volumeToAPIType(v volume.Volume) *types.Volume { - return &types.Volume{ + tv := &types.Volume{ Name: v.Name(), Driver: v.DriverName(), Mountpoint: v.Path(), } + if v, ok := v.(interface { + Labels() map[string]string + }); ok { + tv.Labels = v.Labels() + } + return tv } // Len returns the number of mounts. Used in sorting. @@ -118,7 +124,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo if len(bind.Name) > 0 { // create the volume - v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil) + v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil, nil) if err != nil { return err } diff --git a/hack/vendor.sh b/hack/vendor.sh index e8f41dc518..f619d7766f 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -24,12 +24,12 @@ clone git golang.org/x/net 47990a1ba55743e6ef1affd3a14e5bac8553615d https://gith clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://github.com/golang/sys.git clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3 clone git github.com/docker/go-connections v0.2.0 -clone git github.com/docker/engine-api 68a7b6bebf8f57d559b7788a46c55045438747b9 +clone git github.com/docker/engine-api 9524d7ae81ff55771852b6269f40f2a878315de9 clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837 clone git github.com/imdario/mergo 0.2.1 #get libnetwork packages -clone git github.com/docker/libnetwork v0.7.0-dev.8 +clone git github.com/docker/libnetwork v0.7.0-dev.9 clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4 diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index b11bfc1c78..6d67861d0b 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -6673,6 +6673,134 @@ func (s *DockerSuite) TestBuildWorkdirWindowsPath(c *check.C) { } } +func (s *DockerSuite) TestBuildLabel(c *check.C) { + name := "testbuildlabel" + testLabel := "foo" + + _, err := buildImage(name, ` + FROM `+minimalBaseImage()+` + LABEL default foo +`, false, []string{"--label", testLabel}...) + + if err != nil { + c.Fatal("error building image with labels", err) + } + + res := inspectFieldJSON(c, name, "Config.Labels") + + var labels map[string]string + + if err := json.Unmarshal([]byte(res), &labels); err != nil { + c.Fatal(err) + } + + if _, ok := labels[testLabel]; !ok { + c.Fatal("label not found in image") + } +} + +func (s *DockerSuite) TestBuildLabelCacheCommit(c *check.C) { + name := "testbuildlabelcachecommit" + testLabel := "foo" + + if _, err := buildImage(name, ` + FROM `+minimalBaseImage()+` + LABEL default foo + `, false); err != nil { + c.Fatal(err) + } + + _, err := buildImage(name, ` + FROM `+minimalBaseImage()+` + LABEL default foo +`, true, []string{"--label", testLabel}...) + + if err != nil { + c.Fatal("error building image with labels", err) + } + + res := inspectFieldJSON(c, name, "Config.Labels") + + var labels map[string]string + + if err := json.Unmarshal([]byte(res), &labels); err != nil { + c.Fatal(err) + } + + if _, ok := labels[testLabel]; !ok { + c.Fatal("label not found in image") + } +} + +func (s *DockerSuite) TestBuildLabelMultiple(c *check.C) { + name := "testbuildlabelmultiple" + testLabels := map[string]string{ + "foo": "bar", + "123": "456", + } + + labelArgs := []string{} + + for k, v := range testLabels { + labelArgs = append(labelArgs, "--label", k+"="+v) + } + + _, err := buildImage(name, ` + FROM `+minimalBaseImage()+` + LABEL default foo +`, false, labelArgs...) + + if err != nil { + c.Fatal("error building image with labels", err) + } + + res := inspectFieldJSON(c, name, "Config.Labels") + + var labels map[string]string + + if err := json.Unmarshal([]byte(res), &labels); err != nil { + c.Fatal(err) + } + + for k, v := range testLabels { + if x, ok := labels[k]; !ok || x != v { + c.Fatalf("label %s=%s not found in image", k, v) + } + } +} + +func (s *DockerSuite) TestBuildLabelOverwrite(c *check.C) { + name := "testbuildlabeloverwrite" + testLabel := "foo" + testValue := "bar" + + _, err := buildImage(name, ` + FROM `+minimalBaseImage()+` + LABEL `+testLabel+`+ foo +`, false, []string{"--label", testLabel + "=" + testValue}...) + + if err != nil { + c.Fatal("error building image with labels", err) + } + + res := inspectFieldJSON(c, name, "Config.Labels") + + var labels map[string]string + + if err := json.Unmarshal([]byte(res), &labels); err != nil { + c.Fatal(err) + } + + v, ok := labels[testLabel] + if !ok { + c.Fatal("label not found in image") + } + + if v != testValue { + c.Fatal("label not overwritten") + } +} + func (s *DockerRegistryAuthHtpasswdSuite) TestBuildFromAuthenticatedRegistry(c *check.C) { dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL) diff --git a/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go index fd6da66883..a25b8dd1c8 100644 --- a/integration-cli/docker_cli_network_unix_test.go +++ b/integration-cli/docker_cli_network_unix_test.go @@ -341,6 +341,22 @@ func (s *DockerNetworkSuite) TestDockerNetworkCreateDelete(c *check.C) { assertNwNotAvailable(c, "test") } +func (s *DockerNetworkSuite) TestDockerNetworkCreateLabel(c *check.C) { + testNet := "testnetcreatelabel" + testLabel := "foo" + testValue := "bar" + + dockerCmd(c, "network", "create", "--label", testLabel+"="+testValue, testNet) + assertNwIsAvailable(c, testNet) + + out, _, err := dockerCmdWithError("network", "inspect", "--format='{{ .Labels."+testLabel+" }}'", testNet) + c.Assert(err, check.IsNil) + c.Assert(strings.TrimSpace(out), check.Equals, testValue) + + dockerCmd(c, "network", "rm", testNet) + assertNwNotAvailable(c, testNet) +} + func (s *DockerSuite) TestDockerNetworkDeleteNotExists(c *check.C) { out, _, err := dockerCmdWithError("network", "rm", "test") c.Assert(err, checker.NotNil, check.Commentf("%v", out)) diff --git a/integration-cli/docker_cli_volume_test.go b/integration-cli/docker_cli_volume_test.go index ddbba0db2d..a10316f616 100644 --- a/integration-cli/docker_cli_volume_test.go +++ b/integration-cli/docker_cli_volume_test.go @@ -241,3 +241,43 @@ func (s *DockerSuite) TestVolumeCliCreateWithOpts(c *check.C) { } c.Assert(found, checker.Equals, true) } + +func (s *DockerSuite) TestVolumeCliCreateLabel(c *check.C) { + testVol := "testvolcreatelabel" + testLabel := "foo" + testValue := "bar" + + out, _, err := dockerCmdWithError("volume", "create", "--label", testLabel+"="+testValue, "--name", testVol) + c.Assert(err, check.IsNil) + + out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Labels."+testLabel+" }}'", testVol) + c.Assert(strings.TrimSpace(out), check.Equals, testValue) +} + +func (s *DockerSuite) TestVolumeCliCreateLabelMultiple(c *check.C) { + testVol := "testvolcreatelabel" + + testLabels := map[string]string{ + "foo": "bar", + "baz": "foo", + } + + args := []string{ + "volume", + "create", + "--name", + testVol, + } + + for k, v := range testLabels { + args = append(args, "--label", k+"="+v) + } + + out, _, err := dockerCmdWithError(args...) + c.Assert(err, check.IsNil) + + for k, v := range testLabels { + out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Labels."+k+" }}'", testVol) + c.Assert(strings.TrimSpace(out), check.Equals, v) + } +} diff --git a/vendor/src/github.com/docker/engine-api/client/image_build.go b/vendor/src/github.com/docker/engine-api/client/image_build.go index d5f96cbd54..1612c6a1b8 100644 --- a/vendor/src/github.com/docker/engine-api/client/image_build.go +++ b/vendor/src/github.com/docker/engine-api/client/image_build.go @@ -101,6 +101,11 @@ func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, erro } query.Set("buildargs", string(buildArgsJSON)) + labelsJSON, err := json.Marshal(options.Labels) + if err != nil { + return query, err + } + query.Set("labels", string(labelsJSON)) return query, nil } diff --git a/vendor/src/github.com/docker/engine-api/types/client.go b/vendor/src/github.com/docker/engine-api/types/client.go index 4880140367..f09ad02e11 100644 --- a/vendor/src/github.com/docker/engine-api/types/client.go +++ b/vendor/src/github.com/docker/engine-api/types/client.go @@ -142,6 +142,7 @@ type ImageBuildOptions struct { BuildArgs map[string]string AuthConfigs map[string]AuthConfig Context io.Reader + Labels map[string]string } // ImageBuildResponse holds information diff --git a/vendor/src/github.com/docker/engine-api/types/types.go b/vendor/src/github.com/docker/engine-api/types/types.go index c47df2acbe..18cfbdda0f 100644 --- a/vendor/src/github.com/docker/engine-api/types/types.go +++ b/vendor/src/github.com/docker/engine-api/types/types.go @@ -103,6 +103,13 @@ type GraphDriverData struct { Data map[string]string } +// RootFS returns Image's RootFS description including the layer IDs. +type RootFS struct { + Type string + Layers []string `json:",omitempty"` + BaseLayer string `json:",omitempty"` +} + // ImageInspect contains response of Remote API: // GET "/images/{name:.*}/json" type ImageInspect struct { @@ -122,6 +129,7 @@ type ImageInspect struct { Size int64 VirtualSize int64 GraphDriver GraphDriverData + RootFS RootFS } // Port stores open ports info of container @@ -372,6 +380,7 @@ type Volume struct { Driver string // Driver is the Driver name used to create the volume Mountpoint string // Mountpoint is the location on disk of the volume Status map[string]interface{} `json:",omitempty"` // Status provides low-level status information about the volume + Labels map[string]string // Labels is metadata specific to the volume } // VolumesListResponse contains the response for the remote API: @@ -387,6 +396,7 @@ type VolumeCreateRequest struct { Name string // Name is the requested name of the volume Driver string // Driver is the name of the driver that should be used to create the volume DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume. + Labels map[string]string // Labels holds metadata specific to the volume being created. } // NetworkResource is the body of the "get network" http response message @@ -400,6 +410,7 @@ type NetworkResource struct { Internal bool Containers map[string]EndpointResource Options map[string]string + Labels map[string]string } // EndpointResource contains network resources allocated and used for a container in a network @@ -420,6 +431,7 @@ type NetworkCreate struct { IPAM network.IPAM Internal bool Options map[string]string + Labels map[string]string } // NetworkCreateResponse is the response message sent by the server for network create call diff --git a/vendor/src/github.com/docker/libnetwork/CHANGELOG.md b/vendor/src/github.com/docker/libnetwork/CHANGELOG.md index a2bb0121a2..a46131079e 100644 --- a/vendor/src/github.com/docker/libnetwork/CHANGELOG.md +++ b/vendor/src/github.com/docker/libnetwork/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.7.0-dev.9 (2016-03-18) +- Support labels on networks + ## 0.7.0-dev.8 (2016-03-16) - Windows driver to respect user set MAC address. - Fix possible nil pointer reference in ServeDNS() with concurrent go routines. diff --git a/vendor/src/github.com/docker/libnetwork/Dockerfile.build b/vendor/src/github.com/docker/libnetwork/Dockerfile.build index 8dcfd83255..01b690e955 100644 --- a/vendor/src/github.com/docker/libnetwork/Dockerfile.build +++ b/vendor/src/github.com/docker/libnetwork/Dockerfile.build @@ -1,10 +1,6 @@ -FROM golang:1.4-cross +FROM golang:1.5.3 RUN apt-get update && apt-get -y install iptables -RUN cd /go/src && mkdir -p golang.org/x && \ - cd golang.org/x && git clone https://github.com/golang/tools && \ - cd tools && git checkout release-branch.go1.5 - RUN go get github.com/tools/godep \ github.com/golang/lint/golint \ golang.org/x/tools/cmd/vet \ diff --git a/vendor/src/github.com/docker/libnetwork/network.go b/vendor/src/github.com/docker/libnetwork/network.go index 422d1f2062..e4b6e4adaa 100644 --- a/vendor/src/github.com/docker/libnetwork/network.go +++ b/vendor/src/github.com/docker/libnetwork/network.go @@ -63,6 +63,7 @@ type NetworkInfo interface { Scope() string IPv6Enabled() bool Internal() bool + Labels() map[string]string } // EndpointWalker is a client provided function which will be used to walk the Endpoints. @@ -150,6 +151,7 @@ type network struct { networkType string id string scope string + labels map[string]string ipamType string ipamOptions map[string]string addrSpace string @@ -309,6 +311,14 @@ func (n *network) CopyTo(o datastore.KVObject) error { dstN.internal = n.internal dstN.inDelete = n.inDelete + // copy labels + if dstN.labels == nil { + dstN.labels = make(map[string]string, len(n.labels)) + } + for k, v := range n.labels { + dstN.labels[k] = v + } + for _, v4conf := range n.ipamV4Config { dstV4Conf := &IpamConf{} v4conf.CopyTo(dstV4Conf) @@ -359,6 +369,7 @@ func (n *network) MarshalJSON() ([]byte, error) { netMap["id"] = n.id netMap["networkType"] = n.networkType netMap["scope"] = n.scope + netMap["labels"] = n.labels netMap["ipamType"] = n.ipamType netMap["addrSpace"] = n.addrSpace netMap["enableIPv6"] = n.enableIPv6 @@ -411,6 +422,15 @@ func (n *network) UnmarshalJSON(b []byte) (err error) { n.networkType = netMap["networkType"].(string) n.enableIPv6 = netMap["enableIPv6"].(bool) + // if we weren't unmarshaling to netMap we could simply set n.labels + // unfortunately, we can't because map[string]interface{} != map[string]string + if labels, ok := netMap["labels"].(map[string]interface{}); ok { + n.labels = make(map[string]string, len(labels)) + for label, value := range labels { + n.labels[label] = value.(string) + } + } + if v, ok := netMap["generic"]; ok { n.generic = v.(map[string]interface{}) // Restore opts in their map[string]string form @@ -539,7 +559,7 @@ func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ip } } -// NetworkOptionDriverOpts function returns an option setter for any parameter described by a map +// NetworkOptionDriverOpts function returns an option setter for any driver parameter described by a map func NetworkOptionDriverOpts(opts map[string]string) NetworkOption { return func(n *network) { if n.generic == nil { @@ -553,6 +573,13 @@ func NetworkOptionDriverOpts(opts map[string]string) NetworkOption { } } +// NetworkOptionLabels function returns an option setter for labels specific to a network +func NetworkOptionLabels(labels map[string]string) NetworkOption { + return func(n *network) { + n.labels = labels + } +} + // NetworkOptionDeferIPv6Alloc instructs the network to defer the IPV6 address allocation until after the endpoint has been created // It is being provided to support the specific docker daemon flags where user can deterministically assign an IPv6 address // to a container as combination of fixed-cidr-v6 + mac-address @@ -1285,3 +1312,15 @@ func (n *network) IPv6Enabled() bool { return n.enableIPv6 } + +func (n *network) Labels() map[string]string { + n.Lock() + defer n.Unlock() + + var lbls = make(map[string]string, len(n.labels)) + for k, v := range n.labels { + lbls[k] = v + } + + return lbls +} diff --git a/vendor/src/github.com/docker/libnetwork/osl/interface_linux.go b/vendor/src/github.com/docker/libnetwork/osl/interface_linux.go index 205e3a3909..081ff2eda9 100644 --- a/vendor/src/github.com/docker/libnetwork/osl/interface_linux.go +++ b/vendor/src/github.com/docker/libnetwork/osl/interface_linux.go @@ -313,8 +313,8 @@ func configureInterface(iface netlink.Link, i *nwIface) error { }{ {setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, i.DstName())}, {setInterfaceMAC, fmt.Sprintf("error setting interface %q MAC to %q", ifaceName, i.MacAddress())}, - {setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %q", ifaceName, i.Address())}, - {setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %q", ifaceName, i.AddressIPv6())}, + {setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %v", ifaceName, i.Address())}, + {setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %v", ifaceName, i.AddressIPv6())}, {setInterfaceMaster, fmt.Sprintf("error setting interface %q master to %q", ifaceName, i.DstMaster())}, } diff --git a/volume/drivers/adapter.go b/volume/drivers/adapter.go index 1468b34344..e7ca3d5034 100644 --- a/volume/drivers/adapter.go +++ b/volume/drivers/adapter.go @@ -22,7 +22,8 @@ func (a *volumeDriverAdapter) Create(name string, opts map[string]string) (volum return &volumeAdapter{ proxy: a.proxy, name: name, - driverName: a.name}, nil + driverName: a.name, + }, nil } func (a *volumeDriverAdapter) Remove(v volume.Volume) error { diff --git a/volume/drivers/extpoint.go b/volume/drivers/extpoint.go index d705b8a597..a55da5aff9 100644 --- a/volume/drivers/extpoint.go +++ b/volume/drivers/extpoint.go @@ -21,7 +21,7 @@ const extName = "VolumeDriver" // NewVolumeDriver returns a driver has the given name mapped on the given client. func NewVolumeDriver(name string, c client) volume.Driver { proxy := &volumeDriverProxy{c} - return &volumeDriverAdapter{name, proxy} + return &volumeDriverAdapter{name: name, proxy: proxy} } type opts map[string]string diff --git a/volume/local/local.go b/volume/local/local.go index b154a36e7b..2d3ac4448a 100644 --- a/volume/local/local.go +++ b/volume/local/local.go @@ -77,6 +77,10 @@ func New(scope string, rootUID, rootGID int) (*Root, error) { } for _, d := range dirs { + if !d.IsDir() { + continue + } + name := filepath.Base(d.Name()) v := &localVolume{ driverName: r.Name(), @@ -198,7 +202,7 @@ func (r *Root) Remove(v volume.Volume) error { lv, ok := v.(*localVolume) if !ok { - return fmt.Errorf("unknown volume type") + return fmt.Errorf("unknown volume type %T", v) } realPath, err := filepath.EvalSymlinks(lv.path) diff --git a/volume/store/store.go b/volume/store/store.go index 6fe0717b26..1686b93b15 100644 --- a/volume/store/store.go +++ b/volume/store/store.go @@ -1,22 +1,77 @@ package store import ( + "bytes" + "encoding/json" + "os" + "path/filepath" "sync" + "time" "github.com/Sirupsen/logrus" + "github.com/boltdb/bolt" "github.com/docker/docker/pkg/locker" "github.com/docker/docker/volume" "github.com/docker/docker/volume/drivers" ) +const ( + volumeDataDir = "volumes" + volumeBucketName = "volumes" +) + +type volumeMetadata struct { + Name string + Labels map[string]string +} + +type volumeWithLabels struct { + volume.Volume + labels map[string]string +} + +func (v volumeWithLabels) Labels() map[string]string { + return v.labels +} + // New initializes a VolumeStore to keep // reference counting of volumes in the system. -func New() *VolumeStore { - return &VolumeStore{ - locks: &locker.Locker{}, - names: make(map[string]volume.Volume), - refs: make(map[string][]string), +func New(rootPath string) (*VolumeStore, error) { + vs := &VolumeStore{ + locks: &locker.Locker{}, + names: make(map[string]volume.Volume), + refs: make(map[string][]string), + labels: make(map[string]map[string]string), } + + if rootPath != "" { + // initialize metadata store + volPath := filepath.Join(rootPath, volumeDataDir) + if err := os.MkdirAll(volPath, 750); err != nil { + return nil, err + } + + dbPath := filepath.Join(volPath, "metadata.db") + + var err error + vs.db, err = bolt.Open(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second}) + if err != nil { + return nil, err + } + + // initialize volumes bucket + if err := vs.db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucketIfNotExists([]byte(volumeBucketName)); err != nil { + return err + } + + return nil + }); err != nil { + return nil, err + } + } + + return vs, nil } func (s *VolumeStore) getNamed(name string) (volume.Volume, bool) { @@ -39,6 +94,7 @@ func (s *VolumeStore) purge(name string) { s.globalLock.Lock() delete(s.names, name) delete(s.refs, name) + delete(s.labels, name) s.globalLock.Unlock() } @@ -51,6 +107,9 @@ type VolumeStore struct { names map[string]volume.Volume // refs stores the volume name and the list of things referencing it refs map[string][]string + // labels stores volume labels for each volume + labels map[string]map[string]string + db *bolt.DB } // List proxies to all registered volume drivers to get the full list of volumes @@ -137,12 +196,12 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) { // CreateWithRef creates a volume with the given name and driver and stores the ref // This is just like Create() except we store the reference while holding the lock. // This ensures there's no race between creating a volume and then storing a reference. -func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts map[string]string) (volume.Volume, error) { +func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts, labels map[string]string) (volume.Volume, error) { name = normaliseVolumeName(name) s.locks.Lock(name) defer s.locks.Unlock(name) - v, err := s.create(name, driverName, opts) + v, err := s.create(name, driverName, opts, labels) if err != nil { return nil, &OpErr{Err: err, Name: name, Op: "create"} } @@ -152,12 +211,12 @@ func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts map[strin } // Create creates a volume with the given name and driver. -func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) { +func (s *VolumeStore) Create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) { name = normaliseVolumeName(name) s.locks.Lock(name) defer s.locks.Unlock(name) - v, err := s.create(name, driverName, opts) + v, err := s.create(name, driverName, opts, labels) if err != nil { return nil, &OpErr{Err: err, Name: name, Op: "create"} } @@ -169,7 +228,7 @@ func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (v // If a volume with the name is already known, it will ask the stored driver for the volume. // If the passed in driver name does not match the driver name which is stored for the given volume name, an error is returned. // It is expected that callers of this function hold any necessary locks. -func (s *VolumeStore) create(name, driverName string, opts map[string]string) (volume.Volume, error) { +func (s *VolumeStore) create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) { // Validate the name in a platform-specific manner valid, err := volume.IsVolumeNameValid(name) if err != nil { @@ -205,7 +264,33 @@ func (s *VolumeStore) create(name, driverName string, opts map[string]string) (v if v, _ := vd.Get(name); v != nil { return v, nil } - return vd.Create(name, opts) + v, err := vd.Create(name, opts) + if err != nil { + return nil, err + } + s.labels[name] = labels + + if s.db != nil { + metadata := &volumeMetadata{ + Name: name, + Labels: labels, + } + + volData, err := json.Marshal(metadata) + if err != nil { + return nil, err + } + + if err := s.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(volumeBucketName)) + err := b.Put([]byte(name), volData) + return err + }); err != nil { + return nil, err + } + } + + return volumeWithLabels{v, labels}, nil } // GetWithRef gets a volume with the given name from the passed in driver and stores the ref @@ -227,6 +312,9 @@ func (s *VolumeStore) GetWithRef(name, driverName, ref string) (volume.Volume, e } s.setNamed(v, ref) + if labels, ok := s.labels[name]; ok { + return volumeWithLabels{v, labels}, nil + } return v, nil } @@ -248,13 +336,43 @@ func (s *VolumeStore) Get(name string) (volume.Volume, error) { // if the driver is unknown it probes all drivers until it finds the first volume with that name. // it is expected that callers of this function hold any necessary locks func (s *VolumeStore) getVolume(name string) (volume.Volume, error) { + labels := map[string]string{} + + if s.db != nil { + // get meta + if err := s.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(volumeBucketName)) + data := b.Get([]byte(name)) + + if string(data) == "" { + return nil + } + + var meta volumeMetadata + buf := bytes.NewBuffer(data) + + if err := json.NewDecoder(buf).Decode(&meta); err != nil { + return err + } + labels = meta.Labels + + return nil + }); err != nil { + return nil, err + } + } + logrus.Debugf("Getting volume reference for name: %s", name) if v, exists := s.names[name]; exists { vd, err := volumedrivers.GetDriver(v.DriverName()) if err != nil { return nil, err } - return vd.Get(name) + vol, err := vd.Get(name) + if err != nil { + return nil, err + } + return volumeWithLabels{vol, labels}, nil } logrus.Debugf("Probing all drivers for volume with name: %s", name) @@ -268,7 +386,8 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) { if err != nil { continue } - return v, nil + + return volumeWithLabels{v, labels}, nil } return nil, errNoSuchVolume } @@ -289,7 +408,8 @@ func (s *VolumeStore) Remove(v volume.Volume) error { } logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name) - if err := vd.Remove(v); err != nil { + vol := withoutLabels(v) + if err := vd.Remove(vol); err != nil { return &OpErr{Err: err, Name: name, Op: "remove"} } @@ -372,3 +492,11 @@ func (s *VolumeStore) filter(vols []volume.Volume, f filterFunc) []volume.Volume } return ls } + +func withoutLabels(v volume.Volume) volume.Volume { + if vol, ok := v.(volumeWithLabels); ok { + return vol.Volume + } + + return v +} diff --git a/volume/store/store_test.go b/volume/store/store_test.go index 7c3f730ef0..4f3e3cbdf1 100644 --- a/volume/store/store_test.go +++ b/volume/store/store_test.go @@ -12,8 +12,11 @@ import ( func TestCreate(t *testing.T) { volumedrivers.Register(vt.NewFakeDriver("fake"), "fake") defer volumedrivers.Unregister("fake") - s := New() - v, err := s.Create("fake1", "fake", nil) + s, err := New("") + if err != nil { + t.Fatal(err) + } + v, err := s.Create("fake1", "fake", nil, nil) if err != nil { t.Fatal(err) } @@ -24,11 +27,11 @@ func TestCreate(t *testing.T) { t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l) } - if _, err := s.Create("none", "none", nil); err == nil { + if _, err := s.Create("none", "none", nil, nil); err == nil { t.Fatalf("Expected unknown driver error, got nil") } - _, err = s.Create("fakeerror", "fake", map[string]string{"error": "create error"}) + _, err = s.Create("fakeerror", "fake", map[string]string{"error": "create error"}, nil) expected := &OpErr{Op: "create", Name: "fakeerror", Err: errors.New("create error")} if err != nil && err.Error() != expected.Error() { t.Fatalf("Expected create fakeError: create error, got %v", err) @@ -40,7 +43,10 @@ func TestRemove(t *testing.T) { volumedrivers.Register(vt.NewFakeDriver("noop"), "noop") defer volumedrivers.Unregister("fake") defer volumedrivers.Unregister("noop") - s := New() + s, err := New("") + if err != nil { + t.Fatal(err) + } // doing string compare here since this error comes directly from the driver expected := "no such volume" @@ -48,7 +54,7 @@ func TestRemove(t *testing.T) { t.Fatalf("Expected error %q, got %v", expected, err) } - v, err := s.CreateWithRef("fake1", "fake", "fake", nil) + v, err := s.CreateWithRef("fake1", "fake", "fake", nil, nil) if err != nil { t.Fatal(err) } @@ -71,11 +77,14 @@ func TestList(t *testing.T) { defer volumedrivers.Unregister("fake") defer volumedrivers.Unregister("fake2") - s := New() - if _, err := s.Create("test", "fake", nil); err != nil { + s, err := New("") + if err != nil { t.Fatal(err) } - if _, err := s.Create("test2", "fake2", nil); err != nil { + if _, err := s.Create("test", "fake", nil, nil); err != nil { + t.Fatal(err) + } + if _, err := s.Create("test2", "fake2", nil, nil); err != nil { t.Fatal(err) } @@ -88,7 +97,10 @@ func TestList(t *testing.T) { } // and again with a new store - s = New() + s, err = New("") + if err != nil { + t.Fatal(err) + } ls, _, err = s.List() if err != nil { t.Fatal(err) @@ -103,15 +115,18 @@ func TestFilterByDriver(t *testing.T) { volumedrivers.Register(vt.NewFakeDriver("noop"), "noop") defer volumedrivers.Unregister("fake") defer volumedrivers.Unregister("noop") - s := New() + s, err := New("") + if err != nil { + t.Fatal(err) + } - if _, err := s.Create("fake1", "fake", nil); err != nil { + if _, err := s.Create("fake1", "fake", nil, nil); err != nil { t.Fatal(err) } - if _, err := s.Create("fake2", "fake", nil); err != nil { + if _, err := s.Create("fake2", "fake", nil, nil); err != nil { t.Fatal(err) } - if _, err := s.Create("fake3", "noop", nil); err != nil { + if _, err := s.Create("fake3", "noop", nil, nil); err != nil { t.Fatal(err) } @@ -128,11 +143,15 @@ func TestFilterByUsed(t *testing.T) { volumedrivers.Register(vt.NewFakeDriver("fake"), "fake") volumedrivers.Register(vt.NewFakeDriver("noop"), "noop") - s := New() - if _, err := s.CreateWithRef("fake1", "fake", "volReference", nil); err != nil { + s, err := New("") + if err != nil { t.Fatal(err) } - if _, err := s.Create("fake2", "fake", nil); err != nil { + + if _, err := s.CreateWithRef("fake1", "fake", "volReference", nil, nil); err != nil { + t.Fatal(err) + } + if _, err := s.Create("fake2", "fake", nil, nil); err != nil { t.Fatal(err) } @@ -161,8 +180,12 @@ func TestFilterByUsed(t *testing.T) { func TestDerefMultipleOfSameRef(t *testing.T) { volumedrivers.Register(vt.NewFakeDriver("fake"), "fake") - s := New() - v, err := s.CreateWithRef("fake1", "fake", "volReference", nil) + s, err := New("") + if err != nil { + t.Fatal(err) + } + + v, err := s.CreateWithRef("fake1", "fake", "volReference", nil, nil) if err != nil { t.Fatal(err) }