Browse code

Remove duplicate keys in labels of `docker info`

This fix tries to address the issue raised in 24392 where
labels with duplicate keys exist in `docker info`, which
contradicts with the specifications in the docs.

The reason for duplicate keys is that labels are stored as
slice of strings in the format of `A=B` (and the input/output).

This fix tries to address this issue by checking conflict
labels when daemon started, and remove duplicate labels (K-V).

The existing `/info` API has not been changed.

An additional integration test has been added to cover the
changes in this fix.

This fix fixes 24392.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

Yong Tang authored on 2016/07/12 21:08:05
Showing 5 changed files
... ...
@@ -223,6 +223,21 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error {
223 223
 		for _, attribute := range info.Labels {
224 224
 			fmt.Fprintf(dockerCli.Out(), " %s\n", attribute)
225 225
 		}
226
+		// TODO: Engine labels with duplicate keys has been deprecated in 1.13 and will be error out
227
+		// after 3 release cycles (1.16). For now, a WARNING will be generated. The following will
228
+		// be removed eventually.
229
+		labelMap := map[string]string{}
230
+		for _, label := range info.Labels {
231
+			stringSlice := strings.SplitN(label, "=", 2)
232
+			if len(stringSlice) > 1 {
233
+				// If there is a conflict we will throw out an warning
234
+				if v, ok := labelMap[stringSlice[0]]; ok && v != stringSlice[1] {
235
+					fmt.Fprintln(dockerCli.Err(), "WARNING: labels with duplicate keys and conflicting values have been deprecated")
236
+					break
237
+				}
238
+				labelMap[stringSlice[0]] = stringSlice[1]
239
+			}
240
+		}
226 241
 	}
227 242
 
228 243
 	ioutils.FprintfIfTrue(dockerCli.Out(), "Experimental: %v\n", info.ExperimentalBuild)
... ...
@@ -396,6 +396,23 @@ func loadDaemonCliConfig(opts daemonOptions) (*daemon.Config, error) {
396 396
 		return nil, err
397 397
 	}
398 398
 
399
+	// Labels of the docker engine used to allow multiple values associated with the same key.
400
+	// This is deprecated in 1.13, and, be removed after 3 release cycles.
401
+	// The following will check the conflict of labels, and report a warning for deprecation.
402
+	//
403
+	// TODO: After 3 release cycles (1.16) an error will be returned, and labels will be
404
+	// sanitized to consolidate duplicate key-value pairs (config.Labels = newLabels):
405
+	//
406
+	// newLabels, err := daemon.GetConflictFreeLabels(config.Labels)
407
+	// if err != nil {
408
+	//	return nil, err
409
+	// }
410
+	// config.Labels = newLabels
411
+	//
412
+	if _, err := daemon.GetConflictFreeLabels(config.Labels); err != nil {
413
+		logrus.Warnf("Engine labels with duplicate keys and conflicting values have been deprecated: %s", err)
414
+	}
415
+
399 416
 	// Regardless of whether the user sets it to true or false, if they
400 417
 	// specify TLSVerify at all then we need to turn on TLS
401 418
 	if config.IsValueSet(cliflags.FlagTLSVerify) {
... ...
@@ -220,6 +220,30 @@ func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (strin
220 220
 	return advertise, nil
221 221
 }
222 222
 
223
+// GetConflictFreeLabels validate Labels for conflict
224
+// In swarm the duplicates for labels are removed
225
+// so we only take same values here, no conflict values
226
+// If the key-value is the same we will only take the last label
227
+func GetConflictFreeLabels(labels []string) ([]string, error) {
228
+	labelMap := map[string]string{}
229
+	for _, label := range labels {
230
+		stringSlice := strings.SplitN(label, "=", 2)
231
+		if len(stringSlice) > 1 {
232
+			// If there is a conflict we will return an error
233
+			if v, ok := labelMap[stringSlice[0]]; ok && v != stringSlice[1] {
234
+				return nil, fmt.Errorf("conflict labels for %s=%s and %s=%s", stringSlice[0], stringSlice[1], stringSlice[0], v)
235
+			}
236
+			labelMap[stringSlice[0]] = stringSlice[1]
237
+		}
238
+	}
239
+
240
+	newLabels := []string{}
241
+	for k, v := range labelMap {
242
+		newLabels = append(newLabels, fmt.Sprintf("%s=%s", k, v))
243
+	}
244
+	return newLabels, nil
245
+}
246
+
223 247
 // ReloadConfiguration reads the configuration in the host and reloads the daemon and server.
224 248
 func ReloadConfiguration(configFile string, flags *pflag.FlagSet, reload func(*Config)) error {
225 249
 	logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
... ...
@@ -232,6 +256,23 @@ func ReloadConfiguration(configFile string, flags *pflag.FlagSet, reload func(*C
232 232
 		return fmt.Errorf("file configuration validation failed (%v)", err)
233 233
 	}
234 234
 
235
+	// Labels of the docker engine used to allow multiple values associated with the same key.
236
+	// This is deprecated in 1.13, and, be removed after 3 release cycles.
237
+	// The following will check the conflict of labels, and report a warning for deprecation.
238
+	//
239
+	// TODO: After 3 release cycles (1.16) an error will be returned, and labels will be
240
+	// sanitized to consolidate duplicate key-value pairs (config.Labels = newLabels):
241
+	//
242
+	// newLabels, err := GetConflictFreeLabels(newConfig.Labels)
243
+	// if err != nil {
244
+	//      return err
245
+	// }
246
+	// newConfig.Labels = newLabels
247
+	//
248
+	if _, err := GetConflictFreeLabels(newConfig.Labels); err != nil {
249
+		logrus.Warnf("Engine labels with duplicate keys and conflicting values have been deprecated: %s", err)
250
+	}
251
+
235 252
 	reload(newConfig)
236 253
 	return nil
237 254
 }
... ...
@@ -26,6 +26,14 @@ see [Feature Deprecation Policy](index.md#feature-deprecation-policy).
26 26
 
27 27
 The daemon is moved to a separate binary (`dockerd`), and should be used instead.
28 28
 
29
+### Duplicate keys with conflicting values in engine labels
30
+**Deprecated In Release: [v1.13](https://github.com/docker/docker/releases/)**
31
+
32
+**Target For Removal In Release: v1.16**
33
+
34
+Duplicate keys with conflicting values have been deprecated. A warning is displayed
35
+in the output, and an error will be returned in the future.
36
+
29 37
 ### Three argument form in `docker import`
30 38
 **Deprecated In Release: [v0.6.7](https://github.com/docker/docker/releases/tag/v0.6.7)**
31 39
 
... ...
@@ -219,3 +219,15 @@ func (s *DockerDaemonSuite) TestRegistryMirrors(c *check.C) {
219 219
 	c.Assert(out, checker.Contains, fmt.Sprintf(" %s", registryMirror1))
220 220
 	c.Assert(out, checker.Contains, fmt.Sprintf(" %s", registryMirror2))
221 221
 }
222
+
223
+// Test case for #24392
224
+func (s *DockerDaemonSuite) TestInfoLabels(c *check.C) {
225
+	testRequires(c, SameHostDaemon, DaemonIsLinux)
226
+
227
+	err := s.d.Start("--label", `test.empty=`, "--label", `test.empty=`, "--label", `test.label="1"`, "--label", `test.label="2"`)
228
+	c.Assert(err, checker.IsNil)
229
+
230
+	out, err := s.d.Cmd("info")
231
+	c.Assert(err, checker.IsNil)
232
+	c.Assert(out, checker.Contains, "WARNING: labels with duplicate keys and conflicting values have been deprecated")
233
+}