github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/compose/convert/volume_test.go (about)

     1  package convert
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/docker/docker/api/types/mount"
     7  	composetypes "github.com/khulnasoft/cli/cli/compose/types"
     8  	"gotest.tools/v3/assert"
     9  	is "gotest.tools/v3/assert/cmp"
    10  )
    11  
    12  func TestVolumesWithMultipleServiceVolumeConfigs(t *testing.T) {
    13  	namespace := NewNamespace("foo")
    14  
    15  	serviceVolumes := []composetypes.ServiceVolumeConfig{
    16  		{
    17  			Type:   "volume",
    18  			Target: "/foo",
    19  		},
    20  		{
    21  			Type:   "volume",
    22  			Target: "/foo/bar",
    23  		},
    24  	}
    25  
    26  	expected := []mount.Mount{
    27  		{
    28  			Type:   "volume",
    29  			Target: "/foo",
    30  		},
    31  		{
    32  			Type:   "volume",
    33  			Target: "/foo/bar",
    34  		},
    35  	}
    36  
    37  	mnt, err := Volumes(serviceVolumes, volumes{}, namespace)
    38  	assert.NilError(t, err)
    39  	assert.Check(t, is.DeepEqual(expected, mnt))
    40  }
    41  
    42  func TestVolumesWithMultipleServiceVolumeConfigsWithUndefinedVolumeConfig(t *testing.T) {
    43  	namespace := NewNamespace("foo")
    44  
    45  	serviceVolumes := []composetypes.ServiceVolumeConfig{
    46  		{
    47  			Type:   "volume",
    48  			Source: "foo",
    49  			Target: "/foo",
    50  		},
    51  		{
    52  			Type:   "volume",
    53  			Target: "/foo/bar",
    54  		},
    55  	}
    56  
    57  	_, err := Volumes(serviceVolumes, volumes{}, namespace)
    58  	assert.Error(t, err, "undefined volume \"foo\"")
    59  }
    60  
    61  func TestConvertVolumeToMountAnonymousVolume(t *testing.T) {
    62  	config := composetypes.ServiceVolumeConfig{
    63  		Type:   "volume",
    64  		Target: "/foo/bar",
    65  	}
    66  	expected := mount.Mount{
    67  		Type:   mount.TypeVolume,
    68  		Target: "/foo/bar",
    69  	}
    70  	mnt, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
    71  	assert.NilError(t, err)
    72  	assert.Check(t, is.DeepEqual(expected, mnt))
    73  }
    74  
    75  func TestConvertVolumeToMountAnonymousBind(t *testing.T) {
    76  	config := composetypes.ServiceVolumeConfig{
    77  		Type:   "bind",
    78  		Target: "/foo/bar",
    79  		Bind: &composetypes.ServiceVolumeBind{
    80  			Propagation: "slave",
    81  		},
    82  	}
    83  	_, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
    84  	assert.Error(t, err, "invalid bind source, source cannot be empty")
    85  }
    86  
    87  func TestConvertVolumeToMountUnapprovedType(t *testing.T) {
    88  	config := composetypes.ServiceVolumeConfig{
    89  		Type:   "foo",
    90  		Target: "/foo/bar",
    91  	}
    92  	_, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
    93  	assert.Error(t, err, "volume type must be volume, bind, tmpfs, npipe, or cluster")
    94  }
    95  
    96  func TestConvertVolumeToMountConflictingOptionsBindInVolume(t *testing.T) {
    97  	namespace := NewNamespace("foo")
    98  
    99  	config := composetypes.ServiceVolumeConfig{
   100  		Type:   "volume",
   101  		Source: "foo",
   102  		Target: "/target",
   103  		Bind: &composetypes.ServiceVolumeBind{
   104  			Propagation: "slave",
   105  		},
   106  	}
   107  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   108  	assert.Error(t, err, "bind options are incompatible with type volume")
   109  }
   110  
   111  func TestConvertVolumeToMountConflictingOptionsTmpfsInVolume(t *testing.T) {
   112  	namespace := NewNamespace("foo")
   113  
   114  	config := composetypes.ServiceVolumeConfig{
   115  		Type:   "volume",
   116  		Source: "foo",
   117  		Target: "/target",
   118  		Tmpfs: &composetypes.ServiceVolumeTmpfs{
   119  			Size: 1000,
   120  		},
   121  	}
   122  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   123  	assert.Error(t, err, "tmpfs options are incompatible with type volume")
   124  }
   125  
   126  func TestConvertVolumeToMountConflictingOptionsVolumeInBind(t *testing.T) {
   127  	namespace := NewNamespace("foo")
   128  
   129  	config := composetypes.ServiceVolumeConfig{
   130  		Type:   "bind",
   131  		Source: "/foo",
   132  		Target: "/target",
   133  		Volume: &composetypes.ServiceVolumeVolume{
   134  			NoCopy: true,
   135  		},
   136  	}
   137  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   138  	assert.Error(t, err, "volume options are incompatible with type bind")
   139  }
   140  
   141  func TestConvertVolumeToMountConflictingOptionsTmpfsInBind(t *testing.T) {
   142  	namespace := NewNamespace("foo")
   143  
   144  	config := composetypes.ServiceVolumeConfig{
   145  		Type:   "bind",
   146  		Source: "/foo",
   147  		Target: "/target",
   148  		Tmpfs: &composetypes.ServiceVolumeTmpfs{
   149  			Size: 1000,
   150  		},
   151  	}
   152  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   153  	assert.Error(t, err, "tmpfs options are incompatible with type bind")
   154  }
   155  
   156  func TestConvertVolumeToMountConflictingOptionsClusterInVolume(t *testing.T) {
   157  	namespace := NewNamespace("foo")
   158  
   159  	config := composetypes.ServiceVolumeConfig{
   160  		Type:    "volume",
   161  		Target:  "/target",
   162  		Cluster: &composetypes.ServiceVolumeCluster{},
   163  	}
   164  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   165  	assert.Error(t, err, "cluster options are incompatible with type volume")
   166  }
   167  
   168  func TestConvertVolumeToMountConflictingOptionsClusterInBind(t *testing.T) {
   169  	namespace := NewNamespace("foo")
   170  
   171  	config := composetypes.ServiceVolumeConfig{
   172  		Type:   "bind",
   173  		Source: "/foo",
   174  		Target: "/target",
   175  		Bind: &composetypes.ServiceVolumeBind{
   176  			Propagation: "slave",
   177  		},
   178  		Cluster: &composetypes.ServiceVolumeCluster{},
   179  	}
   180  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   181  	assert.Error(t, err, "cluster options are incompatible with type bind")
   182  }
   183  
   184  func TestConvertVolumeToMountConflictingOptionsClusterInTmpfs(t *testing.T) {
   185  	namespace := NewNamespace("foo")
   186  
   187  	config := composetypes.ServiceVolumeConfig{
   188  		Type:   "tmpfs",
   189  		Target: "/target",
   190  		Tmpfs: &composetypes.ServiceVolumeTmpfs{
   191  			Size: 1000,
   192  		},
   193  		Cluster: &composetypes.ServiceVolumeCluster{},
   194  	}
   195  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   196  	assert.Error(t, err, "cluster options are incompatible with type tmpfs")
   197  }
   198  
   199  func TestConvertVolumeToMountConflictingOptionsBindInTmpfs(t *testing.T) {
   200  	namespace := NewNamespace("foo")
   201  
   202  	config := composetypes.ServiceVolumeConfig{
   203  		Type:   "tmpfs",
   204  		Target: "/target",
   205  		Bind: &composetypes.ServiceVolumeBind{
   206  			Propagation: "slave",
   207  		},
   208  	}
   209  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   210  	assert.Error(t, err, "bind options are incompatible with type tmpfs")
   211  }
   212  
   213  func TestConvertVolumeToMountConflictingOptionsVolumeInTmpfs(t *testing.T) {
   214  	namespace := NewNamespace("foo")
   215  
   216  	config := composetypes.ServiceVolumeConfig{
   217  		Type:   "tmpfs",
   218  		Target: "/target",
   219  		Volume: &composetypes.ServiceVolumeVolume{
   220  			NoCopy: true,
   221  		},
   222  	}
   223  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   224  	assert.Error(t, err, "volume options are incompatible with type tmpfs")
   225  }
   226  
   227  func TestHandleNpipeToMountAnonymousNpipe(t *testing.T) {
   228  	namespace := NewNamespace("foo")
   229  
   230  	config := composetypes.ServiceVolumeConfig{
   231  		Type:   "npipe",
   232  		Target: "/target",
   233  		Volume: &composetypes.ServiceVolumeVolume{
   234  			NoCopy: true,
   235  		},
   236  	}
   237  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   238  	assert.Error(t, err, "invalid npipe source, source cannot be empty")
   239  }
   240  
   241  func TestHandleNpipeToMountConflictingOptionsTmpfsInNpipe(t *testing.T) {
   242  	namespace := NewNamespace("foo")
   243  
   244  	config := composetypes.ServiceVolumeConfig{
   245  		Type:   "npipe",
   246  		Source: "/foo",
   247  		Target: "/target",
   248  		Tmpfs: &composetypes.ServiceVolumeTmpfs{
   249  			Size: 1000,
   250  		},
   251  	}
   252  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   253  	assert.Error(t, err, "tmpfs options are incompatible with type npipe")
   254  }
   255  
   256  func TestHandleNpipeToMountConflictingOptionsVolumeInNpipe(t *testing.T) {
   257  	namespace := NewNamespace("foo")
   258  
   259  	config := composetypes.ServiceVolumeConfig{
   260  		Type:   "npipe",
   261  		Source: "/foo",
   262  		Target: "/target",
   263  		Volume: &composetypes.ServiceVolumeVolume{
   264  			NoCopy: true,
   265  		},
   266  	}
   267  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   268  	assert.Error(t, err, "volume options are incompatible with type npipe")
   269  }
   270  
   271  func TestConvertVolumeToMountNamedVolume(t *testing.T) {
   272  	stackVolumes := volumes{
   273  		"normal": composetypes.VolumeConfig{
   274  			Driver: "glusterfs",
   275  			DriverOpts: map[string]string{
   276  				"opt": "value",
   277  			},
   278  			Labels: map[string]string{
   279  				"something": "labeled",
   280  			},
   281  		},
   282  	}
   283  	namespace := NewNamespace("foo")
   284  	expected := mount.Mount{
   285  		Type:     mount.TypeVolume,
   286  		Source:   "foo_normal",
   287  		Target:   "/foo",
   288  		ReadOnly: true,
   289  		VolumeOptions: &mount.VolumeOptions{
   290  			Labels: map[string]string{
   291  				LabelNamespace: "foo",
   292  				"something":    "labeled",
   293  			},
   294  			DriverConfig: &mount.Driver{
   295  				Name: "glusterfs",
   296  				Options: map[string]string{
   297  					"opt": "value",
   298  				},
   299  			},
   300  			NoCopy: true,
   301  		},
   302  	}
   303  	config := composetypes.ServiceVolumeConfig{
   304  		Type:     "volume",
   305  		Source:   "normal",
   306  		Target:   "/foo",
   307  		ReadOnly: true,
   308  		Volume: &composetypes.ServiceVolumeVolume{
   309  			NoCopy: true,
   310  		},
   311  	}
   312  	mnt, err := convertVolumeToMount(config, stackVolumes, namespace)
   313  	assert.NilError(t, err)
   314  	assert.Check(t, is.DeepEqual(expected, mnt))
   315  }
   316  
   317  func TestConvertVolumeToMountNamedVolumeWithNameCustomizd(t *testing.T) {
   318  	stackVolumes := volumes{
   319  		"normal": composetypes.VolumeConfig{
   320  			Name:   "user_specified_name",
   321  			Driver: "vsphere",
   322  			DriverOpts: map[string]string{
   323  				"opt": "value",
   324  			},
   325  			Labels: map[string]string{
   326  				"something": "labeled",
   327  			},
   328  		},
   329  	}
   330  	namespace := NewNamespace("foo")
   331  	expected := mount.Mount{
   332  		Type:     mount.TypeVolume,
   333  		Source:   "user_specified_name",
   334  		Target:   "/foo",
   335  		ReadOnly: true,
   336  		VolumeOptions: &mount.VolumeOptions{
   337  			Labels: map[string]string{
   338  				LabelNamespace: "foo",
   339  				"something":    "labeled",
   340  			},
   341  			DriverConfig: &mount.Driver{
   342  				Name: "vsphere",
   343  				Options: map[string]string{
   344  					"opt": "value",
   345  				},
   346  			},
   347  			NoCopy: true,
   348  		},
   349  	}
   350  	config := composetypes.ServiceVolumeConfig{
   351  		Type:     "volume",
   352  		Source:   "normal",
   353  		Target:   "/foo",
   354  		ReadOnly: true,
   355  		Volume: &composetypes.ServiceVolumeVolume{
   356  			NoCopy: true,
   357  		},
   358  	}
   359  	mnt, err := convertVolumeToMount(config, stackVolumes, namespace)
   360  	assert.NilError(t, err)
   361  	assert.Check(t, is.DeepEqual(expected, mnt))
   362  }
   363  
   364  func TestConvertVolumeToMountNamedVolumeExternal(t *testing.T) {
   365  	stackVolumes := volumes{
   366  		"outside": composetypes.VolumeConfig{
   367  			Name:     "special",
   368  			External: composetypes.External{External: true},
   369  		},
   370  	}
   371  	namespace := NewNamespace("foo")
   372  	expected := mount.Mount{
   373  		Type:          mount.TypeVolume,
   374  		Source:        "special",
   375  		Target:        "/foo",
   376  		VolumeOptions: &mount.VolumeOptions{NoCopy: false},
   377  	}
   378  	config := composetypes.ServiceVolumeConfig{
   379  		Type:   "volume",
   380  		Source: "outside",
   381  		Target: "/foo",
   382  	}
   383  	mnt, err := convertVolumeToMount(config, stackVolumes, namespace)
   384  	assert.NilError(t, err)
   385  	assert.Check(t, is.DeepEqual(expected, mnt))
   386  }
   387  
   388  func TestConvertVolumeToMountNamedVolumeExternalNoCopy(t *testing.T) {
   389  	stackVolumes := volumes{
   390  		"outside": composetypes.VolumeConfig{
   391  			Name:     "special",
   392  			External: composetypes.External{External: true},
   393  		},
   394  	}
   395  	namespace := NewNamespace("foo")
   396  	expected := mount.Mount{
   397  		Type:   mount.TypeVolume,
   398  		Source: "special",
   399  		Target: "/foo",
   400  		VolumeOptions: &mount.VolumeOptions{
   401  			NoCopy: true,
   402  		},
   403  	}
   404  	config := composetypes.ServiceVolumeConfig{
   405  		Type:   "volume",
   406  		Source: "outside",
   407  		Target: "/foo",
   408  		Volume: &composetypes.ServiceVolumeVolume{
   409  			NoCopy: true,
   410  		},
   411  	}
   412  	mnt, err := convertVolumeToMount(config, stackVolumes, namespace)
   413  	assert.NilError(t, err)
   414  	assert.Check(t, is.DeepEqual(expected, mnt))
   415  }
   416  
   417  func TestConvertVolumeToMountBind(t *testing.T) {
   418  	stackVolumes := volumes{}
   419  	namespace := NewNamespace("foo")
   420  	expected := mount.Mount{
   421  		Type:        mount.TypeBind,
   422  		Source:      "/bar",
   423  		Target:      "/foo",
   424  		ReadOnly:    true,
   425  		BindOptions: &mount.BindOptions{Propagation: mount.PropagationShared},
   426  	}
   427  	config := composetypes.ServiceVolumeConfig{
   428  		Type:     "bind",
   429  		Source:   "/bar",
   430  		Target:   "/foo",
   431  		ReadOnly: true,
   432  		Bind:     &composetypes.ServiceVolumeBind{Propagation: "shared"},
   433  	}
   434  	mnt, err := convertVolumeToMount(config, stackVolumes, namespace)
   435  	assert.NilError(t, err)
   436  	assert.Check(t, is.DeepEqual(expected, mnt))
   437  }
   438  
   439  func TestConvertVolumeToMountVolumeDoesNotExist(t *testing.T) {
   440  	namespace := NewNamespace("foo")
   441  	config := composetypes.ServiceVolumeConfig{
   442  		Type:     "volume",
   443  		Source:   "unknown",
   444  		Target:   "/foo",
   445  		ReadOnly: true,
   446  	}
   447  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   448  	assert.Error(t, err, "undefined volume \"unknown\"")
   449  }
   450  
   451  func TestConvertTmpfsToMountVolume(t *testing.T) {
   452  	config := composetypes.ServiceVolumeConfig{
   453  		Type:   "tmpfs",
   454  		Target: "/foo/bar",
   455  		Tmpfs: &composetypes.ServiceVolumeTmpfs{
   456  			Size: 1000,
   457  		},
   458  	}
   459  	expected := mount.Mount{
   460  		Type:         mount.TypeTmpfs,
   461  		Target:       "/foo/bar",
   462  		TmpfsOptions: &mount.TmpfsOptions{SizeBytes: 1000},
   463  	}
   464  	mnt, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
   465  	assert.NilError(t, err)
   466  	assert.Check(t, is.DeepEqual(expected, mnt))
   467  }
   468  
   469  func TestConvertTmpfsToMountVolumeWithSource(t *testing.T) {
   470  	config := composetypes.ServiceVolumeConfig{
   471  		Type:   "tmpfs",
   472  		Source: "/bar",
   473  		Target: "/foo/bar",
   474  		Tmpfs: &composetypes.ServiceVolumeTmpfs{
   475  			Size: 1000,
   476  		},
   477  	}
   478  
   479  	_, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
   480  	assert.Error(t, err, "invalid tmpfs source, source must be empty")
   481  }
   482  
   483  func TestHandleNpipeToMountBind(t *testing.T) {
   484  	namespace := NewNamespace("foo")
   485  	expected := mount.Mount{
   486  		Type:        mount.TypeNamedPipe,
   487  		Source:      "/bar",
   488  		Target:      "/foo",
   489  		ReadOnly:    true,
   490  		BindOptions: &mount.BindOptions{Propagation: mount.PropagationShared},
   491  	}
   492  	config := composetypes.ServiceVolumeConfig{
   493  		Type:     "npipe",
   494  		Source:   "/bar",
   495  		Target:   "/foo",
   496  		ReadOnly: true,
   497  		Bind:     &composetypes.ServiceVolumeBind{Propagation: "shared"},
   498  	}
   499  	mnt, err := convertVolumeToMount(config, volumes{}, namespace)
   500  	assert.NilError(t, err)
   501  	assert.Check(t, is.DeepEqual(expected, mnt))
   502  }
   503  
   504  func TestConvertVolumeToMountAnonymousNpipe(t *testing.T) {
   505  	config := composetypes.ServiceVolumeConfig{
   506  		Type:   "npipe",
   507  		Source: `\\.\pipe\foo`,
   508  		Target: `\\.\pipe\foo`,
   509  	}
   510  	expected := mount.Mount{
   511  		Type:   mount.TypeNamedPipe,
   512  		Source: `\\.\pipe\foo`,
   513  		Target: `\\.\pipe\foo`,
   514  	}
   515  	mnt, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
   516  	assert.NilError(t, err)
   517  	assert.Check(t, is.DeepEqual(expected, mnt))
   518  }
   519  
   520  func TestConvertVolumeMountClusterName(t *testing.T) {
   521  	stackVolumes := volumes{
   522  		"my-csi": composetypes.VolumeConfig{
   523  			Driver: "mycsidriver",
   524  			Spec: &composetypes.ClusterVolumeSpec{
   525  				Group: "mygroup",
   526  				AccessMode: &composetypes.AccessMode{
   527  					Scope:       "single",
   528  					Sharing:     "none",
   529  					BlockVolume: &composetypes.BlockVolume{},
   530  				},
   531  				Availability: "active",
   532  			},
   533  		},
   534  	}
   535  
   536  	config := composetypes.ServiceVolumeConfig{
   537  		Type:   "cluster",
   538  		Source: "my-csi",
   539  		Target: "/srv",
   540  	}
   541  
   542  	expected := mount.Mount{
   543  		Type:           mount.TypeCluster,
   544  		Source:         "foo_my-csi",
   545  		Target:         "/srv",
   546  		ClusterOptions: &mount.ClusterOptions{},
   547  	}
   548  
   549  	mnt, err := convertVolumeToMount(config, stackVolumes, NewNamespace("foo"))
   550  	assert.NilError(t, err)
   551  	assert.Check(t, is.DeepEqual(expected, mnt))
   552  }
   553  
   554  func TestConvertVolumeMountClusterGroup(t *testing.T) {
   555  	stackVolumes := volumes{
   556  		"my-csi": composetypes.VolumeConfig{
   557  			Driver: "mycsidriver",
   558  			Spec: &composetypes.ClusterVolumeSpec{
   559  				Group: "mygroup",
   560  				AccessMode: &composetypes.AccessMode{
   561  					Scope:       "single",
   562  					Sharing:     "none",
   563  					BlockVolume: &composetypes.BlockVolume{},
   564  				},
   565  				Availability: "active",
   566  			},
   567  		},
   568  	}
   569  
   570  	config := composetypes.ServiceVolumeConfig{
   571  		Type:   "cluster",
   572  		Source: "group:mygroup",
   573  		Target: "/srv",
   574  	}
   575  
   576  	expected := mount.Mount{
   577  		Type:           mount.TypeCluster,
   578  		Source:         "group:mygroup",
   579  		Target:         "/srv",
   580  		ClusterOptions: &mount.ClusterOptions{},
   581  	}
   582  
   583  	mnt, err := convertVolumeToMount(config, stackVolumes, NewNamespace("foo"))
   584  	assert.NilError(t, err)
   585  	assert.Check(t, is.DeepEqual(expected, mnt))
   586  }
   587  
   588  func TestHandleClusterToMountAnonymousCluster(t *testing.T) {
   589  	namespace := NewNamespace("foo")
   590  
   591  	config := composetypes.ServiceVolumeConfig{
   592  		Type:   "cluster",
   593  		Target: "/target",
   594  		Volume: &composetypes.ServiceVolumeVolume{
   595  			NoCopy: true,
   596  		},
   597  	}
   598  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   599  	assert.Error(t, err, "invalid cluster source, source cannot be empty")
   600  }
   601  
   602  func TestHandleClusterToMountConflictingOptionsTmpfsInCluster(t *testing.T) {
   603  	namespace := NewNamespace("foo")
   604  
   605  	config := composetypes.ServiceVolumeConfig{
   606  		Type:   "cluster",
   607  		Source: "/foo",
   608  		Target: "/target",
   609  		Tmpfs: &composetypes.ServiceVolumeTmpfs{
   610  			Size: 1000,
   611  		},
   612  	}
   613  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   614  	assert.Error(t, err, "tmpfs options are incompatible with type cluster")
   615  }
   616  
   617  func TestHandleClusterToMountConflictingOptionsVolumeInCluster(t *testing.T) {
   618  	namespace := NewNamespace("foo")
   619  
   620  	config := composetypes.ServiceVolumeConfig{
   621  		Type:   "cluster",
   622  		Source: "/foo",
   623  		Target: "/target",
   624  		Volume: &composetypes.ServiceVolumeVolume{
   625  			NoCopy: true,
   626  		},
   627  	}
   628  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   629  	assert.Error(t, err, "volume options are incompatible with type cluster")
   630  }
   631  
   632  func TestHandleClusterToMountWithUndefinedVolumeConfig(t *testing.T) {
   633  	namespace := NewNamespace("foo")
   634  
   635  	config := composetypes.ServiceVolumeConfig{
   636  		Type:   "cluster",
   637  		Source: "foo",
   638  		Target: "/srv",
   639  	}
   640  
   641  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   642  	assert.Error(t, err, "undefined volume \"foo\"")
   643  }
   644  
   645  func TestHandleClusterToMountWithVolumeConfigName(t *testing.T) {
   646  	stackVolumes := volumes{
   647  		"foo": composetypes.VolumeConfig{
   648  			Name: "bar",
   649  		},
   650  	}
   651  
   652  	config := composetypes.ServiceVolumeConfig{
   653  		Type:   "cluster",
   654  		Source: "foo",
   655  		Target: "/srv",
   656  	}
   657  
   658  	expected := mount.Mount{
   659  		Type:           mount.TypeCluster,
   660  		Source:         "bar",
   661  		Target:         "/srv",
   662  		ClusterOptions: &mount.ClusterOptions{},
   663  	}
   664  
   665  	mnt, err := convertVolumeToMount(config, stackVolumes, NewNamespace("foo"))
   666  	assert.NilError(t, err)
   667  	assert.Check(t, is.DeepEqual(expected, mnt))
   668  }
   669  
   670  func TestHandleClusterToMountBind(t *testing.T) {
   671  	namespace := NewNamespace("foo")
   672  	config := composetypes.ServiceVolumeConfig{
   673  		Type:     "cluster",
   674  		Source:   "/bar",
   675  		Target:   "/foo",
   676  		ReadOnly: true,
   677  		Bind:     &composetypes.ServiceVolumeBind{Propagation: "shared"},
   678  	}
   679  	_, err := convertVolumeToMount(config, volumes{}, namespace)
   680  	assert.Error(t, err, "bind options are incompatible with type cluster")
   681  }