Add Label support for Images (build), Networks and Volumes on Creation
| ... | ... |
@@ -62,6 +62,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 62 | 62 |
cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
|
| 63 | 63 |
isolation := cmd.String([]string{"-isolation"}, "", "Container isolation technology")
|
| 64 | 64 |
|
| 65 |
+ flLabels := opts.NewListOpts(nil) |
|
| 66 |
+ cmd.Var(&flLabels, []string{"-label"}, "Set metadata for an image")
|
|
| 67 |
+ |
|
| 65 | 68 |
ulimits := make(map[string]*units.Ulimit) |
| 66 | 69 |
flUlimits := runconfigopts.NewUlimitOpt(&ulimits) |
| 67 | 70 |
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
|
| ... | ... |
@@ -230,6 +233,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 230 | 230 |
Ulimits: flUlimits.GetList(), |
| 231 | 231 |
BuildArgs: runconfigopts.ConvertKVStringsToMap(flBuildArg.GetAll()), |
| 232 | 232 |
AuthConfigs: cli.retrieveAuthConfigs(), |
| 233 |
+ Labels: runconfigopts.ConvertKVStringsToMap(flLabels.GetAll()), |
|
| 233 | 234 |
} |
| 234 | 235 |
|
| 235 | 236 |
response, err := cli.client.ImageBuild(context.Background(), options) |
| ... | ... |
@@ -44,6 +44,7 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
|
| 44 | 44 |
flIpamGateway := opts.NewListOpts(nil) |
| 45 | 45 |
flIpamAux := opts.NewMapOpts(nil, nil) |
| 46 | 46 |
flIpamOpt := opts.NewMapOpts(nil, nil) |
| 47 |
+ flLabels := opts.NewListOpts(nil) |
|
| 47 | 48 |
|
| 48 | 49 |
cmd.Var(&flIpamSubnet, []string{"-subnet"}, "subnet in CIDR format that represents a network segment")
|
| 49 | 50 |
cmd.Var(&flIpamIPRange, []string{"-ip-range"}, "allocate container ip from a sub-range")
|
| ... | ... |
@@ -51,6 +52,7 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
|
| 51 | 51 |
cmd.Var(flIpamAux, []string{"-aux-address"}, "auxiliary ipv4 or ipv6 addresses used by Network driver")
|
| 52 | 52 |
cmd.Var(flOpts, []string{"o", "-opt"}, "set driver specific options")
|
| 53 | 53 |
cmd.Var(flIpamOpt, []string{"-ipam-opt"}, "set IPAM driver specific options")
|
| 54 |
+ cmd.Var(&flLabels, []string{"-label"}, "set metadata on a network")
|
|
| 54 | 55 |
|
| 55 | 56 |
flInternal := cmd.Bool([]string{"-internal"}, false, "restricts external access to the network")
|
| 56 | 57 |
flIPv6 := cmd.Bool([]string{"-ipv6"}, false, "enable IPv6 networking")
|
| ... | ... |
@@ -82,6 +84,7 @@ func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
|
| 82 | 82 |
CheckDuplicate: true, |
| 83 | 83 |
Internal: *flInternal, |
| 84 | 84 |
EnableIPv6: *flIPv6, |
| 85 |
+ Labels: runconfigopts.ConvertKVStringsToMap(flLabels.GetAll()), |
|
| 85 | 86 |
} |
| 86 | 87 |
|
| 87 | 88 |
resp, err := cli.client.NetworkCreate(context.Background(), nc) |
| ... | ... |
@@ -10,6 +10,7 @@ import ( |
| 10 | 10 |
Cli "github.com/docker/docker/cli" |
| 11 | 11 |
"github.com/docker/docker/opts" |
| 12 | 12 |
flag "github.com/docker/docker/pkg/mflag" |
| 13 |
+ runconfigopts "github.com/docker/docker/runconfig/opts" |
|
| 13 | 14 |
"github.com/docker/engine-api/types" |
| 14 | 15 |
"github.com/docker/engine-api/types/filters" |
| 15 | 16 |
) |
| ... | ... |
@@ -128,6 +129,9 @@ func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
|
| 128 | 128 |
flDriverOpts := opts.NewMapOpts(nil, nil) |
| 129 | 129 |
cmd.Var(flDriverOpts, []string{"o", "-opt"}, "Set driver specific options")
|
| 130 | 130 |
|
| 131 |
+ flLabels := opts.NewListOpts(nil) |
|
| 132 |
+ cmd.Var(&flLabels, []string{"-label"}, "Set metadata for a volume")
|
|
| 133 |
+ |
|
| 131 | 134 |
cmd.Require(flag.Exact, 0) |
| 132 | 135 |
cmd.ParseFlags(args, true) |
| 133 | 136 |
|
| ... | ... |
@@ -135,6 +139,7 @@ func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
|
| 135 | 135 |
Driver: *flDriver, |
| 136 | 136 |
DriverOpts: flDriverOpts.GetAll(), |
| 137 | 137 |
Name: *flName, |
| 138 |
+ Labels: runconfigopts.ConvertKVStringsToMap(flLabels.GetAll()), |
|
| 138 | 139 |
} |
| 139 | 140 |
|
| 140 | 141 |
vol, err := cli.client.VolumeCreate(context.Background(), volReq) |
| ... | ... |
@@ -82,6 +82,15 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui |
| 82 | 82 |
} |
| 83 | 83 |
options.BuildArgs = buildArgs |
| 84 | 84 |
} |
| 85 |
+ var labels = map[string]string{}
|
|
| 86 |
+ labelsJSON := r.FormValue("labels")
|
|
| 87 |
+ if labelsJSON != "" {
|
|
| 88 |
+ if err := json.NewDecoder(strings.NewReader(labelsJSON)).Decode(&labels); err != nil {
|
|
| 89 |
+ return nil, err |
|
| 90 |
+ } |
|
| 91 |
+ options.Labels = labels |
|
| 92 |
+ } |
|
| 93 |
+ |
|
| 85 | 94 |
return options, nil |
| 86 | 95 |
} |
| 87 | 96 |
|
| ... | ... |
@@ -12,7 +12,7 @@ type Backend interface {
|
| 12 | 12 |
GetNetworkByName(idName string) (libnetwork.Network, error) |
| 13 | 13 |
GetNetworksByID(partialID string) []libnetwork.Network |
| 14 | 14 |
GetAllNetworks() []libnetwork.Network |
| 15 |
- CreateNetwork(name, driver string, ipam network.IPAM, options map[string]string, internal bool, enableIPv6 bool) (libnetwork.Network, error) |
|
| 15 |
+ CreateNetwork(name, driver string, ipam network.IPAM, options map[string]string, labels map[string]string, internal bool, enableIPv6 bool) (libnetwork.Network, error) |
|
| 16 | 16 |
ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error |
| 17 | 17 |
DisconnectContainerFromNetwork(containerName string, network libnetwork.Network, force bool) error |
| 18 | 18 |
DeleteNetwork(name string) error |
| ... | ... |
@@ -91,7 +91,7 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr |
| 91 | 91 |
warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID())
|
| 92 | 92 |
} |
| 93 | 93 |
|
| 94 |
- nw, err = n.backend.CreateNetwork(create.Name, create.Driver, create.IPAM, create.Options, create.Internal, create.EnableIPv6) |
|
| 94 |
+ nw, err = n.backend.CreateNetwork(create.Name, create.Driver, create.IPAM, create.Options, create.Labels, create.Internal, create.EnableIPv6) |
|
| 95 | 95 |
if err != nil {
|
| 96 | 96 |
return err |
| 97 | 97 |
} |
| ... | ... |
@@ -163,16 +163,18 @@ func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
|
| 163 | 163 |
return r |
| 164 | 164 |
} |
| 165 | 165 |
|
| 166 |
+ info := nw.Info() |
|
| 166 | 167 |
r.Name = nw.Name() |
| 167 | 168 |
r.ID = nw.ID() |
| 168 |
- r.Scope = nw.Info().Scope() |
|
| 169 |
+ r.Scope = info.Scope() |
|
| 169 | 170 |
r.Driver = nw.Type() |
| 170 |
- r.EnableIPv6 = nw.Info().IPv6Enabled() |
|
| 171 |
- r.Internal = nw.Info().Internal() |
|
| 172 |
- r.Options = nw.Info().DriverOptions() |
|
| 171 |
+ r.EnableIPv6 = info.IPv6Enabled() |
|
| 172 |
+ r.Internal = info.Internal() |
|
| 173 |
+ r.Options = info.DriverOptions() |
|
| 173 | 174 |
r.Containers = make(map[string]types.EndpointResource) |
| 174 |
- buildIpamResources(r, nw) |
|
| 175 |
- r.Internal = nw.Info().Internal() |
|
| 175 |
+ buildIpamResources(r, info) |
|
| 176 |
+ r.Internal = info.Internal() |
|
| 177 |
+ r.Labels = info.Labels() |
|
| 176 | 178 |
|
| 177 | 179 |
epl := nw.Endpoints() |
| 178 | 180 |
for _, e := range epl {
|
| ... | ... |
@@ -191,10 +193,10 @@ func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
|
| 191 | 191 |
return r |
| 192 | 192 |
} |
| 193 | 193 |
|
| 194 |
-func buildIpamResources(r *types.NetworkResource, nw libnetwork.Network) {
|
|
| 195 |
- id, opts, ipv4conf, ipv6conf := nw.Info().IpamConfig() |
|
| 194 |
+func buildIpamResources(r *types.NetworkResource, nwInfo libnetwork.NetworkInfo) {
|
|
| 195 |
+ id, opts, ipv4conf, ipv6conf := nwInfo.IpamConfig() |
|
| 196 | 196 |
|
| 197 |
- ipv4Info, ipv6Info := nw.Info().IpamInfo() |
|
| 197 |
+ ipv4Info, ipv6Info := nwInfo.IpamInfo() |
|
| 198 | 198 |
|
| 199 | 199 |
r.IPAM.Driver = id |
| 200 | 200 |
|
| ... | ... |
@@ -10,7 +10,6 @@ import ( |
| 10 | 10 |
type Backend interface {
|
| 11 | 11 |
Volumes(filter string) ([]*types.Volume, []string, error) |
| 12 | 12 |
VolumeInspect(name string) (*types.Volume, error) |
| 13 |
- VolumeCreate(name, driverName string, |
|
| 14 |
- opts map[string]string) (*types.Volume, error) |
|
| 13 |
+ VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) |
|
| 15 | 14 |
VolumeRm(name string) error |
| 16 | 15 |
} |
| ... | ... |
@@ -47,7 +47,7 @@ func (v *volumeRouter) postVolumesCreate(ctx context.Context, w http.ResponseWri |
| 47 | 47 |
return err |
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 |
- volume, err := v.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts) |
|
| 50 |
+ volume, err := v.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels) |
|
| 51 | 51 |
if err != nil {
|
| 52 | 52 |
return err |
| 53 | 53 |
} |
| ... | ... |
@@ -218,6 +218,10 @@ func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context |
| 218 | 218 |
|
| 219 | 219 |
var shortImgID string |
| 220 | 220 |
for i, n := range b.dockerfile.Children {
|
| 221 |
+ // we only want to add labels to the last layer |
|
| 222 |
+ if i == len(b.dockerfile.Children)-1 {
|
|
| 223 |
+ b.addLabels() |
|
| 224 |
+ } |
|
| 221 | 225 |
select {
|
| 222 | 226 |
case <-b.cancelled: |
| 223 | 227 |
logrus.Debug("Builder: build cancelled!")
|
| ... | ... |
@@ -37,6 +37,19 @@ import ( |
| 37 | 37 |
"github.com/docker/engine-api/types/strslice" |
| 38 | 38 |
) |
| 39 | 39 |
|
| 40 |
+func (b *Builder) addLabels() {
|
|
| 41 |
+ // merge labels |
|
| 42 |
+ if len(b.options.Labels) > 0 {
|
|
| 43 |
+ logrus.Debugf("[BUILDER] setting labels %v", b.options.Labels)
|
|
| 44 |
+ if b.runConfig.Labels == nil {
|
|
| 45 |
+ b.runConfig.Labels = make(map[string]string) |
|
| 46 |
+ } |
|
| 47 |
+ for kL, vL := range b.options.Labels {
|
|
| 48 |
+ b.runConfig.Labels[kL] = vL |
|
| 49 |
+ } |
|
| 50 |
+ } |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 40 | 53 |
func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) error {
|
| 41 | 54 |
if b.disableCommit {
|
| 42 | 55 |
return nil |
| ... | ... |
@@ -45,6 +58,7 @@ func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) e |
| 45 | 45 |
return fmt.Errorf("Please provide a source image with `from` prior to commit")
|
| 46 | 46 |
} |
| 47 | 47 |
b.runConfig.Image = b.image |
| 48 |
+ |
|
| 48 | 49 |
if id == "" {
|
| 49 | 50 |
cmd := b.runConfig.Cmd |
| 50 | 51 |
if runtime.GOOS != "windows" {
|
| ... | ... |
@@ -81,6 +95,7 @@ func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) e |
| 81 | 81 |
if err != nil {
|
| 82 | 82 |
return err |
| 83 | 83 |
} |
| 84 |
+ |
|
| 84 | 85 |
b.image = imageID |
| 85 | 86 |
return nil |
| 86 | 87 |
} |
| ... | ... |
@@ -167,12 +167,12 @@ func (daemon *Daemon) setRWLayer(container *container.Container) error {
|
| 167 | 167 |
|
| 168 | 168 |
// VolumeCreate creates a volume with the specified name, driver, and opts |
| 169 | 169 |
// This is called directly from the remote API |
| 170 |
-func (daemon *Daemon) VolumeCreate(name, driverName string, opts map[string]string) (*types.Volume, error) {
|
|
| 170 |
+func (daemon *Daemon) VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) {
|
|
| 171 | 171 |
if name == "" {
|
| 172 | 172 |
name = stringid.GenerateNonCryptoID() |
| 173 | 173 |
} |
| 174 | 174 |
|
| 175 |
- v, err := daemon.volumes.Create(name, driverName, opts) |
|
| 175 |
+ v, err := daemon.volumes.Create(name, driverName, opts, labels) |
|
| 176 | 176 |
if err != nil {
|
| 177 | 177 |
if volumestore.IsNameConflict(err) {
|
| 178 | 178 |
return nil, fmt.Errorf("A volume named %s already exists. Choose a different volume name.", name)
|
| ... | ... |
@@ -45,7 +45,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain |
| 45 | 45 |
return fmt.Errorf("cannot mount volume over existing file, file exists %s", path)
|
| 46 | 46 |
} |
| 47 | 47 |
|
| 48 |
- v, err := daemon.volumes.CreateWithRef(name, hostConfig.VolumeDriver, container.ID, nil) |
|
| 48 |
+ v, err := daemon.volumes.CreateWithRef(name, hostConfig.VolumeDriver, container.ID, nil, nil) |
|
| 49 | 49 |
if err != nil {
|
| 50 | 50 |
return err |
| 51 | 51 |
} |
| ... | ... |
@@ -33,7 +33,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain |
| 33 | 33 |
|
| 34 | 34 |
// Create the volume in the volume driver. If it doesn't exist, |
| 35 | 35 |
// a new one will be created. |
| 36 |
- v, err := daemon.volumes.CreateWithRef(mp.Name, volumeDriver, container.ID, nil) |
|
| 36 |
+ v, err := daemon.volumes.CreateWithRef(mp.Name, volumeDriver, container.ID, nil, nil) |
|
| 37 | 37 |
if err != nil {
|
| 38 | 38 |
return err |
| 39 | 39 |
} |
| ... | ... |
@@ -1498,7 +1498,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore, |
| 1498 | 1498 |
} |
| 1499 | 1499 |
|
| 1500 | 1500 |
volumedrivers.Register(volumesDriver, volumesDriver.Name()) |
| 1501 |
- return store.New(), nil |
|
| 1501 |
+ return store.New(config.Root) |
|
| 1502 | 1502 |
} |
| 1503 | 1503 |
|
| 1504 | 1504 |
// AuthenticateToRegistry checks the validity of credentials in authConfig |
| ... | ... |
@@ -118,10 +118,14 @@ func TestGetContainer(t *testing.T) {
|
| 118 | 118 |
} |
| 119 | 119 |
|
| 120 | 120 |
func initDaemonWithVolumeStore(tmp string) (*Daemon, error) {
|
| 121 |
+ var err error |
|
| 121 | 122 |
daemon := &Daemon{
|
| 122 | 123 |
repository: tmp, |
| 123 | 124 |
root: tmp, |
| 124 |
- volumes: store.New(), |
|
| 125 |
+ } |
|
| 126 |
+ daemon.volumes, err = store.New(tmp) |
|
| 127 |
+ if err != nil {
|
|
| 128 |
+ return nil, err |
|
| 125 | 129 |
} |
| 126 | 130 |
|
| 127 | 131 |
volumesDriver, err := local.New(tmp, 0, 0) |
| ... | ... |
@@ -91,22 +91,23 @@ func (daemon *Daemon) GetAllNetworks() []libnetwork.Network {
|
| 91 | 91 |
} |
| 92 | 92 |
|
| 93 | 93 |
// CreateNetwork creates a network with the given name, driver and other optional parameters |
| 94 |
-func (daemon *Daemon) CreateNetwork(name, driver string, ipam network.IPAM, netOption map[string]string, internal bool, enableIPv6 bool) (libnetwork.Network, error) {
|
|
| 94 |
+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) {
|
|
| 95 | 95 |
c := daemon.netController |
| 96 | 96 |
if driver == "" {
|
| 97 | 97 |
driver = c.Config().Daemon.DefaultDriver |
| 98 | 98 |
} |
| 99 | 99 |
|
| 100 |
- nwOptions := []libnetwork.NetworkOption{}
|
|
| 101 |
- |
|
| 102 | 100 |
v4Conf, v6Conf, err := getIpamConfig(ipam.Config) |
| 103 | 101 |
if err != nil {
|
| 104 | 102 |
return nil, err |
| 105 | 103 |
} |
| 106 | 104 |
|
| 107 |
- nwOptions = append(nwOptions, libnetwork.NetworkOptionIpam(ipam.Driver, "", v4Conf, v6Conf, ipam.Options)) |
|
| 108 |
- nwOptions = append(nwOptions, libnetwork.NetworkOptionEnableIPv6(enableIPv6)) |
|
| 109 |
- nwOptions = append(nwOptions, libnetwork.NetworkOptionDriverOpts(netOption)) |
|
| 105 |
+ nwOptions := []libnetwork.NetworkOption{
|
|
| 106 |
+ libnetwork.NetworkOptionIpam(ipam.Driver, "", v4Conf, v6Conf, ipam.Options), |
|
| 107 |
+ libnetwork.NetworkOptionEnableIPv6(enableIPv6), |
|
| 108 |
+ libnetwork.NetworkOptionDriverOpts(netOption), |
|
| 109 |
+ libnetwork.NetworkOptionLabels(labels), |
|
| 110 |
+ } |
|
| 110 | 111 |
if internal {
|
| 111 | 112 |
nwOptions = append(nwOptions, libnetwork.NetworkOptionInternalNetwork()) |
| 112 | 113 |
} |
| ... | ... |
@@ -24,11 +24,17 @@ type mounts []container.Mount |
| 24 | 24 |
|
| 25 | 25 |
// volumeToAPIType converts a volume.Volume to the type used by the remote API |
| 26 | 26 |
func volumeToAPIType(v volume.Volume) *types.Volume {
|
| 27 |
- return &types.Volume{
|
|
| 27 |
+ tv := &types.Volume{
|
|
| 28 | 28 |
Name: v.Name(), |
| 29 | 29 |
Driver: v.DriverName(), |
| 30 | 30 |
Mountpoint: v.Path(), |
| 31 | 31 |
} |
| 32 |
+ if v, ok := v.(interface {
|
|
| 33 |
+ Labels() map[string]string |
|
| 34 |
+ }); ok {
|
|
| 35 |
+ tv.Labels = v.Labels() |
|
| 36 |
+ } |
|
| 37 |
+ return tv |
|
| 32 | 38 |
} |
| 33 | 39 |
|
| 34 | 40 |
// Len returns the number of mounts. Used in sorting. |
| ... | ... |
@@ -118,7 +124,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 118 | 118 |
|
| 119 | 119 |
if len(bind.Name) > 0 {
|
| 120 | 120 |
// create the volume |
| 121 |
- v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil) |
|
| 121 |
+ v, err := daemon.volumes.CreateWithRef(bind.Name, bind.Driver, container.ID, nil, nil) |
|
| 122 | 122 |
if err != nil {
|
| 123 | 123 |
return err |
| 124 | 124 |
} |
| ... | ... |
@@ -24,12 +24,12 @@ clone git golang.org/x/net 47990a1ba55743e6ef1affd3a14e5bac8553615d https://gith |
| 24 | 24 |
clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://github.com/golang/sys.git |
| 25 | 25 |
clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3 |
| 26 | 26 |
clone git github.com/docker/go-connections v0.2.0 |
| 27 |
-clone git github.com/docker/engine-api 68a7b6bebf8f57d559b7788a46c55045438747b9 |
|
| 27 |
+clone git github.com/docker/engine-api 9524d7ae81ff55771852b6269f40f2a878315de9 |
|
| 28 | 28 |
clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837 |
| 29 | 29 |
clone git github.com/imdario/mergo 0.2.1 |
| 30 | 30 |
|
| 31 | 31 |
#get libnetwork packages |
| 32 |
-clone git github.com/docker/libnetwork v0.7.0-dev.8 |
|
| 32 |
+clone git github.com/docker/libnetwork v0.7.0-dev.9 |
|
| 33 | 33 |
clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec |
| 34 | 34 |
clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b |
| 35 | 35 |
clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4 |
| ... | ... |
@@ -6673,6 +6673,134 @@ func (s *DockerSuite) TestBuildWorkdirWindowsPath(c *check.C) {
|
| 6673 | 6673 |
} |
| 6674 | 6674 |
} |
| 6675 | 6675 |
|
| 6676 |
+func (s *DockerSuite) TestBuildLabel(c *check.C) {
|
|
| 6677 |
+ name := "testbuildlabel" |
|
| 6678 |
+ testLabel := "foo" |
|
| 6679 |
+ |
|
| 6680 |
+ _, err := buildImage(name, ` |
|
| 6681 |
+ FROM `+minimalBaseImage()+` |
|
| 6682 |
+ LABEL default foo |
|
| 6683 |
+`, false, []string{"--label", testLabel}...)
|
|
| 6684 |
+ |
|
| 6685 |
+ if err != nil {
|
|
| 6686 |
+ c.Fatal("error building image with labels", err)
|
|
| 6687 |
+ } |
|
| 6688 |
+ |
|
| 6689 |
+ res := inspectFieldJSON(c, name, "Config.Labels") |
|
| 6690 |
+ |
|
| 6691 |
+ var labels map[string]string |
|
| 6692 |
+ |
|
| 6693 |
+ if err := json.Unmarshal([]byte(res), &labels); err != nil {
|
|
| 6694 |
+ c.Fatal(err) |
|
| 6695 |
+ } |
|
| 6696 |
+ |
|
| 6697 |
+ if _, ok := labels[testLabel]; !ok {
|
|
| 6698 |
+ c.Fatal("label not found in image")
|
|
| 6699 |
+ } |
|
| 6700 |
+} |
|
| 6701 |
+ |
|
| 6702 |
+func (s *DockerSuite) TestBuildLabelCacheCommit(c *check.C) {
|
|
| 6703 |
+ name := "testbuildlabelcachecommit" |
|
| 6704 |
+ testLabel := "foo" |
|
| 6705 |
+ |
|
| 6706 |
+ if _, err := buildImage(name, ` |
|
| 6707 |
+ FROM `+minimalBaseImage()+` |
|
| 6708 |
+ LABEL default foo |
|
| 6709 |
+ `, false); err != nil {
|
|
| 6710 |
+ c.Fatal(err) |
|
| 6711 |
+ } |
|
| 6712 |
+ |
|
| 6713 |
+ _, err := buildImage(name, ` |
|
| 6714 |
+ FROM `+minimalBaseImage()+` |
|
| 6715 |
+ LABEL default foo |
|
| 6716 |
+`, true, []string{"--label", testLabel}...)
|
|
| 6717 |
+ |
|
| 6718 |
+ if err != nil {
|
|
| 6719 |
+ c.Fatal("error building image with labels", err)
|
|
| 6720 |
+ } |
|
| 6721 |
+ |
|
| 6722 |
+ res := inspectFieldJSON(c, name, "Config.Labels") |
|
| 6723 |
+ |
|
| 6724 |
+ var labels map[string]string |
|
| 6725 |
+ |
|
| 6726 |
+ if err := json.Unmarshal([]byte(res), &labels); err != nil {
|
|
| 6727 |
+ c.Fatal(err) |
|
| 6728 |
+ } |
|
| 6729 |
+ |
|
| 6730 |
+ if _, ok := labels[testLabel]; !ok {
|
|
| 6731 |
+ c.Fatal("label not found in image")
|
|
| 6732 |
+ } |
|
| 6733 |
+} |
|
| 6734 |
+ |
|
| 6735 |
+func (s *DockerSuite) TestBuildLabelMultiple(c *check.C) {
|
|
| 6736 |
+ name := "testbuildlabelmultiple" |
|
| 6737 |
+ testLabels := map[string]string{
|
|
| 6738 |
+ "foo": "bar", |
|
| 6739 |
+ "123": "456", |
|
| 6740 |
+ } |
|
| 6741 |
+ |
|
| 6742 |
+ labelArgs := []string{}
|
|
| 6743 |
+ |
|
| 6744 |
+ for k, v := range testLabels {
|
|
| 6745 |
+ labelArgs = append(labelArgs, "--label", k+"="+v) |
|
| 6746 |
+ } |
|
| 6747 |
+ |
|
| 6748 |
+ _, err := buildImage(name, ` |
|
| 6749 |
+ FROM `+minimalBaseImage()+` |
|
| 6750 |
+ LABEL default foo |
|
| 6751 |
+`, false, labelArgs...) |
|
| 6752 |
+ |
|
| 6753 |
+ if err != nil {
|
|
| 6754 |
+ c.Fatal("error building image with labels", err)
|
|
| 6755 |
+ } |
|
| 6756 |
+ |
|
| 6757 |
+ res := inspectFieldJSON(c, name, "Config.Labels") |
|
| 6758 |
+ |
|
| 6759 |
+ var labels map[string]string |
|
| 6760 |
+ |
|
| 6761 |
+ if err := json.Unmarshal([]byte(res), &labels); err != nil {
|
|
| 6762 |
+ c.Fatal(err) |
|
| 6763 |
+ } |
|
| 6764 |
+ |
|
| 6765 |
+ for k, v := range testLabels {
|
|
| 6766 |
+ if x, ok := labels[k]; !ok || x != v {
|
|
| 6767 |
+ c.Fatalf("label %s=%s not found in image", k, v)
|
|
| 6768 |
+ } |
|
| 6769 |
+ } |
|
| 6770 |
+} |
|
| 6771 |
+ |
|
| 6772 |
+func (s *DockerSuite) TestBuildLabelOverwrite(c *check.C) {
|
|
| 6773 |
+ name := "testbuildlabeloverwrite" |
|
| 6774 |
+ testLabel := "foo" |
|
| 6775 |
+ testValue := "bar" |
|
| 6776 |
+ |
|
| 6777 |
+ _, err := buildImage(name, ` |
|
| 6778 |
+ FROM `+minimalBaseImage()+` |
|
| 6779 |
+ LABEL `+testLabel+`+ foo |
|
| 6780 |
+`, false, []string{"--label", testLabel + "=" + testValue}...)
|
|
| 6781 |
+ |
|
| 6782 |
+ if err != nil {
|
|
| 6783 |
+ c.Fatal("error building image with labels", err)
|
|
| 6784 |
+ } |
|
| 6785 |
+ |
|
| 6786 |
+ res := inspectFieldJSON(c, name, "Config.Labels") |
|
| 6787 |
+ |
|
| 6788 |
+ var labels map[string]string |
|
| 6789 |
+ |
|
| 6790 |
+ if err := json.Unmarshal([]byte(res), &labels); err != nil {
|
|
| 6791 |
+ c.Fatal(err) |
|
| 6792 |
+ } |
|
| 6793 |
+ |
|
| 6794 |
+ v, ok := labels[testLabel] |
|
| 6795 |
+ if !ok {
|
|
| 6796 |
+ c.Fatal("label not found in image")
|
|
| 6797 |
+ } |
|
| 6798 |
+ |
|
| 6799 |
+ if v != testValue {
|
|
| 6800 |
+ c.Fatal("label not overwritten")
|
|
| 6801 |
+ } |
|
| 6802 |
+} |
|
| 6803 |
+ |
|
| 6676 | 6804 |
func (s *DockerRegistryAuthHtpasswdSuite) TestBuildFromAuthenticatedRegistry(c *check.C) {
|
| 6677 | 6805 |
dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL) |
| 6678 | 6806 |
|
| ... | ... |
@@ -341,6 +341,22 @@ func (s *DockerNetworkSuite) TestDockerNetworkCreateDelete(c *check.C) {
|
| 341 | 341 |
assertNwNotAvailable(c, "test") |
| 342 | 342 |
} |
| 343 | 343 |
|
| 344 |
+func (s *DockerNetworkSuite) TestDockerNetworkCreateLabel(c *check.C) {
|
|
| 345 |
+ testNet := "testnetcreatelabel" |
|
| 346 |
+ testLabel := "foo" |
|
| 347 |
+ testValue := "bar" |
|
| 348 |
+ |
|
| 349 |
+ dockerCmd(c, "network", "create", "--label", testLabel+"="+testValue, testNet) |
|
| 350 |
+ assertNwIsAvailable(c, testNet) |
|
| 351 |
+ |
|
| 352 |
+ out, _, err := dockerCmdWithError("network", "inspect", "--format='{{ .Labels."+testLabel+" }}'", testNet)
|
|
| 353 |
+ c.Assert(err, check.IsNil) |
|
| 354 |
+ c.Assert(strings.TrimSpace(out), check.Equals, testValue) |
|
| 355 |
+ |
|
| 356 |
+ dockerCmd(c, "network", "rm", testNet) |
|
| 357 |
+ assertNwNotAvailable(c, testNet) |
|
| 358 |
+} |
|
| 359 |
+ |
|
| 344 | 360 |
func (s *DockerSuite) TestDockerNetworkDeleteNotExists(c *check.C) {
|
| 345 | 361 |
out, _, err := dockerCmdWithError("network", "rm", "test")
|
| 346 | 362 |
c.Assert(err, checker.NotNil, check.Commentf("%v", out))
|
| ... | ... |
@@ -241,3 +241,43 @@ func (s *DockerSuite) TestVolumeCliCreateWithOpts(c *check.C) {
|
| 241 | 241 |
} |
| 242 | 242 |
c.Assert(found, checker.Equals, true) |
| 243 | 243 |
} |
| 244 |
+ |
|
| 245 |
+func (s *DockerSuite) TestVolumeCliCreateLabel(c *check.C) {
|
|
| 246 |
+ testVol := "testvolcreatelabel" |
|
| 247 |
+ testLabel := "foo" |
|
| 248 |
+ testValue := "bar" |
|
| 249 |
+ |
|
| 250 |
+ out, _, err := dockerCmdWithError("volume", "create", "--label", testLabel+"="+testValue, "--name", testVol)
|
|
| 251 |
+ c.Assert(err, check.IsNil) |
|
| 252 |
+ |
|
| 253 |
+ out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Labels."+testLabel+" }}'", testVol)
|
|
| 254 |
+ c.Assert(strings.TrimSpace(out), check.Equals, testValue) |
|
| 255 |
+} |
|
| 256 |
+ |
|
| 257 |
+func (s *DockerSuite) TestVolumeCliCreateLabelMultiple(c *check.C) {
|
|
| 258 |
+ testVol := "testvolcreatelabel" |
|
| 259 |
+ |
|
| 260 |
+ testLabels := map[string]string{
|
|
| 261 |
+ "foo": "bar", |
|
| 262 |
+ "baz": "foo", |
|
| 263 |
+ } |
|
| 264 |
+ |
|
| 265 |
+ args := []string{
|
|
| 266 |
+ "volume", |
|
| 267 |
+ "create", |
|
| 268 |
+ "--name", |
|
| 269 |
+ testVol, |
|
| 270 |
+ } |
|
| 271 |
+ |
|
| 272 |
+ for k, v := range testLabels {
|
|
| 273 |
+ args = append(args, "--label", k+"="+v) |
|
| 274 |
+ } |
|
| 275 |
+ |
|
| 276 |
+ out, _, err := dockerCmdWithError(args...) |
|
| 277 |
+ c.Assert(err, check.IsNil) |
|
| 278 |
+ |
|
| 279 |
+ for k, v := range testLabels {
|
|
| 280 |
+ out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Labels."+k+" }}'", testVol)
|
|
| 281 |
+ c.Assert(strings.TrimSpace(out), check.Equals, v) |
|
| 282 |
+ } |
|
| 283 |
+} |
| ... | ... |
@@ -101,6 +101,11 @@ func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, erro |
| 101 | 101 |
} |
| 102 | 102 |
query.Set("buildargs", string(buildArgsJSON))
|
| 103 | 103 |
|
| 104 |
+ labelsJSON, err := json.Marshal(options.Labels) |
|
| 105 |
+ if err != nil {
|
|
| 106 |
+ return query, err |
|
| 107 |
+ } |
|
| 108 |
+ query.Set("labels", string(labelsJSON))
|
|
| 104 | 109 |
return query, nil |
| 105 | 110 |
} |
| 106 | 111 |
|
| ... | ... |
@@ -103,6 +103,13 @@ type GraphDriverData struct {
|
| 103 | 103 |
Data map[string]string |
| 104 | 104 |
} |
| 105 | 105 |
|
| 106 |
+// RootFS returns Image's RootFS description including the layer IDs. |
|
| 107 |
+type RootFS struct {
|
|
| 108 |
+ Type string |
|
| 109 |
+ Layers []string `json:",omitempty"` |
|
| 110 |
+ BaseLayer string `json:",omitempty"` |
|
| 111 |
+} |
|
| 112 |
+ |
|
| 106 | 113 |
// ImageInspect contains response of Remote API: |
| 107 | 114 |
// GET "/images/{name:.*}/json"
|
| 108 | 115 |
type ImageInspect struct {
|
| ... | ... |
@@ -122,6 +129,7 @@ type ImageInspect struct {
|
| 122 | 122 |
Size int64 |
| 123 | 123 |
VirtualSize int64 |
| 124 | 124 |
GraphDriver GraphDriverData |
| 125 |
+ RootFS RootFS |
|
| 125 | 126 |
} |
| 126 | 127 |
|
| 127 | 128 |
// Port stores open ports info of container |
| ... | ... |
@@ -372,6 +380,7 @@ type Volume struct {
|
| 372 | 372 |
Driver string // Driver is the Driver name used to create the volume |
| 373 | 373 |
Mountpoint string // Mountpoint is the location on disk of the volume |
| 374 | 374 |
Status map[string]interface{} `json:",omitempty"` // Status provides low-level status information about the volume
|
| 375 |
+ Labels map[string]string // Labels is metadata specific to the volume |
|
| 375 | 376 |
} |
| 376 | 377 |
|
| 377 | 378 |
// VolumesListResponse contains the response for the remote API: |
| ... | ... |
@@ -387,6 +396,7 @@ type VolumeCreateRequest struct {
|
| 387 | 387 |
Name string // Name is the requested name of the volume |
| 388 | 388 |
Driver string // Driver is the name of the driver that should be used to create the volume |
| 389 | 389 |
DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume. |
| 390 |
+ Labels map[string]string // Labels holds metadata specific to the volume being created. |
|
| 390 | 391 |
} |
| 391 | 392 |
|
| 392 | 393 |
// NetworkResource is the body of the "get network" http response message |
| ... | ... |
@@ -400,6 +410,7 @@ type NetworkResource struct {
|
| 400 | 400 |
Internal bool |
| 401 | 401 |
Containers map[string]EndpointResource |
| 402 | 402 |
Options map[string]string |
| 403 |
+ Labels map[string]string |
|
| 403 | 404 |
} |
| 404 | 405 |
|
| 405 | 406 |
// EndpointResource contains network resources allocated and used for a container in a network |
| ... | ... |
@@ -420,6 +431,7 @@ type NetworkCreate struct {
|
| 420 | 420 |
IPAM network.IPAM |
| 421 | 421 |
Internal bool |
| 422 | 422 |
Options map[string]string |
| 423 |
+ Labels map[string]string |
|
| 423 | 424 |
} |
| 424 | 425 |
|
| 425 | 426 |
// NetworkCreateResponse is the response message sent by the server for network create call |
| ... | ... |
@@ -1,10 +1,6 @@ |
| 1 |
-FROM golang:1.4-cross |
|
| 1 |
+FROM golang:1.5.3 |
|
| 2 | 2 |
RUN apt-get update && apt-get -y install iptables |
| 3 | 3 |
|
| 4 |
-RUN cd /go/src && mkdir -p golang.org/x && \ |
|
| 5 |
- cd golang.org/x && git clone https://github.com/golang/tools && \ |
|
| 6 |
- cd tools && git checkout release-branch.go1.5 |
|
| 7 |
- |
|
| 8 | 4 |
RUN go get github.com/tools/godep \ |
| 9 | 5 |
github.com/golang/lint/golint \ |
| 10 | 6 |
golang.org/x/tools/cmd/vet \ |
| ... | ... |
@@ -63,6 +63,7 @@ type NetworkInfo interface {
|
| 63 | 63 |
Scope() string |
| 64 | 64 |
IPv6Enabled() bool |
| 65 | 65 |
Internal() bool |
| 66 |
+ Labels() map[string]string |
|
| 66 | 67 |
} |
| 67 | 68 |
|
| 68 | 69 |
// EndpointWalker is a client provided function which will be used to walk the Endpoints. |
| ... | ... |
@@ -150,6 +151,7 @@ type network struct {
|
| 150 | 150 |
networkType string |
| 151 | 151 |
id string |
| 152 | 152 |
scope string |
| 153 |
+ labels map[string]string |
|
| 153 | 154 |
ipamType string |
| 154 | 155 |
ipamOptions map[string]string |
| 155 | 156 |
addrSpace string |
| ... | ... |
@@ -309,6 +311,14 @@ func (n *network) CopyTo(o datastore.KVObject) error {
|
| 309 | 309 |
dstN.internal = n.internal |
| 310 | 310 |
dstN.inDelete = n.inDelete |
| 311 | 311 |
|
| 312 |
+ // copy labels |
|
| 313 |
+ if dstN.labels == nil {
|
|
| 314 |
+ dstN.labels = make(map[string]string, len(n.labels)) |
|
| 315 |
+ } |
|
| 316 |
+ for k, v := range n.labels {
|
|
| 317 |
+ dstN.labels[k] = v |
|
| 318 |
+ } |
|
| 319 |
+ |
|
| 312 | 320 |
for _, v4conf := range n.ipamV4Config {
|
| 313 | 321 |
dstV4Conf := &IpamConf{}
|
| 314 | 322 |
v4conf.CopyTo(dstV4Conf) |
| ... | ... |
@@ -359,6 +369,7 @@ func (n *network) MarshalJSON() ([]byte, error) {
|
| 359 | 359 |
netMap["id"] = n.id |
| 360 | 360 |
netMap["networkType"] = n.networkType |
| 361 | 361 |
netMap["scope"] = n.scope |
| 362 |
+ netMap["labels"] = n.labels |
|
| 362 | 363 |
netMap["ipamType"] = n.ipamType |
| 363 | 364 |
netMap["addrSpace"] = n.addrSpace |
| 364 | 365 |
netMap["enableIPv6"] = n.enableIPv6 |
| ... | ... |
@@ -411,6 +422,15 @@ func (n *network) UnmarshalJSON(b []byte) (err error) {
|
| 411 | 411 |
n.networkType = netMap["networkType"].(string) |
| 412 | 412 |
n.enableIPv6 = netMap["enableIPv6"].(bool) |
| 413 | 413 |
|
| 414 |
+ // if we weren't unmarshaling to netMap we could simply set n.labels |
|
| 415 |
+ // unfortunately, we can't because map[string]interface{} != map[string]string
|
|
| 416 |
+ if labels, ok := netMap["labels"].(map[string]interface{}); ok {
|
|
| 417 |
+ n.labels = make(map[string]string, len(labels)) |
|
| 418 |
+ for label, value := range labels {
|
|
| 419 |
+ n.labels[label] = value.(string) |
|
| 420 |
+ } |
|
| 421 |
+ } |
|
| 422 |
+ |
|
| 414 | 423 |
if v, ok := netMap["generic"]; ok {
|
| 415 | 424 |
n.generic = v.(map[string]interface{})
|
| 416 | 425 |
// Restore opts in their map[string]string form |
| ... | ... |
@@ -539,7 +559,7 @@ func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ip |
| 539 | 539 |
} |
| 540 | 540 |
} |
| 541 | 541 |
|
| 542 |
-// NetworkOptionDriverOpts function returns an option setter for any parameter described by a map |
|
| 542 |
+// NetworkOptionDriverOpts function returns an option setter for any driver parameter described by a map |
|
| 543 | 543 |
func NetworkOptionDriverOpts(opts map[string]string) NetworkOption {
|
| 544 | 544 |
return func(n *network) {
|
| 545 | 545 |
if n.generic == nil {
|
| ... | ... |
@@ -553,6 +573,13 @@ func NetworkOptionDriverOpts(opts map[string]string) NetworkOption {
|
| 553 | 553 |
} |
| 554 | 554 |
} |
| 555 | 555 |
|
| 556 |
+// NetworkOptionLabels function returns an option setter for labels specific to a network |
|
| 557 |
+func NetworkOptionLabels(labels map[string]string) NetworkOption {
|
|
| 558 |
+ return func(n *network) {
|
|
| 559 |
+ n.labels = labels |
|
| 560 |
+ } |
|
| 561 |
+} |
|
| 562 |
+ |
|
| 556 | 563 |
// NetworkOptionDeferIPv6Alloc instructs the network to defer the IPV6 address allocation until after the endpoint has been created |
| 557 | 564 |
// It is being provided to support the specific docker daemon flags where user can deterministically assign an IPv6 address |
| 558 | 565 |
// to a container as combination of fixed-cidr-v6 + mac-address |
| ... | ... |
@@ -1285,3 +1312,15 @@ func (n *network) IPv6Enabled() bool {
|
| 1285 | 1285 |
|
| 1286 | 1286 |
return n.enableIPv6 |
| 1287 | 1287 |
} |
| 1288 |
+ |
|
| 1289 |
+func (n *network) Labels() map[string]string {
|
|
| 1290 |
+ n.Lock() |
|
| 1291 |
+ defer n.Unlock() |
|
| 1292 |
+ |
|
| 1293 |
+ var lbls = make(map[string]string, len(n.labels)) |
|
| 1294 |
+ for k, v := range n.labels {
|
|
| 1295 |
+ lbls[k] = v |
|
| 1296 |
+ } |
|
| 1297 |
+ |
|
| 1298 |
+ return lbls |
|
| 1299 |
+} |
| ... | ... |
@@ -313,8 +313,8 @@ func configureInterface(iface netlink.Link, i *nwIface) error {
|
| 313 | 313 |
}{
|
| 314 | 314 |
{setInterfaceName, fmt.Sprintf("error renaming interface %q to %q", ifaceName, i.DstName())},
|
| 315 | 315 |
{setInterfaceMAC, fmt.Sprintf("error setting interface %q MAC to %q", ifaceName, i.MacAddress())},
|
| 316 |
- {setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %q", ifaceName, i.Address())},
|
|
| 317 |
- {setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %q", ifaceName, i.AddressIPv6())},
|
|
| 316 |
+ {setInterfaceIP, fmt.Sprintf("error setting interface %q IP to %v", ifaceName, i.Address())},
|
|
| 317 |
+ {setInterfaceIPv6, fmt.Sprintf("error setting interface %q IPv6 to %v", ifaceName, i.AddressIPv6())},
|
|
| 318 | 318 |
{setInterfaceMaster, fmt.Sprintf("error setting interface %q master to %q", ifaceName, i.DstMaster())},
|
| 319 | 319 |
} |
| 320 | 320 |
|
| ... | ... |
@@ -22,7 +22,8 @@ func (a *volumeDriverAdapter) Create(name string, opts map[string]string) (volum |
| 22 | 22 |
return &volumeAdapter{
|
| 23 | 23 |
proxy: a.proxy, |
| 24 | 24 |
name: name, |
| 25 |
- driverName: a.name}, nil |
|
| 25 |
+ driverName: a.name, |
|
| 26 |
+ }, nil |
|
| 26 | 27 |
} |
| 27 | 28 |
|
| 28 | 29 |
func (a *volumeDriverAdapter) Remove(v volume.Volume) error {
|
| ... | ... |
@@ -21,7 +21,7 @@ const extName = "VolumeDriver" |
| 21 | 21 |
// NewVolumeDriver returns a driver has the given name mapped on the given client. |
| 22 | 22 |
func NewVolumeDriver(name string, c client) volume.Driver {
|
| 23 | 23 |
proxy := &volumeDriverProxy{c}
|
| 24 |
- return &volumeDriverAdapter{name, proxy}
|
|
| 24 |
+ return &volumeDriverAdapter{name: name, proxy: proxy}
|
|
| 25 | 25 |
} |
| 26 | 26 |
|
| 27 | 27 |
type opts map[string]string |
| ... | ... |
@@ -77,6 +77,10 @@ func New(scope string, rootUID, rootGID int) (*Root, error) {
|
| 77 | 77 |
} |
| 78 | 78 |
|
| 79 | 79 |
for _, d := range dirs {
|
| 80 |
+ if !d.IsDir() {
|
|
| 81 |
+ continue |
|
| 82 |
+ } |
|
| 83 |
+ |
|
| 80 | 84 |
name := filepath.Base(d.Name()) |
| 81 | 85 |
v := &localVolume{
|
| 82 | 86 |
driverName: r.Name(), |
| ... | ... |
@@ -198,7 +202,7 @@ func (r *Root) Remove(v volume.Volume) error {
|
| 198 | 198 |
|
| 199 | 199 |
lv, ok := v.(*localVolume) |
| 200 | 200 |
if !ok {
|
| 201 |
- return fmt.Errorf("unknown volume type")
|
|
| 201 |
+ return fmt.Errorf("unknown volume type %T", v)
|
|
| 202 | 202 |
} |
| 203 | 203 |
|
| 204 | 204 |
realPath, err := filepath.EvalSymlinks(lv.path) |
| ... | ... |
@@ -1,22 +1,77 @@ |
| 1 | 1 |
package store |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "bytes" |
|
| 5 |
+ "encoding/json" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path/filepath" |
|
| 4 | 8 |
"sync" |
| 9 |
+ "time" |
|
| 5 | 10 |
|
| 6 | 11 |
"github.com/Sirupsen/logrus" |
| 12 |
+ "github.com/boltdb/bolt" |
|
| 7 | 13 |
"github.com/docker/docker/pkg/locker" |
| 8 | 14 |
"github.com/docker/docker/volume" |
| 9 | 15 |
"github.com/docker/docker/volume/drivers" |
| 10 | 16 |
) |
| 11 | 17 |
|
| 18 |
+const ( |
|
| 19 |
+ volumeDataDir = "volumes" |
|
| 20 |
+ volumeBucketName = "volumes" |
|
| 21 |
+) |
|
| 22 |
+ |
|
| 23 |
+type volumeMetadata struct {
|
|
| 24 |
+ Name string |
|
| 25 |
+ Labels map[string]string |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+type volumeWithLabels struct {
|
|
| 29 |
+ volume.Volume |
|
| 30 |
+ labels map[string]string |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func (v volumeWithLabels) Labels() map[string]string {
|
|
| 34 |
+ return v.labels |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 12 | 37 |
// New initializes a VolumeStore to keep |
| 13 | 38 |
// reference counting of volumes in the system. |
| 14 |
-func New() *VolumeStore {
|
|
| 15 |
- return &VolumeStore{
|
|
| 16 |
- locks: &locker.Locker{},
|
|
| 17 |
- names: make(map[string]volume.Volume), |
|
| 18 |
- refs: make(map[string][]string), |
|
| 39 |
+func New(rootPath string) (*VolumeStore, error) {
|
|
| 40 |
+ vs := &VolumeStore{
|
|
| 41 |
+ locks: &locker.Locker{},
|
|
| 42 |
+ names: make(map[string]volume.Volume), |
|
| 43 |
+ refs: make(map[string][]string), |
|
| 44 |
+ labels: make(map[string]map[string]string), |
|
| 19 | 45 |
} |
| 46 |
+ |
|
| 47 |
+ if rootPath != "" {
|
|
| 48 |
+ // initialize metadata store |
|
| 49 |
+ volPath := filepath.Join(rootPath, volumeDataDir) |
|
| 50 |
+ if err := os.MkdirAll(volPath, 750); err != nil {
|
|
| 51 |
+ return nil, err |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ dbPath := filepath.Join(volPath, "metadata.db") |
|
| 55 |
+ |
|
| 56 |
+ var err error |
|
| 57 |
+ vs.db, err = bolt.Open(dbPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
|
| 58 |
+ if err != nil {
|
|
| 59 |
+ return nil, err |
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ // initialize volumes bucket |
|
| 63 |
+ if err := vs.db.Update(func(tx *bolt.Tx) error {
|
|
| 64 |
+ if _, err := tx.CreateBucketIfNotExists([]byte(volumeBucketName)); err != nil {
|
|
| 65 |
+ return err |
|
| 66 |
+ } |
|
| 67 |
+ |
|
| 68 |
+ return nil |
|
| 69 |
+ }); err != nil {
|
|
| 70 |
+ return nil, err |
|
| 71 |
+ } |
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ return vs, nil |
|
| 20 | 75 |
} |
| 21 | 76 |
|
| 22 | 77 |
func (s *VolumeStore) getNamed(name string) (volume.Volume, bool) {
|
| ... | ... |
@@ -39,6 +94,7 @@ func (s *VolumeStore) purge(name string) {
|
| 39 | 39 |
s.globalLock.Lock() |
| 40 | 40 |
delete(s.names, name) |
| 41 | 41 |
delete(s.refs, name) |
| 42 |
+ delete(s.labels, name) |
|
| 42 | 43 |
s.globalLock.Unlock() |
| 43 | 44 |
} |
| 44 | 45 |
|
| ... | ... |
@@ -51,6 +107,9 @@ type VolumeStore struct {
|
| 51 | 51 |
names map[string]volume.Volume |
| 52 | 52 |
// refs stores the volume name and the list of things referencing it |
| 53 | 53 |
refs map[string][]string |
| 54 |
+ // labels stores volume labels for each volume |
|
| 55 |
+ labels map[string]map[string]string |
|
| 56 |
+ db *bolt.DB |
|
| 54 | 57 |
} |
| 55 | 58 |
|
| 56 | 59 |
// 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) {
|
| 137 | 137 |
// CreateWithRef creates a volume with the given name and driver and stores the ref |
| 138 | 138 |
// This is just like Create() except we store the reference while holding the lock. |
| 139 | 139 |
// This ensures there's no race between creating a volume and then storing a reference. |
| 140 |
-func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts map[string]string) (volume.Volume, error) {
|
|
| 140 |
+func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts, labels map[string]string) (volume.Volume, error) {
|
|
| 141 | 141 |
name = normaliseVolumeName(name) |
| 142 | 142 |
s.locks.Lock(name) |
| 143 | 143 |
defer s.locks.Unlock(name) |
| 144 | 144 |
|
| 145 |
- v, err := s.create(name, driverName, opts) |
|
| 145 |
+ v, err := s.create(name, driverName, opts, labels) |
|
| 146 | 146 |
if err != nil {
|
| 147 | 147 |
return nil, &OpErr{Err: err, Name: name, Op: "create"}
|
| 148 | 148 |
} |
| ... | ... |
@@ -152,12 +211,12 @@ func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts map[strin |
| 152 | 152 |
} |
| 153 | 153 |
|
| 154 | 154 |
// Create creates a volume with the given name and driver. |
| 155 |
-func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
|
| 155 |
+func (s *VolumeStore) Create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
|
|
| 156 | 156 |
name = normaliseVolumeName(name) |
| 157 | 157 |
s.locks.Lock(name) |
| 158 | 158 |
defer s.locks.Unlock(name) |
| 159 | 159 |
|
| 160 |
- v, err := s.create(name, driverName, opts) |
|
| 160 |
+ v, err := s.create(name, driverName, opts, labels) |
|
| 161 | 161 |
if err != nil {
|
| 162 | 162 |
return nil, &OpErr{Err: err, Name: name, Op: "create"}
|
| 163 | 163 |
} |
| ... | ... |
@@ -169,7 +228,7 @@ func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (v |
| 169 | 169 |
// If a volume with the name is already known, it will ask the stored driver for the volume. |
| 170 | 170 |
// If the passed in driver name does not match the driver name which is stored for the given volume name, an error is returned. |
| 171 | 171 |
// It is expected that callers of this function hold any necessary locks. |
| 172 |
-func (s *VolumeStore) create(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
|
| 172 |
+func (s *VolumeStore) create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
|
|
| 173 | 173 |
// Validate the name in a platform-specific manner |
| 174 | 174 |
valid, err := volume.IsVolumeNameValid(name) |
| 175 | 175 |
if err != nil {
|
| ... | ... |
@@ -205,7 +264,33 @@ func (s *VolumeStore) create(name, driverName string, opts map[string]string) (v |
| 205 | 205 |
if v, _ := vd.Get(name); v != nil {
|
| 206 | 206 |
return v, nil |
| 207 | 207 |
} |
| 208 |
- return vd.Create(name, opts) |
|
| 208 |
+ v, err := vd.Create(name, opts) |
|
| 209 |
+ if err != nil {
|
|
| 210 |
+ return nil, err |
|
| 211 |
+ } |
|
| 212 |
+ s.labels[name] = labels |
|
| 213 |
+ |
|
| 214 |
+ if s.db != nil {
|
|
| 215 |
+ metadata := &volumeMetadata{
|
|
| 216 |
+ Name: name, |
|
| 217 |
+ Labels: labels, |
|
| 218 |
+ } |
|
| 219 |
+ |
|
| 220 |
+ volData, err := json.Marshal(metadata) |
|
| 221 |
+ if err != nil {
|
|
| 222 |
+ return nil, err |
|
| 223 |
+ } |
|
| 224 |
+ |
|
| 225 |
+ if err := s.db.Update(func(tx *bolt.Tx) error {
|
|
| 226 |
+ b := tx.Bucket([]byte(volumeBucketName)) |
|
| 227 |
+ err := b.Put([]byte(name), volData) |
|
| 228 |
+ return err |
|
| 229 |
+ }); err != nil {
|
|
| 230 |
+ return nil, err |
|
| 231 |
+ } |
|
| 232 |
+ } |
|
| 233 |
+ |
|
| 234 |
+ return volumeWithLabels{v, labels}, nil
|
|
| 209 | 235 |
} |
| 210 | 236 |
|
| 211 | 237 |
// 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 |
| 227 | 227 |
} |
| 228 | 228 |
|
| 229 | 229 |
s.setNamed(v, ref) |
| 230 |
+ if labels, ok := s.labels[name]; ok {
|
|
| 231 |
+ return volumeWithLabels{v, labels}, nil
|
|
| 232 |
+ } |
|
| 230 | 233 |
return v, nil |
| 231 | 234 |
} |
| 232 | 235 |
|
| ... | ... |
@@ -248,13 +336,43 @@ func (s *VolumeStore) Get(name string) (volume.Volume, error) {
|
| 248 | 248 |
// if the driver is unknown it probes all drivers until it finds the first volume with that name. |
| 249 | 249 |
// it is expected that callers of this function hold any necessary locks |
| 250 | 250 |
func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
|
| 251 |
+ labels := map[string]string{}
|
|
| 252 |
+ |
|
| 253 |
+ if s.db != nil {
|
|
| 254 |
+ // get meta |
|
| 255 |
+ if err := s.db.Update(func(tx *bolt.Tx) error {
|
|
| 256 |
+ b := tx.Bucket([]byte(volumeBucketName)) |
|
| 257 |
+ data := b.Get([]byte(name)) |
|
| 258 |
+ |
|
| 259 |
+ if string(data) == "" {
|
|
| 260 |
+ return nil |
|
| 261 |
+ } |
|
| 262 |
+ |
|
| 263 |
+ var meta volumeMetadata |
|
| 264 |
+ buf := bytes.NewBuffer(data) |
|
| 265 |
+ |
|
| 266 |
+ if err := json.NewDecoder(buf).Decode(&meta); err != nil {
|
|
| 267 |
+ return err |
|
| 268 |
+ } |
|
| 269 |
+ labels = meta.Labels |
|
| 270 |
+ |
|
| 271 |
+ return nil |
|
| 272 |
+ }); err != nil {
|
|
| 273 |
+ return nil, err |
|
| 274 |
+ } |
|
| 275 |
+ } |
|
| 276 |
+ |
|
| 251 | 277 |
logrus.Debugf("Getting volume reference for name: %s", name)
|
| 252 | 278 |
if v, exists := s.names[name]; exists {
|
| 253 | 279 |
vd, err := volumedrivers.GetDriver(v.DriverName()) |
| 254 | 280 |
if err != nil {
|
| 255 | 281 |
return nil, err |
| 256 | 282 |
} |
| 257 |
- return vd.Get(name) |
|
| 283 |
+ vol, err := vd.Get(name) |
|
| 284 |
+ if err != nil {
|
|
| 285 |
+ return nil, err |
|
| 286 |
+ } |
|
| 287 |
+ return volumeWithLabels{vol, labels}, nil
|
|
| 258 | 288 |
} |
| 259 | 289 |
|
| 260 | 290 |
logrus.Debugf("Probing all drivers for volume with name: %s", name)
|
| ... | ... |
@@ -268,7 +386,8 @@ func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
|
| 268 | 268 |
if err != nil {
|
| 269 | 269 |
continue |
| 270 | 270 |
} |
| 271 |
- return v, nil |
|
| 271 |
+ |
|
| 272 |
+ return volumeWithLabels{v, labels}, nil
|
|
| 272 | 273 |
} |
| 273 | 274 |
return nil, errNoSuchVolume |
| 274 | 275 |
} |
| ... | ... |
@@ -289,7 +408,8 @@ func (s *VolumeStore) Remove(v volume.Volume) error {
|
| 289 | 289 |
} |
| 290 | 290 |
|
| 291 | 291 |
logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
| 292 |
- if err := vd.Remove(v); err != nil {
|
|
| 292 |
+ vol := withoutLabels(v) |
|
| 293 |
+ if err := vd.Remove(vol); err != nil {
|
|
| 293 | 294 |
return &OpErr{Err: err, Name: name, Op: "remove"}
|
| 294 | 295 |
} |
| 295 | 296 |
|
| ... | ... |
@@ -372,3 +492,11 @@ func (s *VolumeStore) filter(vols []volume.Volume, f filterFunc) []volume.Volume |
| 372 | 372 |
} |
| 373 | 373 |
return ls |
| 374 | 374 |
} |
| 375 |
+ |
|
| 376 |
+func withoutLabels(v volume.Volume) volume.Volume {
|
|
| 377 |
+ if vol, ok := v.(volumeWithLabels); ok {
|
|
| 378 |
+ return vol.Volume |
|
| 379 |
+ } |
|
| 380 |
+ |
|
| 381 |
+ return v |
|
| 382 |
+} |
| ... | ... |
@@ -12,8 +12,11 @@ import ( |
| 12 | 12 |
func TestCreate(t *testing.T) {
|
| 13 | 13 |
volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
| 14 | 14 |
defer volumedrivers.Unregister("fake")
|
| 15 |
- s := New() |
|
| 16 |
- v, err := s.Create("fake1", "fake", nil)
|
|
| 15 |
+ s, err := New("")
|
|
| 16 |
+ if err != nil {
|
|
| 17 |
+ t.Fatal(err) |
|
| 18 |
+ } |
|
| 19 |
+ v, err := s.Create("fake1", "fake", nil, nil)
|
|
| 17 | 20 |
if err != nil {
|
| 18 | 21 |
t.Fatal(err) |
| 19 | 22 |
} |
| ... | ... |
@@ -24,11 +27,11 @@ func TestCreate(t *testing.T) {
|
| 24 | 24 |
t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l)
|
| 25 | 25 |
} |
| 26 | 26 |
|
| 27 |
- if _, err := s.Create("none", "none", nil); err == nil {
|
|
| 27 |
+ if _, err := s.Create("none", "none", nil, nil); err == nil {
|
|
| 28 | 28 |
t.Fatalf("Expected unknown driver error, got nil")
|
| 29 | 29 |
} |
| 30 | 30 |
|
| 31 |
- _, err = s.Create("fakeerror", "fake", map[string]string{"error": "create error"})
|
|
| 31 |
+ _, err = s.Create("fakeerror", "fake", map[string]string{"error": "create error"}, nil)
|
|
| 32 | 32 |
expected := &OpErr{Op: "create", Name: "fakeerror", Err: errors.New("create error")}
|
| 33 | 33 |
if err != nil && err.Error() != expected.Error() {
|
| 34 | 34 |
t.Fatalf("Expected create fakeError: create error, got %v", err)
|
| ... | ... |
@@ -40,7 +43,10 @@ func TestRemove(t *testing.T) {
|
| 40 | 40 |
volumedrivers.Register(vt.NewFakeDriver("noop"), "noop")
|
| 41 | 41 |
defer volumedrivers.Unregister("fake")
|
| 42 | 42 |
defer volumedrivers.Unregister("noop")
|
| 43 |
- s := New() |
|
| 43 |
+ s, err := New("")
|
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ t.Fatal(err) |
|
| 46 |
+ } |
|
| 44 | 47 |
|
| 45 | 48 |
// doing string compare here since this error comes directly from the driver |
| 46 | 49 |
expected := "no such volume" |
| ... | ... |
@@ -48,7 +54,7 @@ func TestRemove(t *testing.T) {
|
| 48 | 48 |
t.Fatalf("Expected error %q, got %v", expected, err)
|
| 49 | 49 |
} |
| 50 | 50 |
|
| 51 |
- v, err := s.CreateWithRef("fake1", "fake", "fake", nil)
|
|
| 51 |
+ v, err := s.CreateWithRef("fake1", "fake", "fake", nil, nil)
|
|
| 52 | 52 |
if err != nil {
|
| 53 | 53 |
t.Fatal(err) |
| 54 | 54 |
} |
| ... | ... |
@@ -71,11 +77,14 @@ func TestList(t *testing.T) {
|
| 71 | 71 |
defer volumedrivers.Unregister("fake")
|
| 72 | 72 |
defer volumedrivers.Unregister("fake2")
|
| 73 | 73 |
|
| 74 |
- s := New() |
|
| 75 |
- if _, err := s.Create("test", "fake", nil); err != nil {
|
|
| 74 |
+ s, err := New("")
|
|
| 75 |
+ if err != nil {
|
|
| 76 |
+ t.Fatal(err) |
|
| 77 |
+ } |
|
| 78 |
+ if _, err := s.Create("test", "fake", nil, nil); err != nil {
|
|
| 76 | 79 |
t.Fatal(err) |
| 77 | 80 |
} |
| 78 |
- if _, err := s.Create("test2", "fake2", nil); err != nil {
|
|
| 81 |
+ if _, err := s.Create("test2", "fake2", nil, nil); err != nil {
|
|
| 79 | 82 |
t.Fatal(err) |
| 80 | 83 |
} |
| 81 | 84 |
|
| ... | ... |
@@ -88,7 +97,10 @@ func TestList(t *testing.T) {
|
| 88 | 88 |
} |
| 89 | 89 |
|
| 90 | 90 |
// and again with a new store |
| 91 |
- s = New() |
|
| 91 |
+ s, err = New("")
|
|
| 92 |
+ if err != nil {
|
|
| 93 |
+ t.Fatal(err) |
|
| 94 |
+ } |
|
| 92 | 95 |
ls, _, err = s.List() |
| 93 | 96 |
if err != nil {
|
| 94 | 97 |
t.Fatal(err) |
| ... | ... |
@@ -103,15 +115,18 @@ func TestFilterByDriver(t *testing.T) {
|
| 103 | 103 |
volumedrivers.Register(vt.NewFakeDriver("noop"), "noop")
|
| 104 | 104 |
defer volumedrivers.Unregister("fake")
|
| 105 | 105 |
defer volumedrivers.Unregister("noop")
|
| 106 |
- s := New() |
|
| 106 |
+ s, err := New("")
|
|
| 107 |
+ if err != nil {
|
|
| 108 |
+ t.Fatal(err) |
|
| 109 |
+ } |
|
| 107 | 110 |
|
| 108 |
- if _, err := s.Create("fake1", "fake", nil); err != nil {
|
|
| 111 |
+ if _, err := s.Create("fake1", "fake", nil, nil); err != nil {
|
|
| 109 | 112 |
t.Fatal(err) |
| 110 | 113 |
} |
| 111 |
- if _, err := s.Create("fake2", "fake", nil); err != nil {
|
|
| 114 |
+ if _, err := s.Create("fake2", "fake", nil, nil); err != nil {
|
|
| 112 | 115 |
t.Fatal(err) |
| 113 | 116 |
} |
| 114 |
- if _, err := s.Create("fake3", "noop", nil); err != nil {
|
|
| 117 |
+ if _, err := s.Create("fake3", "noop", nil, nil); err != nil {
|
|
| 115 | 118 |
t.Fatal(err) |
| 116 | 119 |
} |
| 117 | 120 |
|
| ... | ... |
@@ -128,11 +143,15 @@ func TestFilterByUsed(t *testing.T) {
|
| 128 | 128 |
volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
| 129 | 129 |
volumedrivers.Register(vt.NewFakeDriver("noop"), "noop")
|
| 130 | 130 |
|
| 131 |
- s := New() |
|
| 132 |
- if _, err := s.CreateWithRef("fake1", "fake", "volReference", nil); err != nil {
|
|
| 131 |
+ s, err := New("")
|
|
| 132 |
+ if err != nil {
|
|
| 133 | 133 |
t.Fatal(err) |
| 134 | 134 |
} |
| 135 |
- if _, err := s.Create("fake2", "fake", nil); err != nil {
|
|
| 135 |
+ |
|
| 136 |
+ if _, err := s.CreateWithRef("fake1", "fake", "volReference", nil, nil); err != nil {
|
|
| 137 |
+ t.Fatal(err) |
|
| 138 |
+ } |
|
| 139 |
+ if _, err := s.Create("fake2", "fake", nil, nil); err != nil {
|
|
| 136 | 140 |
t.Fatal(err) |
| 137 | 141 |
} |
| 138 | 142 |
|
| ... | ... |
@@ -161,8 +180,12 @@ func TestFilterByUsed(t *testing.T) {
|
| 161 | 161 |
func TestDerefMultipleOfSameRef(t *testing.T) {
|
| 162 | 162 |
volumedrivers.Register(vt.NewFakeDriver("fake"), "fake")
|
| 163 | 163 |
|
| 164 |
- s := New() |
|
| 165 |
- v, err := s.CreateWithRef("fake1", "fake", "volReference", nil)
|
|
| 164 |
+ s, err := New("")
|
|
| 165 |
+ if err != nil {
|
|
| 166 |
+ t.Fatal(err) |
|
| 167 |
+ } |
|
| 168 |
+ |
|
| 169 |
+ v, err := s.CreateWithRef("fake1", "fake", "volReference", nil, nil)
|
|
| 166 | 170 |
if err != nil {
|
| 167 | 171 |
t.Fatal(err) |
| 168 | 172 |
} |