1package runconfig // import "github.com/docker/docker/runconfig" 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "runtime" 9 "strings" 10 "testing" 11 12 "github.com/docker/docker/api/types/container" 13 networktypes "github.com/docker/docker/api/types/network" 14 "github.com/docker/docker/api/types/strslice" 15 "gotest.tools/assert" 16 is "gotest.tools/assert/cmp" 17) 18 19type f struct { 20 file string 21 entrypoint strslice.StrSlice 22} 23 24func TestDecodeContainerConfig(t *testing.T) { 25 26 var ( 27 fixtures []f 28 image string 29 ) 30 31 if runtime.GOOS != "windows" { 32 image = "ubuntu" 33 fixtures = []f{ 34 {"fixtures/unix/container_config_1_14.json", strslice.StrSlice{}}, 35 {"fixtures/unix/container_config_1_17.json", strslice.StrSlice{"bash"}}, 36 {"fixtures/unix/container_config_1_19.json", strslice.StrSlice{"bash"}}, 37 } 38 } else { 39 image = "windows" 40 fixtures = []f{ 41 {"fixtures/windows/container_config_1_19.json", strslice.StrSlice{"cmd"}}, 42 } 43 } 44 45 for _, f := range fixtures { 46 b, err := ioutil.ReadFile(f.file) 47 if err != nil { 48 t.Fatal(err) 49 } 50 51 c, h, _, err := decodeContainerConfig(bytes.NewReader(b)) 52 if err != nil { 53 t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err)) 54 } 55 56 if c.Image != image { 57 t.Fatalf("Expected %s image, found %s\n", image, c.Image) 58 } 59 60 if len(c.Entrypoint) != len(f.entrypoint) { 61 t.Fatalf("Expected %v, found %v\n", f.entrypoint, c.Entrypoint) 62 } 63 64 if h != nil && h.Memory != 1000 { 65 t.Fatalf("Expected memory to be 1000, found %d\n", h.Memory) 66 } 67 } 68} 69 70// TestDecodeContainerConfigIsolation validates isolation passed 71// to the daemon in the hostConfig structure. Note this is platform specific 72// as to what level of container isolation is supported. 73func TestDecodeContainerConfigIsolation(t *testing.T) { 74 75 // An Invalid isolation level 76 if _, _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil { 77 if !strings.Contains(err.Error(), `Invalid isolation: "invalid"`) { 78 t.Fatal(err) 79 } 80 } 81 82 // Blank isolation (== default) 83 if _, _, _, err := callDecodeContainerConfigIsolation(""); err != nil { 84 t.Fatal("Blank isolation should have succeeded") 85 } 86 87 // Default isolation 88 if _, _, _, err := callDecodeContainerConfigIsolation("default"); err != nil { 89 t.Fatal("default isolation should have succeeded") 90 } 91 92 // Process isolation (Valid on Windows only) 93 if runtime.GOOS == "windows" { 94 if _, _, _, err := callDecodeContainerConfigIsolation("process"); err != nil { 95 t.Fatal("process isolation should have succeeded") 96 } 97 } else { 98 if _, _, _, err := callDecodeContainerConfigIsolation("process"); err != nil { 99 if !strings.Contains(err.Error(), `Invalid isolation: "process"`) { 100 t.Fatal(err) 101 } 102 } 103 } 104 105 // Hyper-V Containers isolation (Valid on Windows only) 106 if runtime.GOOS == "windows" { 107 if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil { 108 t.Fatal("hyperv isolation should have succeeded") 109 } 110 } else { 111 if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil { 112 if !strings.Contains(err.Error(), `Invalid isolation: "hyperv"`) { 113 t.Fatal(err) 114 } 115 } 116 } 117} 118 119// callDecodeContainerConfigIsolation is a utility function to call 120// DecodeContainerConfig for validating isolation 121func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { 122 var ( 123 b []byte 124 err error 125 ) 126 w := ContainerConfigWrapper{ 127 Config: &container.Config{}, 128 HostConfig: &container.HostConfig{ 129 NetworkMode: "none", 130 Isolation: container.Isolation(isolation)}, 131 } 132 if b, err = json.Marshal(w); err != nil { 133 return nil, nil, nil, fmt.Errorf("Error on marshal %s", err.Error()) 134 } 135 return decodeContainerConfig(bytes.NewReader(b)) 136} 137 138type decodeConfigTestcase struct { 139 doc string 140 wrapper ContainerConfigWrapper 141 expectedErr string 142 expectedConfig *container.Config 143 expectedHostConfig *container.HostConfig 144 goos string 145} 146 147func runDecodeContainerConfigTestCase(testcase decodeConfigTestcase) func(t *testing.T) { 148 return func(t *testing.T) { 149 raw := marshal(t, testcase.wrapper, testcase.doc) 150 config, hostConfig, _, err := decodeContainerConfig(bytes.NewReader(raw)) 151 if testcase.expectedErr != "" { 152 if !assert.Check(t, is.ErrorContains(err, "")) { 153 return 154 } 155 assert.Check(t, is.Contains(err.Error(), testcase.expectedErr)) 156 return 157 } 158 assert.Check(t, err) 159 assert.Check(t, is.DeepEqual(testcase.expectedConfig, config)) 160 assert.Check(t, is.DeepEqual(testcase.expectedHostConfig, hostConfig)) 161 } 162} 163 164func marshal(t *testing.T, w ContainerConfigWrapper, doc string) []byte { 165 b, err := json.Marshal(w) 166 assert.NilError(t, err, "%s: failed to encode config wrapper", doc) 167 return b 168} 169 170func containerWrapperWithVolume(volume string) ContainerConfigWrapper { 171 return ContainerConfigWrapper{ 172 Config: &container.Config{ 173 Volumes: map[string]struct{}{ 174 volume: {}, 175 }, 176 }, 177 HostConfig: &container.HostConfig{}, 178 } 179} 180 181func containerWrapperWithBind(bind string) ContainerConfigWrapper { 182 return ContainerConfigWrapper{ 183 Config: &container.Config{ 184 Volumes: map[string]struct{}{}, 185 }, 186 HostConfig: &container.HostConfig{ 187 Binds: []string{bind}, 188 }, 189 } 190} 191