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 }