github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/integration/container/mounts_linux_test.go (about) 1 package container // import "github.com/docker/docker/integration/container" 2 3 import ( 4 "context" 5 "fmt" 6 "path/filepath" 7 "testing" 8 "time" 9 10 "github.com/docker/docker/api/types" 11 containertypes "github.com/docker/docker/api/types/container" 12 mounttypes "github.com/docker/docker/api/types/mount" 13 "github.com/docker/docker/api/types/network" 14 "github.com/docker/docker/api/types/versions" 15 "github.com/docker/docker/client" 16 "github.com/docker/docker/integration/internal/container" 17 "github.com/docker/docker/pkg/mount" 18 "github.com/docker/docker/pkg/system" 19 "gotest.tools/assert" 20 is "gotest.tools/assert/cmp" 21 "gotest.tools/fs" 22 "gotest.tools/poll" 23 "gotest.tools/skip" 24 ) 25 26 func TestContainerNetworkMountsNoChown(t *testing.T) { 27 // chown only applies to Linux bind mounted volumes; must be same host to verify 28 skip.If(t, testEnv.IsRemoteDaemon) 29 30 defer setupTest(t)() 31 32 ctx := context.Background() 33 34 tmpDir := fs.NewDir(t, "network-file-mounts", fs.WithMode(0755), fs.WithFile("nwfile", "network file bind mount", fs.WithMode(0644))) 35 defer tmpDir.Remove() 36 37 tmpNWFileMount := tmpDir.Join("nwfile") 38 39 config := containertypes.Config{ 40 Image: "busybox", 41 } 42 hostConfig := containertypes.HostConfig{ 43 Mounts: []mounttypes.Mount{ 44 { 45 Type: "bind", 46 Source: tmpNWFileMount, 47 Target: "/etc/resolv.conf", 48 }, 49 { 50 Type: "bind", 51 Source: tmpNWFileMount, 52 Target: "/etc/hostname", 53 }, 54 { 55 Type: "bind", 56 Source: tmpNWFileMount, 57 Target: "/etc/hosts", 58 }, 59 }, 60 } 61 62 cli, err := client.NewClientWithOpts(client.FromEnv) 63 assert.NilError(t, err) 64 defer cli.Close() 65 66 ctrCreate, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{}, "") 67 assert.NilError(t, err) 68 // container will exit immediately because of no tty, but we only need the start sequence to test the condition 69 err = cli.ContainerStart(ctx, ctrCreate.ID, types.ContainerStartOptions{}) 70 assert.NilError(t, err) 71 72 // Check that host-located bind mount network file did not change ownership when the container was started 73 // Note: If the user specifies a mountpath from the host, we should not be 74 // attempting to chown files outside the daemon's metadata directory 75 // (represented by `daemon.repository` at init time). 76 // This forces users who want to use user namespaces to handle the 77 // ownership needs of any external files mounted as network files 78 // (/etc/resolv.conf, /etc/hosts, /etc/hostname) separately from the 79 // daemon. In all other volume/bind mount situations we have taken this 80 // same line--we don't chown host file content. 81 // See GitHub PR 34224 for details. 82 statT, err := system.Stat(tmpNWFileMount) 83 assert.NilError(t, err) 84 assert.Check(t, is.Equal(uint32(0), statT.UID()), "bind mounted network file should not change ownership from root") 85 } 86 87 func TestMountDaemonRoot(t *testing.T) { 88 skip.If(t, testEnv.IsRemoteDaemon) 89 90 defer setupTest(t)() 91 client := testEnv.APIClient() 92 ctx := context.Background() 93 info, err := client.Info(ctx) 94 if err != nil { 95 t.Fatal(err) 96 } 97 98 for _, test := range []struct { 99 desc string 100 propagation mounttypes.Propagation 101 expected mounttypes.Propagation 102 }{ 103 { 104 desc: "default", 105 propagation: "", 106 expected: mounttypes.PropagationRSlave, 107 }, 108 { 109 desc: "private", 110 propagation: mounttypes.PropagationPrivate, 111 }, 112 { 113 desc: "rprivate", 114 propagation: mounttypes.PropagationRPrivate, 115 }, 116 { 117 desc: "slave", 118 propagation: mounttypes.PropagationSlave, 119 }, 120 { 121 desc: "rslave", 122 propagation: mounttypes.PropagationRSlave, 123 expected: mounttypes.PropagationRSlave, 124 }, 125 { 126 desc: "shared", 127 propagation: mounttypes.PropagationShared, 128 }, 129 { 130 desc: "rshared", 131 propagation: mounttypes.PropagationRShared, 132 expected: mounttypes.PropagationRShared, 133 }, 134 } { 135 t.Run(test.desc, func(t *testing.T) { 136 test := test 137 t.Parallel() 138 139 propagationSpec := fmt.Sprintf(":%s", test.propagation) 140 if test.propagation == "" { 141 propagationSpec = "" 142 } 143 bindSpecRoot := info.DockerRootDir + ":" + "/foo" + propagationSpec 144 bindSpecSub := filepath.Join(info.DockerRootDir, "containers") + ":/foo" + propagationSpec 145 146 for name, hc := range map[string]*containertypes.HostConfig{ 147 "bind root": {Binds: []string{bindSpecRoot}}, 148 "bind subpath": {Binds: []string{bindSpecSub}}, 149 "mount root": { 150 Mounts: []mounttypes.Mount{ 151 { 152 Type: mounttypes.TypeBind, 153 Source: info.DockerRootDir, 154 Target: "/foo", 155 BindOptions: &mounttypes.BindOptions{Propagation: test.propagation}, 156 }, 157 }, 158 }, 159 "mount subpath": { 160 Mounts: []mounttypes.Mount{ 161 { 162 Type: mounttypes.TypeBind, 163 Source: filepath.Join(info.DockerRootDir, "containers"), 164 Target: "/foo", 165 BindOptions: &mounttypes.BindOptions{Propagation: test.propagation}, 166 }, 167 }, 168 }, 169 } { 170 t.Run(name, func(t *testing.T) { 171 hc := hc 172 t.Parallel() 173 174 c, err := client.ContainerCreate(ctx, &containertypes.Config{ 175 Image: "busybox", 176 Cmd: []string{"true"}, 177 }, hc, nil, "") 178 179 if err != nil { 180 if test.expected != "" { 181 t.Fatal(err) 182 } 183 // expected an error, so this is ok and should not continue 184 return 185 } 186 if test.expected == "" { 187 t.Fatal("expected create to fail") 188 } 189 190 defer func() { 191 if err := client.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{Force: true}); err != nil { 192 panic(err) 193 } 194 }() 195 196 inspect, err := client.ContainerInspect(ctx, c.ID) 197 if err != nil { 198 t.Fatal(err) 199 } 200 if len(inspect.Mounts) != 1 { 201 t.Fatalf("unexpected number of mounts: %+v", inspect.Mounts) 202 } 203 204 m := inspect.Mounts[0] 205 if m.Propagation != test.expected { 206 t.Fatalf("got unexpected propagation mode, expected %q, got: %v", test.expected, m.Propagation) 207 } 208 }) 209 } 210 }) 211 } 212 } 213 214 func TestContainerBindMountNonRecursive(t *testing.T) { 215 skip.If(t, testEnv.IsRemoteDaemon) 216 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "BindOptions.NonRecursive requires API v1.40") 217 218 defer setupTest(t)() 219 220 tmpDir1 := fs.NewDir(t, "tmpdir1", fs.WithMode(0755), 221 fs.WithDir("mnt", fs.WithMode(0755))) 222 defer tmpDir1.Remove() 223 tmpDir1Mnt := filepath.Join(tmpDir1.Path(), "mnt") 224 tmpDir2 := fs.NewDir(t, "tmpdir2", fs.WithMode(0755), 225 fs.WithFile("file", "should not be visible when NonRecursive", fs.WithMode(0644))) 226 defer tmpDir2.Remove() 227 228 err := mount.Mount(tmpDir2.Path(), tmpDir1Mnt, "none", "bind,ro") 229 if err != nil { 230 t.Fatal(err) 231 } 232 defer func() { 233 if err := mount.Unmount(tmpDir1Mnt); err != nil { 234 t.Fatal(err) 235 } 236 }() 237 238 // implicit is recursive (NonRecursive: false) 239 implicit := mounttypes.Mount{ 240 Type: "bind", 241 Source: tmpDir1.Path(), 242 Target: "/foo", 243 ReadOnly: true, 244 } 245 recursive := implicit 246 recursive.BindOptions = &mounttypes.BindOptions{ 247 NonRecursive: false, 248 } 249 recursiveVerifier := []string{"test", "-f", "/foo/mnt/file"} 250 nonRecursive := implicit 251 nonRecursive.BindOptions = &mounttypes.BindOptions{ 252 NonRecursive: true, 253 } 254 nonRecursiveVerifier := []string{"test", "!", "-f", "/foo/mnt/file"} 255 256 ctx := context.Background() 257 client := testEnv.APIClient() 258 containers := []string{ 259 container.Run(ctx, t, client, container.WithMount(implicit), container.WithCmd(recursiveVerifier...)), 260 container.Run(ctx, t, client, container.WithMount(recursive), container.WithCmd(recursiveVerifier...)), 261 container.Run(ctx, t, client, container.WithMount(nonRecursive), container.WithCmd(nonRecursiveVerifier...)), 262 } 263 264 for _, c := range containers { 265 poll.WaitOn(t, container.IsSuccessful(ctx, client, c), poll.WithDelay(100*time.Millisecond)) 266 } 267 }