Browse code

Merge pull request #10145 from duglin/Issue10141

Docker run -e FOO should erase FOO if FOO isn't set in client env

Michael Crosby authored on 2015/01/22 07:16:51
Showing 4 changed files
... ...
@@ -1736,9 +1736,12 @@ ports in Docker.
1736 1736
 
1737 1737
 This sets environmental variables in the container. For illustration all three
1738 1738
 flags are shown here. Where `-e`, `--env` take an environment variable and
1739
-value, or if no "=" is provided, then that variable's current value is passed
1740
-through (i.e. `$MYVAR1` from the host is set to `$MYVAR1` in the container). All
1741
-three flags, `-e`, `--env` and `--env-file` can be repeated.
1739
+value, or if no `=` is provided, then that variable's current value is passed
1740
+through (i.e. `$MYVAR1` from the host is set to `$MYVAR1` in the container). 
1741
+When no `=` is provided and that variable is not defined in the client's
1742
+environment then that variable will be removed from the container's list of
1743
+environment variables.
1744
+All three flags, `-e`, `--env` and `--env-file` can be repeated.
1742 1745
 
1743 1746
 Regardless of the order of these three flags, the `--env-file` are processed
1744 1747
 first, and then `-e`, `--env` flags. This way, the `-e` or `--env` will
... ...
@@ -792,10 +792,7 @@ func TestRunEnvironment(t *testing.T) {
792 792
 		t.Fatal(err, out)
793 793
 	}
794 794
 
795
-	actualEnv := strings.Split(out, "\n")
796
-	if actualEnv[len(actualEnv)-1] == "" {
797
-		actualEnv = actualEnv[:len(actualEnv)-1]
798
-	}
795
+	actualEnv := strings.Split(strings.TrimSpace(out), "\n")
799 796
 	sort.Strings(actualEnv)
800 797
 
801 798
 	goodEnv := []string{
... ...
@@ -823,6 +820,72 @@ func TestRunEnvironment(t *testing.T) {
823 823
 	logDone("run - verify environment")
824 824
 }
825 825
 
826
+func TestRunEnvironmentErase(t *testing.T) {
827
+	// Test to make sure that when we use -e on env vars that are
828
+	// not set in our local env that they're removed (if present) in
829
+	// the container
830
+	cmd := exec.Command(dockerBinary, "run", "-e", "FOO", "-e", "HOSTNAME", "busybox", "env")
831
+	cmd.Env = []string{}
832
+	out, _, err := runCommandWithOutput(cmd)
833
+	if err != nil {
834
+		t.Fatal(err, out)
835
+	}
836
+
837
+	actualEnv := strings.Split(strings.TrimSpace(out), "\n")
838
+	sort.Strings(actualEnv)
839
+
840
+	goodEnv := []string{
841
+		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
842
+		"HOME=/root",
843
+	}
844
+	sort.Strings(goodEnv)
845
+	if len(goodEnv) != len(actualEnv) {
846
+		t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", "))
847
+	}
848
+	for i := range goodEnv {
849
+		if actualEnv[i] != goodEnv[i] {
850
+			t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i])
851
+		}
852
+	}
853
+
854
+	deleteAllContainers()
855
+
856
+	logDone("run - verify environment erase")
857
+}
858
+
859
+func TestRunEnvironmentOverride(t *testing.T) {
860
+	// Test to make sure that when we use -e on env vars that are
861
+	// already in the env that we're overriding them
862
+	cmd := exec.Command(dockerBinary, "run", "-e", "HOSTNAME", "-e", "HOME=/root2", "busybox", "env")
863
+	cmd.Env = []string{"HOSTNAME=bar"}
864
+	out, _, err := runCommandWithOutput(cmd)
865
+	if err != nil {
866
+		t.Fatal(err, out)
867
+	}
868
+
869
+	actualEnv := strings.Split(strings.TrimSpace(out), "\n")
870
+	sort.Strings(actualEnv)
871
+
872
+	goodEnv := []string{
873
+		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
874
+		"HOME=/root2",
875
+		"HOSTNAME=bar",
876
+	}
877
+	sort.Strings(goodEnv)
878
+	if len(goodEnv) != len(actualEnv) {
879
+		t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", "))
880
+	}
881
+	for i := range goodEnv {
882
+		if actualEnv[i] != goodEnv[i] {
883
+			t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i])
884
+		}
885
+	}
886
+
887
+	deleteAllContainers()
888
+
889
+	logDone("run - verify environment override")
890
+}
891
+
826 892
 func TestRunContainerNetwork(t *testing.T) {
827 893
 	cmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "127.0.0.1")
828 894
 	if _, err := runCommand(cmd); err != nil {
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/docker/docker/api"
12 12
 	flag "github.com/docker/docker/pkg/mflag"
13 13
 	"github.com/docker/docker/pkg/parsers"
14
+	"github.com/docker/docker/utils"
14 15
 )
15 16
 
16 17
 var (
... ...
@@ -168,6 +169,9 @@ func ValidateEnv(val string) (string, error) {
168 168
 	if len(arr) > 1 {
169 169
 		return val, nil
170 170
 	}
171
+	if !utils.DoesEnvExist(val) {
172
+		return val, nil
173
+	}
171 174
 	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
172 175
 }
173 176
 
... ...
@@ -401,7 +401,17 @@ func ReplaceOrAppendEnvValues(defaults, overrides []string) []string {
401 401
 		parts := strings.SplitN(e, "=", 2)
402 402
 		cache[parts[0]] = i
403 403
 	}
404
+
404 405
 	for _, value := range overrides {
406
+		// Values w/o = means they want this env to be removed/unset.
407
+		if !strings.Contains(value, "=") {
408
+			if i, exists := cache[value]; exists {
409
+				defaults[i] = "" // Used to indicate it should be removed
410
+			}
411
+			continue
412
+		}
413
+
414
+		// Just do a normal set/update
405 415
 		parts := strings.SplitN(value, "=", 2)
406 416
 		if i, exists := cache[parts[0]]; exists {
407 417
 			defaults[i] = value
... ...
@@ -409,9 +419,28 @@ func ReplaceOrAppendEnvValues(defaults, overrides []string) []string {
409 409
 			defaults = append(defaults, value)
410 410
 		}
411 411
 	}
412
+
413
+	// Now remove all entries that we want to "unset"
414
+	for i := 0; i < len(defaults); i++ {
415
+		if defaults[i] == "" {
416
+			defaults = append(defaults[:i], defaults[i+1:]...)
417
+			i--
418
+		}
419
+	}
420
+
412 421
 	return defaults
413 422
 }
414 423
 
424
+func DoesEnvExist(name string) bool {
425
+	for _, entry := range os.Environ() {
426
+		parts := strings.SplitN(entry, "=", 2)
427
+		if parts[0] == name {
428
+			return true
429
+		}
430
+	}
431
+	return false
432
+}
433
+
415 434
 // ReadSymlinkedDirectory returns the target directory of a symlink.
416 435
 // The target of the symbolic link may not be a file.
417 436
 func ReadSymlinkedDirectory(path string) (string, error) {