github.com/rawahars/moby@v24.0.4+incompatible/volume/mounts/linux_parser_test.go (about)

     1  package mounts // import "github.com/docker/docker/volume/mounts"
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/docker/docker/api/types/mount"
     9  	"gotest.tools/v3/assert"
    10  )
    11  
    12  func TestLinuxParseMountRaw(t *testing.T) {
    13  	valid := []string{
    14  		"/home",
    15  		"/home:/home",
    16  		"/home:/something/else",
    17  		"/with space",
    18  		"/home:/with space",
    19  		"relative:/absolute-path",
    20  		"hostPath:/containerPath:ro",
    21  		"/hostPath:/containerPath:rw",
    22  		"/rw:/ro",
    23  		"/hostPath:/containerPath:shared",
    24  		"/hostPath:/containerPath:rshared",
    25  		"/hostPath:/containerPath:slave",
    26  		"/hostPath:/containerPath:rslave",
    27  		"/hostPath:/containerPath:private",
    28  		"/hostPath:/containerPath:rprivate",
    29  		"/hostPath:/containerPath:ro,shared",
    30  		"/hostPath:/containerPath:ro,slave",
    31  		"/hostPath:/containerPath:ro,private",
    32  		"/hostPath:/containerPath:ro,z,shared",
    33  		"/hostPath:/containerPath:ro,Z,slave",
    34  		"/hostPath:/containerPath:Z,ro,slave",
    35  		"/hostPath:/containerPath:slave,Z,ro",
    36  		"/hostPath:/containerPath:Z,slave,ro",
    37  		"/hostPath:/containerPath:slave,ro,Z",
    38  		"/hostPath:/containerPath:rslave,ro,Z",
    39  		"/hostPath:/containerPath:ro,rshared,Z",
    40  		"/hostPath:/containerPath:ro,Z,rprivate",
    41  	}
    42  
    43  	invalid := map[string]string{
    44  		"":                                "invalid volume specification",
    45  		"./":                              "mount path must be absolute",
    46  		"../":                             "mount path must be absolute",
    47  		"/:../":                           "mount path must be absolute",
    48  		"/:path":                          "mount path must be absolute",
    49  		":":                               "invalid volume specification",
    50  		"/tmp:":                           "invalid volume specification",
    51  		":test":                           "invalid volume specification",
    52  		":/test":                          "invalid volume specification",
    53  		"tmp:":                            "invalid volume specification",
    54  		":test:":                          "invalid volume specification",
    55  		"::":                              "invalid volume specification",
    56  		":::":                             "invalid volume specification",
    57  		"/tmp:::":                         "invalid volume specification",
    58  		":/tmp::":                         "invalid volume specification",
    59  		"/path:rw":                        "invalid volume specification",
    60  		"/path:ro":                        "invalid volume specification",
    61  		"/rw:rw":                          "invalid volume specification",
    62  		"path:ro":                         "invalid volume specification",
    63  		"/path:/path:sw":                  `invalid mode`,
    64  		"/path:/path:rwz":                 `invalid mode`,
    65  		"/path:/path:ro,rshared,rslave":   `invalid mode`,
    66  		"/path:/path:ro,z,rshared,rslave": `invalid mode`,
    67  		"/path:shared":                    "invalid volume specification",
    68  		"/path:slave":                     "invalid volume specification",
    69  		"/path:private":                   "invalid volume specification",
    70  		"name:/absolute-path:shared":      "invalid volume specification",
    71  		"name:/absolute-path:rshared":     "invalid volume specification",
    72  		"name:/absolute-path:slave":       "invalid volume specification",
    73  		"name:/absolute-path:rslave":      "invalid volume specification",
    74  		"name:/absolute-path:private":     "invalid volume specification",
    75  		"name:/absolute-path:rprivate":    "invalid volume specification",
    76  	}
    77  
    78  	parser := NewLinuxParser()
    79  	if p, ok := parser.(*linuxParser); ok {
    80  		p.fi = mockFiProvider{}
    81  	}
    82  
    83  	for _, path := range valid {
    84  		if _, err := parser.ParseMountRaw(path, "local"); err != nil {
    85  			t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
    86  		}
    87  	}
    88  
    89  	for path, expectedError := range invalid {
    90  		if mp, err := parser.ParseMountRaw(path, "local"); err == nil {
    91  			t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
    92  		} else {
    93  			if !strings.Contains(err.Error(), expectedError) {
    94  				t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
    95  			}
    96  		}
    97  	}
    98  }
    99  
   100  func TestLinuxParseMountRawSplit(t *testing.T) {
   101  	cases := []struct {
   102  		bind      string
   103  		driver    string
   104  		expType   mount.Type
   105  		expDest   string
   106  		expSource string
   107  		expName   string
   108  		expDriver string
   109  		expRW     bool
   110  		fail      bool
   111  	}{
   112  		{"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
   113  		{"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
   114  		{"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
   115  		{"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
   116  		{"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
   117  		{"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
   118  		{"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
   119  		{"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
   120  		{"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
   121  	}
   122  
   123  	parser := NewLinuxParser()
   124  	if p, ok := parser.(*linuxParser); ok {
   125  		p.fi = mockFiProvider{}
   126  	}
   127  
   128  	for i, c := range cases {
   129  		c := c
   130  		t.Run(fmt.Sprintf("%d_%s", i, c.bind), func(t *testing.T) {
   131  			m, err := parser.ParseMountRaw(c.bind, c.driver)
   132  			if c.fail {
   133  				assert.ErrorContains(t, err, "", "expected an error")
   134  				return
   135  			}
   136  
   137  			assert.NilError(t, err)
   138  			assert.Equal(t, m.Destination, c.expDest)
   139  			assert.Equal(t, m.Source, c.expSource)
   140  			assert.Equal(t, m.Name, c.expName)
   141  			assert.Equal(t, m.Driver, c.expDriver)
   142  			assert.Equal(t, m.RW, c.expRW)
   143  			assert.Equal(t, m.Type, c.expType)
   144  		})
   145  	}
   146  }
   147  
   148  // TestLinuxParseMountSpecBindWithFileinfoError makes sure that the parser returns
   149  // the error produced by the fileinfo provider.
   150  //
   151  // Some extra context for the future in case of changes and possible wtf are we
   152  // testing this for:
   153  //
   154  // Currently this "fileInfoProvider" returns (bool, bool, error)
   155  // The 1st bool is "does this path exist"
   156  // The 2nd bool is "is this path a dir"
   157  // Then of course the error is an error.
   158  //
   159  // The issue is the parser was ignoring the error and only looking at the
   160  // "does this path exist" boolean, which is always false if there is an error.
   161  // Then the error returned to the caller was a (slightly, maybe) friendlier
   162  // error string than what comes from `os.Stat`
   163  // So ...the caller was always getting an error saying the path doesn't exist
   164  // even if it does exist but got some other error (like a permission error).
   165  // This is confusing to users.
   166  func TestLinuxParseMountSpecBindWithFileinfoError(t *testing.T) {
   167  	parser := NewLinuxParser()
   168  	testErr := fmt.Errorf("some crazy error")
   169  	if pr, ok := parser.(*linuxParser); ok {
   170  		pr.fi = &mockFiProviderWithError{err: testErr}
   171  	}
   172  
   173  	_, err := parser.ParseMountSpec(mount.Mount{
   174  		Type:   mount.TypeBind,
   175  		Source: `/bananas`,
   176  		Target: `/bananas`,
   177  	})
   178  	assert.ErrorContains(t, err, testErr.Error())
   179  }
   180  
   181  func TestConvertTmpfsOptions(t *testing.T) {
   182  	type testCase struct {
   183  		opt                  mount.TmpfsOptions
   184  		readOnly             bool
   185  		expectedSubstrings   []string
   186  		unexpectedSubstrings []string
   187  	}
   188  	cases := []testCase{
   189  		{
   190  			opt:                  mount.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700},
   191  			readOnly:             false,
   192  			expectedSubstrings:   []string{"size=1m", "mode=700"},
   193  			unexpectedSubstrings: []string{"ro"},
   194  		},
   195  		{
   196  			opt:                  mount.TmpfsOptions{},
   197  			readOnly:             true,
   198  			expectedSubstrings:   []string{"ro"},
   199  			unexpectedSubstrings: []string{},
   200  		},
   201  	}
   202  	p := NewLinuxParser()
   203  	for _, c := range cases {
   204  		data, err := p.ConvertTmpfsOptions(&c.opt, c.readOnly)
   205  		if err != nil {
   206  			t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
   207  				c.opt, c.readOnly, err)
   208  		}
   209  		t.Logf("data=%q", data)
   210  		for _, s := range c.expectedSubstrings {
   211  			if !strings.Contains(data, s) {
   212  				t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c)
   213  			}
   214  		}
   215  		for _, s := range c.unexpectedSubstrings {
   216  			if strings.Contains(data, s) {
   217  				t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c)
   218  			}
   219  		}
   220  	}
   221  }