| package runconfig |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "runtime" |
| "strings" |
| "testing" |
| |
| "github.com/docker/docker/api/types/container" |
| networktypes "github.com/docker/docker/api/types/network" |
| "github.com/docker/docker/api/types/strslice" |
| "github.com/stretchr/testify/assert" |
| "github.com/stretchr/testify/require" |
| ) |
| |
| type f struct { |
| file string |
| entrypoint strslice.StrSlice |
| } |
| |
| func TestDecodeContainerConfig(t *testing.T) { |
| |
| var ( |
| fixtures []f |
| image string |
| ) |
| |
| if runtime.GOOS != "windows" { |
| image = "ubuntu" |
| fixtures = []f{ |
| {"fixtures/unix/container_config_1_14.json", strslice.StrSlice{}}, |
| {"fixtures/unix/container_config_1_17.json", strslice.StrSlice{"bash"}}, |
| {"fixtures/unix/container_config_1_19.json", strslice.StrSlice{"bash"}}, |
| } |
| } else { |
| image = "windows" |
| fixtures = []f{ |
| {"fixtures/windows/container_config_1_19.json", strslice.StrSlice{"cmd"}}, |
| } |
| } |
| |
| for _, f := range fixtures { |
| b, err := ioutil.ReadFile(f.file) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| c, h, _, err := decodeContainerConfig(bytes.NewReader(b)) |
| if err != nil { |
| t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err)) |
| } |
| |
| if c.Image != image { |
| t.Fatalf("Expected %s image, found %s\n", image, c.Image) |
| } |
| |
| if len(c.Entrypoint) != len(f.entrypoint) { |
| t.Fatalf("Expected %v, found %v\n", f.entrypoint, c.Entrypoint) |
| } |
| |
| if h != nil && h.Memory != 1000 { |
| t.Fatalf("Expected memory to be 1000, found %d\n", h.Memory) |
| } |
| } |
| } |
| |
| // TestDecodeContainerConfigIsolation validates isolation passed |
| // to the daemon in the hostConfig structure. Note this is platform specific |
| // as to what level of container isolation is supported. |
| func TestDecodeContainerConfigIsolation(t *testing.T) { |
| |
| // An Invalid isolation level |
| if _, _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil { |
| if !strings.Contains(err.Error(), `Invalid isolation: "invalid"`) { |
| t.Fatal(err) |
| } |
| } |
| |
| // Blank isolation (== default) |
| if _, _, _, err := callDecodeContainerConfigIsolation(""); err != nil { |
| t.Fatal("Blank isolation should have succeeded") |
| } |
| |
| // Default isolation |
| if _, _, _, err := callDecodeContainerConfigIsolation("default"); err != nil { |
| t.Fatal("default isolation should have succeeded") |
| } |
| |
| // Process isolation (Valid on Windows only) |
| if runtime.GOOS == "windows" { |
| if _, _, _, err := callDecodeContainerConfigIsolation("process"); err != nil { |
| t.Fatal("process isolation should have succeeded") |
| } |
| } else { |
| if _, _, _, err := callDecodeContainerConfigIsolation("process"); err != nil { |
| if !strings.Contains(err.Error(), `Invalid isolation: "process"`) { |
| t.Fatal(err) |
| } |
| } |
| } |
| |
| // Hyper-V Containers isolation (Valid on Windows only) |
| if runtime.GOOS == "windows" { |
| if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil { |
| t.Fatal("hyperv isolation should have succeeded") |
| } |
| } else { |
| if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil { |
| if !strings.Contains(err.Error(), `Invalid isolation: "hyperv"`) { |
| t.Fatal(err) |
| } |
| } |
| } |
| } |
| |
| // callDecodeContainerConfigIsolation is a utility function to call |
| // DecodeContainerConfig for validating isolation |
| func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { |
| var ( |
| b []byte |
| err error |
| ) |
| w := ContainerConfigWrapper{ |
| Config: &container.Config{}, |
| HostConfig: &container.HostConfig{ |
| NetworkMode: "none", |
| Isolation: container.Isolation(isolation)}, |
| } |
| if b, err = json.Marshal(w); err != nil { |
| return nil, nil, nil, fmt.Errorf("Error on marshal %s", err.Error()) |
| } |
| return decodeContainerConfig(bytes.NewReader(b)) |
| } |
| |
| type decodeConfigTestcase struct { |
| doc string |
| wrapper ContainerConfigWrapper |
| expectedErr string |
| expectedConfig *container.Config |
| expectedHostConfig *container.HostConfig |
| goos string |
| } |
| |
| func runDecodeContainerConfigTestCase(testcase decodeConfigTestcase) func(t *testing.T) { |
| return func(t *testing.T) { |
| raw := marshal(t, testcase.wrapper, testcase.doc) |
| config, hostConfig, _, err := decodeContainerConfig(bytes.NewReader(raw)) |
| if testcase.expectedErr != "" { |
| if !assert.Error(t, err) { |
| return |
| } |
| assert.Contains(t, err.Error(), testcase.expectedErr) |
| return |
| } |
| assert.NoError(t, err) |
| assert.Equal(t, testcase.expectedConfig, config) |
| assert.Equal(t, testcase.expectedHostConfig, hostConfig) |
| } |
| } |
| |
| func marshal(t *testing.T, w ContainerConfigWrapper, doc string) []byte { |
| b, err := json.Marshal(w) |
| require.NoError(t, err, "%s: failed to encode config wrapper", doc) |
| return b |
| } |
| |
| func containerWrapperWithVolume(volume string) ContainerConfigWrapper { |
| return ContainerConfigWrapper{ |
| Config: &container.Config{ |
| Volumes: map[string]struct{}{ |
| volume: {}, |
| }, |
| }, |
| HostConfig: &container.HostConfig{}, |
| } |
| } |
| |
| func containerWrapperWithBind(bind string) ContainerConfigWrapper { |
| return ContainerConfigWrapper{ |
| Config: &container.Config{ |
| Volumes: map[string]struct{}{}, |
| }, |
| HostConfig: &container.HostConfig{ |
| Binds: []string{bind}, |
| }, |
| } |
| } |