build: implement --label
Signed-off-by: Evan Hazlett <ejhazlett@gmail.com>
network: allow adding labels on create
Signed-off-by: Evan Hazlett <ejhazlett@gmail.com>
volume: allow adding labels on create
Signed-off-by: Evan Hazlett <ejhazlett@gmail.com>
add tests for build, network, volume
Signed-off-by: Evan Hazlett <ejhazlett@gmail.com>
vendor: libnetwork and engine-api bump
Signed-off-by: Evan Hazlett <ejhazlett@gmail.com>
... | ... |
@@ -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 |
} |
... | ... |
@@ -215,6 +215,10 @@ func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context |
215 | 215 |
|
216 | 216 |
var shortImgID string |
217 | 217 |
for i, n := range b.dockerfile.Children { |
218 |
+ // we only want to add labels to the last layer |
|
219 |
+ if i == len(b.dockerfile.Children)-1 { |
|
220 |
+ b.addLabels() |
|
221 |
+ } |
|
218 | 222 |
select { |
219 | 223 |
case <-b.cancelled: |
220 | 224 |
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 |
... | ... |
@@ -6596,6 +6596,134 @@ func (s *DockerSuite) TestBuildWorkdirWindowsPath(c *check.C) { |
6596 | 6596 |
} |
6597 | 6597 |
} |
6598 | 6598 |
|
6599 |
+func (s *DockerSuite) TestBuildLabel(c *check.C) { |
|
6600 |
+ name := "testbuildlabel" |
|
6601 |
+ testLabel := "foo" |
|
6602 |
+ |
|
6603 |
+ _, err := buildImage(name, ` |
|
6604 |
+ FROM `+minimalBaseImage()+` |
|
6605 |
+ LABEL default foo |
|
6606 |
+`, false, []string{"--label", testLabel}...) |
|
6607 |
+ |
|
6608 |
+ if err != nil { |
|
6609 |
+ c.Fatal("error building image with labels", err) |
|
6610 |
+ } |
|
6611 |
+ |
|
6612 |
+ res := inspectFieldJSON(c, name, "Config.Labels") |
|
6613 |
+ |
|
6614 |
+ var labels map[string]string |
|
6615 |
+ |
|
6616 |
+ if err := json.Unmarshal([]byte(res), &labels); err != nil { |
|
6617 |
+ c.Fatal(err) |
|
6618 |
+ } |
|
6619 |
+ |
|
6620 |
+ if _, ok := labels[testLabel]; !ok { |
|
6621 |
+ c.Fatal("label not found in image") |
|
6622 |
+ } |
|
6623 |
+} |
|
6624 |
+ |
|
6625 |
+func (s *DockerSuite) TestBuildLabelCacheCommit(c *check.C) { |
|
6626 |
+ name := "testbuildlabelcachecommit" |
|
6627 |
+ testLabel := "foo" |
|
6628 |
+ |
|
6629 |
+ if _, err := buildImage(name, ` |
|
6630 |
+ FROM `+minimalBaseImage()+` |
|
6631 |
+ LABEL default foo |
|
6632 |
+ `, false); err != nil { |
|
6633 |
+ c.Fatal(err) |
|
6634 |
+ } |
|
6635 |
+ |
|
6636 |
+ _, err := buildImage(name, ` |
|
6637 |
+ FROM `+minimalBaseImage()+` |
|
6638 |
+ LABEL default foo |
|
6639 |
+`, true, []string{"--label", testLabel}...) |
|
6640 |
+ |
|
6641 |
+ if err != nil { |
|
6642 |
+ c.Fatal("error building image with labels", err) |
|
6643 |
+ } |
|
6644 |
+ |
|
6645 |
+ res := inspectFieldJSON(c, name, "Config.Labels") |
|
6646 |
+ |
|
6647 |
+ var labels map[string]string |
|
6648 |
+ |
|
6649 |
+ if err := json.Unmarshal([]byte(res), &labels); err != nil { |
|
6650 |
+ c.Fatal(err) |
|
6651 |
+ } |
|
6652 |
+ |
|
6653 |
+ if _, ok := labels[testLabel]; !ok { |
|
6654 |
+ c.Fatal("label not found in image") |
|
6655 |
+ } |
|
6656 |
+} |
|
6657 |
+ |
|
6658 |
+func (s *DockerSuite) TestBuildLabelMultiple(c *check.C) { |
|
6659 |
+ name := "testbuildlabelmultiple" |
|
6660 |
+ testLabels := map[string]string{ |
|
6661 |
+ "foo": "bar", |
|
6662 |
+ "123": "456", |
|
6663 |
+ } |
|
6664 |
+ |
|
6665 |
+ labelArgs := []string{} |
|
6666 |
+ |
|
6667 |
+ for k, v := range testLabels { |
|
6668 |
+ labelArgs = append(labelArgs, "--label", k+"="+v) |
|
6669 |
+ } |
|
6670 |
+ |
|
6671 |
+ _, err := buildImage(name, ` |
|
6672 |
+ FROM `+minimalBaseImage()+` |
|
6673 |
+ LABEL default foo |
|
6674 |
+`, false, labelArgs...) |
|
6675 |
+ |
|
6676 |
+ if err != nil { |
|
6677 |
+ c.Fatal("error building image with labels", err) |
|
6678 |
+ } |
|
6679 |
+ |
|
6680 |
+ res := inspectFieldJSON(c, name, "Config.Labels") |
|
6681 |
+ |
|
6682 |
+ var labels map[string]string |
|
6683 |
+ |
|
6684 |
+ if err := json.Unmarshal([]byte(res), &labels); err != nil { |
|
6685 |
+ c.Fatal(err) |
|
6686 |
+ } |
|
6687 |
+ |
|
6688 |
+ for k, v := range testLabels { |
|
6689 |
+ if x, ok := labels[k]; !ok || x != v { |
|
6690 |
+ c.Fatalf("label %s=%s not found in image", k, v) |
|
6691 |
+ } |
|
6692 |
+ } |
|
6693 |
+} |
|
6694 |
+ |
|
6695 |
+func (s *DockerSuite) TestBuildLabelOverwrite(c *check.C) { |
|
6696 |
+ name := "testbuildlabeloverwrite" |
|
6697 |
+ testLabel := "foo" |
|
6698 |
+ testValue := "bar" |
|
6699 |
+ |
|
6700 |
+ _, err := buildImage(name, ` |
|
6701 |
+ FROM `+minimalBaseImage()+` |
|
6702 |
+ LABEL `+testLabel+`+ foo |
|
6703 |
+`, false, []string{"--label", testLabel + "=" + testValue}...) |
|
6704 |
+ |
|
6705 |
+ if err != nil { |
|
6706 |
+ c.Fatal("error building image with labels", err) |
|
6707 |
+ } |
|
6708 |
+ |
|
6709 |
+ res := inspectFieldJSON(c, name, "Config.Labels") |
|
6710 |
+ |
|
6711 |
+ var labels map[string]string |
|
6712 |
+ |
|
6713 |
+ if err := json.Unmarshal([]byte(res), &labels); err != nil { |
|
6714 |
+ c.Fatal(err) |
|
6715 |
+ } |
|
6716 |
+ |
|
6717 |
+ v, ok := labels[testLabel] |
|
6718 |
+ if !ok { |
|
6719 |
+ c.Fatal("label not found in image") |
|
6720 |
+ } |
|
6721 |
+ |
|
6722 |
+ if v != testValue { |
|
6723 |
+ c.Fatal("label not overwritten") |
|
6724 |
+ } |
|
6725 |
+} |
|
6726 |
+ |
|
6599 | 6727 |
func (s *DockerRegistryAuthHtpasswdSuite) TestBuildFromAuthenticatedRegistry(c *check.C) { |
6600 | 6728 |
dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL) |
6601 | 6729 |
|
... | ... |
@@ -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 |
} |