github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/providercache/installer_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package providercache 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "log" 11 "net/http" 12 "net/http/httptest" 13 "path/filepath" 14 "strings" 15 "testing" 16 17 "github.com/apparentlymart/go-versions/versions" 18 "github.com/apparentlymart/go-versions/versions/constraints" 19 "github.com/davecgh/go-spew/spew" 20 "github.com/google/go-cmp/cmp" 21 svchost "github.com/hashicorp/terraform-svchost" 22 "github.com/hashicorp/terraform-svchost/disco" 23 24 "github.com/terramate-io/tf/addrs" 25 "github.com/terramate-io/tf/depsfile" 26 "github.com/terramate-io/tf/getproviders" 27 ) 28 29 func TestEnsureProviderVersions(t *testing.T) { 30 // This is a sort of hybrid between table-driven and imperative-style 31 // testing, because the overall sequence of steps is the same for all 32 // of the test cases but the setup and verification have enough different 33 // permutations that it ends up being more concise to express them as 34 // normal code. 35 type Test struct { 36 Source getproviders.Source 37 Prepare func(*testing.T, *Installer, *Dir) 38 LockFile string 39 Reqs getproviders.Requirements 40 Mode InstallMode 41 Check func(*testing.T, *Dir, *depsfile.Locks) 42 WantErr string 43 WantEvents func(*Installer, *Dir) map[addrs.Provider][]*testInstallerEventLogItem 44 } 45 46 // noProvider is just the zero value of addrs.Provider, which we're 47 // using in this test as the key for installer events that are not 48 // specific to a particular provider. 49 var noProvider addrs.Provider 50 beepProvider := addrs.MustParseProviderSourceString("example.com/foo/beep") 51 beepProviderDir := getproviders.PackageLocalDir("testdata/beep-provider") 52 fakePlatform := getproviders.Platform{OS: "bleep", Arch: "bloop"} 53 wrongPlatform := getproviders.Platform{OS: "wrong", Arch: "wrong"} 54 beepProviderHash := getproviders.HashScheme1.New("2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=") 55 terraformProvider := addrs.MustParseProviderSourceString("terraform.io/builtin/terraform") 56 57 tests := map[string]Test{ 58 "no dependencies": { 59 Mode: InstallNewProvidersOnly, 60 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 61 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 62 t.Errorf("unexpected cache directory entries\n%s", spew.Sdump(allCached)) 63 } 64 if allLocked := locks.AllProviders(); len(allLocked) != 0 { 65 t.Errorf("unexpected provider lock entries\n%s", spew.Sdump(allLocked)) 66 } 67 }, 68 WantEvents: func(*Installer, *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 69 return map[addrs.Provider][]*testInstallerEventLogItem{ 70 noProvider: { 71 { 72 Event: "PendingProviders", 73 Args: map[addrs.Provider]getproviders.VersionConstraints(nil), 74 }, 75 }, 76 } 77 }, 78 }, 79 "successful initial install of one provider": { 80 Source: getproviders.NewMockSource( 81 []getproviders.PackageMeta{ 82 { 83 Provider: beepProvider, 84 Version: getproviders.MustParseVersion("1.0.0"), 85 TargetPlatform: fakePlatform, 86 Location: beepProviderDir, 87 }, 88 { 89 Provider: beepProvider, 90 Version: getproviders.MustParseVersion("2.0.0"), 91 TargetPlatform: fakePlatform, 92 Location: beepProviderDir, 93 }, 94 { 95 Provider: beepProvider, 96 Version: getproviders.MustParseVersion("2.1.0"), 97 TargetPlatform: fakePlatform, 98 Location: beepProviderDir, 99 }, 100 }, 101 nil, 102 ), 103 Mode: InstallNewProvidersOnly, 104 Reqs: getproviders.Requirements{ 105 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 106 }, 107 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 108 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 109 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 110 } 111 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 112 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 113 } 114 115 gotLock := locks.Provider(beepProvider) 116 wantLock := depsfile.NewProviderLock( 117 beepProvider, 118 getproviders.MustParseVersion("2.1.0"), 119 getproviders.MustParseVersionConstraints(">= 2.0.0"), 120 []getproviders.Hash{beepProviderHash}, 121 ) 122 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 123 t.Errorf("wrong lock entry\n%s", diff) 124 } 125 126 gotEntry := dir.ProviderLatestVersion(beepProvider) 127 wantEntry := &CachedProvider{ 128 Provider: beepProvider, 129 Version: getproviders.MustParseVersion("2.1.0"), 130 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 131 } 132 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 133 t.Errorf("wrong cache entry\n%s", diff) 134 } 135 }, 136 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 137 return map[addrs.Provider][]*testInstallerEventLogItem{ 138 noProvider: { 139 { 140 Event: "PendingProviders", 141 Args: map[addrs.Provider]getproviders.VersionConstraints{ 142 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 143 }, 144 }, 145 { 146 Event: "ProvidersFetched", 147 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 148 beepProvider: nil, 149 }, 150 }, 151 }, 152 beepProvider: { 153 { 154 Event: "QueryPackagesBegin", 155 Provider: beepProvider, 156 Args: struct { 157 Constraints string 158 Locked bool 159 }{">= 2.0.0", false}, 160 }, 161 { 162 Event: "QueryPackagesSuccess", 163 Provider: beepProvider, 164 Args: "2.1.0", 165 }, 166 { 167 Event: "FetchPackageMeta", 168 Provider: beepProvider, 169 Args: "2.1.0", 170 }, 171 { 172 Event: "FetchPackageBegin", 173 Provider: beepProvider, 174 Args: struct { 175 Version string 176 Location getproviders.PackageLocation 177 }{"2.1.0", beepProviderDir}, 178 }, 179 { 180 Event: "ProvidersLockUpdated", 181 Provider: beepProvider, 182 Args: struct { 183 Version string 184 Local []getproviders.Hash 185 Signed []getproviders.Hash 186 Prior []getproviders.Hash 187 }{ 188 "2.1.0", 189 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 190 nil, 191 nil, 192 }, 193 }, 194 { 195 Event: "FetchPackageSuccess", 196 Provider: beepProvider, 197 Args: struct { 198 Version string 199 LocalDir string 200 AuthResult string 201 }{ 202 "2.1.0", 203 filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 204 "unauthenticated", 205 }, 206 }, 207 }, 208 } 209 }, 210 }, 211 "successful initial install of one provider through a cold global cache": { 212 Source: getproviders.NewMockSource( 213 []getproviders.PackageMeta{ 214 { 215 Provider: beepProvider, 216 Version: getproviders.MustParseVersion("2.0.0"), 217 TargetPlatform: fakePlatform, 218 Location: beepProviderDir, 219 }, 220 { 221 Provider: beepProvider, 222 Version: getproviders.MustParseVersion("2.1.0"), 223 TargetPlatform: fakePlatform, 224 Location: beepProviderDir, 225 }, 226 }, 227 nil, 228 ), 229 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 230 globalCacheDirPath := tmpDir(t) 231 globalCacheDir := NewDirWithPlatform(globalCacheDirPath, fakePlatform) 232 inst.SetGlobalCacheDir(globalCacheDir) 233 }, 234 Mode: InstallNewProvidersOnly, 235 Reqs: getproviders.Requirements{ 236 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 237 }, 238 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 239 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 240 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 241 } 242 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 243 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 244 } 245 246 gotLock := locks.Provider(beepProvider) 247 wantLock := depsfile.NewProviderLock( 248 beepProvider, 249 getproviders.MustParseVersion("2.1.0"), 250 getproviders.MustParseVersionConstraints(">= 2.0.0"), 251 []getproviders.Hash{beepProviderHash}, 252 ) 253 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 254 t.Errorf("wrong lock entry\n%s", diff) 255 } 256 257 gotEntry := dir.ProviderLatestVersion(beepProvider) 258 wantEntry := &CachedProvider{ 259 Provider: beepProvider, 260 Version: getproviders.MustParseVersion("2.1.0"), 261 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 262 } 263 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 264 t.Errorf("wrong cache entry\n%s", diff) 265 } 266 }, 267 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 268 return map[addrs.Provider][]*testInstallerEventLogItem{ 269 noProvider: { 270 { 271 Event: "PendingProviders", 272 Args: map[addrs.Provider]getproviders.VersionConstraints{ 273 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 274 }, 275 }, 276 { 277 Event: "ProvidersFetched", 278 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 279 beepProvider: nil, 280 }, 281 }, 282 }, 283 beepProvider: { 284 { 285 Event: "QueryPackagesBegin", 286 Provider: beepProvider, 287 Args: struct { 288 Constraints string 289 Locked bool 290 }{">= 2.0.0", false}, 291 }, 292 { 293 Event: "QueryPackagesSuccess", 294 Provider: beepProvider, 295 Args: "2.1.0", 296 }, 297 { 298 Event: "FetchPackageMeta", 299 Provider: beepProvider, 300 Args: "2.1.0", 301 }, 302 { 303 Event: "FetchPackageBegin", 304 Provider: beepProvider, 305 Args: struct { 306 Version string 307 Location getproviders.PackageLocation 308 }{"2.1.0", beepProviderDir}, 309 }, 310 { 311 Event: "ProvidersLockUpdated", 312 Provider: beepProvider, 313 Args: struct { 314 Version string 315 Local []getproviders.Hash 316 Signed []getproviders.Hash 317 Prior []getproviders.Hash 318 }{ 319 "2.1.0", 320 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 321 nil, 322 nil, 323 }, 324 }, 325 { 326 Event: "FetchPackageSuccess", 327 Provider: beepProvider, 328 Args: struct { 329 Version string 330 LocalDir string 331 AuthResult string 332 }{ 333 "2.1.0", 334 filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 335 "unauthenticated", 336 }, 337 }, 338 }, 339 } 340 }, 341 }, 342 "successful initial install of one provider through a warm global cache but without a lock file entry": { 343 Source: getproviders.NewMockSource( 344 []getproviders.PackageMeta{ 345 { 346 Provider: beepProvider, 347 Version: getproviders.MustParseVersion("2.0.0"), 348 TargetPlatform: fakePlatform, 349 Location: beepProviderDir, 350 }, 351 { 352 Provider: beepProvider, 353 Version: getproviders.MustParseVersion("2.1.0"), 354 TargetPlatform: fakePlatform, 355 Location: beepProviderDir, 356 }, 357 }, 358 nil, 359 ), 360 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 361 globalCacheDirPath := tmpDir(t) 362 globalCacheDir := NewDirWithPlatform(globalCacheDirPath, fakePlatform) 363 _, err := globalCacheDir.InstallPackage( 364 context.Background(), 365 getproviders.PackageMeta{ 366 Provider: beepProvider, 367 Version: getproviders.MustParseVersion("2.1.0"), 368 TargetPlatform: fakePlatform, 369 Location: beepProviderDir, 370 }, 371 nil, 372 ) 373 if err != nil { 374 t.Fatalf("failed to populate global cache: %s", err) 375 } 376 inst.SetGlobalCacheDir(globalCacheDir) 377 }, 378 Mode: InstallNewProvidersOnly, 379 Reqs: getproviders.Requirements{ 380 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 381 }, 382 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 383 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 384 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 385 } 386 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 387 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 388 } 389 390 gotLock := locks.Provider(beepProvider) 391 wantLock := depsfile.NewProviderLock( 392 beepProvider, 393 getproviders.MustParseVersion("2.1.0"), 394 getproviders.MustParseVersionConstraints(">= 2.0.0"), 395 []getproviders.Hash{beepProviderHash}, 396 ) 397 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 398 t.Errorf("wrong lock entry\n%s", diff) 399 } 400 401 gotEntry := dir.ProviderLatestVersion(beepProvider) 402 wantEntry := &CachedProvider{ 403 Provider: beepProvider, 404 Version: getproviders.MustParseVersion("2.1.0"), 405 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 406 } 407 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 408 t.Errorf("wrong cache entry\n%s", diff) 409 } 410 }, 411 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 412 return map[addrs.Provider][]*testInstallerEventLogItem{ 413 noProvider: { 414 { 415 Event: "PendingProviders", 416 Args: map[addrs.Provider]getproviders.VersionConstraints{ 417 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 418 }, 419 }, 420 { 421 Event: "ProvidersFetched", 422 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 423 beepProvider: nil, 424 }, 425 }, 426 }, 427 beepProvider: { 428 { 429 Event: "QueryPackagesBegin", 430 Provider: beepProvider, 431 Args: struct { 432 Constraints string 433 Locked bool 434 }{">= 2.0.0", false}, 435 }, 436 { 437 Event: "QueryPackagesSuccess", 438 Provider: beepProvider, 439 Args: "2.1.0", 440 }, 441 // Existing cache entry is ineligible for linking because 442 // we have no lock file checksums to compare it to. 443 // Instead, we install from upstream and lock with 444 // whatever checksums we learn in that process. 445 { 446 Event: "FetchPackageMeta", 447 Provider: beepProvider, 448 Args: "2.1.0", 449 }, 450 { 451 Event: "FetchPackageBegin", 452 Provider: beepProvider, 453 Args: struct { 454 Version string 455 Location getproviders.PackageLocation 456 }{ 457 "2.1.0", 458 beepProviderDir, 459 }, 460 }, 461 { 462 Event: "ProvidersLockUpdated", 463 Provider: beepProvider, 464 Args: struct { 465 Version string 466 Local []getproviders.Hash 467 Signed []getproviders.Hash 468 Prior []getproviders.Hash 469 }{ 470 "2.1.0", 471 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 472 nil, 473 nil, 474 }, 475 }, 476 { 477 Event: "FetchPackageSuccess", 478 Provider: beepProvider, 479 Args: struct { 480 Version string 481 LocalDir string 482 AuthResult string 483 }{ 484 "2.1.0", 485 filepath.Join(dir.BasePath(), "/example.com/foo/beep/2.1.0/bleep_bloop"), 486 "unauthenticated", 487 }, 488 }, 489 }, 490 } 491 }, 492 }, 493 "successful initial install of one provider through a warm global cache and correct locked checksum": { 494 Source: getproviders.NewMockSource( 495 []getproviders.PackageMeta{ 496 { 497 Provider: beepProvider, 498 Version: getproviders.MustParseVersion("2.0.0"), 499 TargetPlatform: fakePlatform, 500 Location: beepProviderDir, 501 }, 502 { 503 Provider: beepProvider, 504 Version: getproviders.MustParseVersion("2.1.0"), 505 TargetPlatform: fakePlatform, 506 Location: beepProviderDir, 507 }, 508 }, 509 nil, 510 ), 511 LockFile: ` 512 # The existing cache entry is valid only if it matches a 513 # checksum already recorded in the lock file. 514 provider "example.com/foo/beep" { 515 version = "2.1.0" 516 constraints = ">= 1.0.0" 517 hashes = [ 518 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 519 ] 520 } 521 `, 522 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 523 globalCacheDirPath := tmpDir(t) 524 globalCacheDir := NewDirWithPlatform(globalCacheDirPath, fakePlatform) 525 _, err := globalCacheDir.InstallPackage( 526 context.Background(), 527 getproviders.PackageMeta{ 528 Provider: beepProvider, 529 Version: getproviders.MustParseVersion("2.1.0"), 530 TargetPlatform: fakePlatform, 531 Location: beepProviderDir, 532 }, 533 nil, 534 ) 535 if err != nil { 536 t.Fatalf("failed to populate global cache: %s", err) 537 } 538 inst.SetGlobalCacheDir(globalCacheDir) 539 }, 540 Mode: InstallNewProvidersOnly, 541 Reqs: getproviders.Requirements{ 542 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 543 }, 544 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 545 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 546 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 547 } 548 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 549 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 550 } 551 552 gotLock := locks.Provider(beepProvider) 553 wantLock := depsfile.NewProviderLock( 554 beepProvider, 555 getproviders.MustParseVersion("2.1.0"), 556 getproviders.MustParseVersionConstraints(">= 2.0.0"), 557 []getproviders.Hash{beepProviderHash}, 558 ) 559 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 560 t.Errorf("wrong lock entry\n%s", diff) 561 } 562 563 gotEntry := dir.ProviderLatestVersion(beepProvider) 564 wantEntry := &CachedProvider{ 565 Provider: beepProvider, 566 Version: getproviders.MustParseVersion("2.1.0"), 567 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 568 } 569 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 570 t.Errorf("wrong cache entry\n%s", diff) 571 } 572 }, 573 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 574 return map[addrs.Provider][]*testInstallerEventLogItem{ 575 noProvider: { 576 { 577 Event: "PendingProviders", 578 Args: map[addrs.Provider]getproviders.VersionConstraints{ 579 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 580 }, 581 }, 582 }, 583 beepProvider: { 584 { 585 Event: "QueryPackagesBegin", 586 Provider: beepProvider, 587 Args: struct { 588 Constraints string 589 Locked bool 590 }{">= 2.0.0", true}, 591 }, 592 { 593 Event: "QueryPackagesSuccess", 594 Provider: beepProvider, 595 Args: "2.1.0", 596 }, 597 { 598 Event: "LinkFromCacheBegin", 599 Provider: beepProvider, 600 Args: struct { 601 Version string 602 CacheRoot string 603 }{ 604 "2.1.0", 605 inst.globalCacheDir.BasePath(), 606 }, 607 }, 608 { 609 Event: "ProvidersLockUpdated", 610 Provider: beepProvider, 611 Args: struct { 612 Version string 613 Local []getproviders.Hash 614 Signed []getproviders.Hash 615 Prior []getproviders.Hash 616 }{ 617 "2.1.0", 618 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 619 nil, 620 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 621 }, 622 }, 623 { 624 Event: "LinkFromCacheSuccess", 625 Provider: beepProvider, 626 Args: struct { 627 Version string 628 LocalDir string 629 }{ 630 "2.1.0", 631 filepath.Join(dir.BasePath(), "/example.com/foo/beep/2.1.0/bleep_bloop"), 632 }, 633 }, 634 }, 635 } 636 }, 637 }, 638 "successful initial install of one provider through a warm global cache with an incompatible checksum": { 639 Source: getproviders.NewMockSource( 640 []getproviders.PackageMeta{ 641 { 642 Provider: beepProvider, 643 Version: getproviders.MustParseVersion("2.0.0"), 644 TargetPlatform: fakePlatform, 645 Location: beepProviderDir, 646 }, 647 { 648 Provider: beepProvider, 649 Version: getproviders.MustParseVersion("2.1.0"), 650 TargetPlatform: fakePlatform, 651 Location: beepProviderDir, 652 }, 653 }, 654 nil, 655 ), 656 LockFile: ` 657 # This is approximating the awkward situation where the lock 658 # file was populated by someone who installed from a location 659 # other than the origin registry annd so the set of checksums 660 # is incomplete. In this case we can't prove that our cache 661 # entry is valid and so we silently ignore the cache entry 662 # and try to install from upstream anyway, in the hope that 663 # this will give us an opportunity to access the origin 664 # registry and get a checksum that works for the current 665 # platform. 666 provider "example.com/foo/beep" { 667 version = "2.1.0" 668 constraints = ">= 1.0.0" 669 hashes = [ 670 # NOTE: This is the correct checksum for the 671 # beepProviderDir package, but we're going to 672 # intentionally install from a different directory 673 # below so that the entry in the cache will not 674 # match this checksum. 675 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 676 ] 677 } 678 `, 679 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 680 // This is another "beep provider" package directory that 681 // has a different checksum than the one in beepProviderDir. 682 // We're mimicking the situation where the lock file was 683 // originally built from beepProviderDir but the local system 684 // is running on a different platform and so its existing 685 // cache entry doesn't match the checksum. 686 beepProviderOtherPlatformDir := getproviders.PackageLocalDir("testdata/beep-provider-other-platform") 687 688 globalCacheDirPath := tmpDir(t) 689 globalCacheDir := NewDirWithPlatform(globalCacheDirPath, fakePlatform) 690 _, err := globalCacheDir.InstallPackage( 691 context.Background(), 692 getproviders.PackageMeta{ 693 Provider: beepProvider, 694 Version: getproviders.MustParseVersion("2.1.0"), 695 TargetPlatform: fakePlatform, 696 Location: beepProviderOtherPlatformDir, 697 }, 698 nil, 699 ) 700 if err != nil { 701 t.Fatalf("failed to populate global cache: %s", err) 702 } 703 inst.SetGlobalCacheDir(globalCacheDir) 704 }, 705 Mode: InstallNewProvidersOnly, 706 Reqs: getproviders.Requirements{ 707 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 708 }, 709 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 710 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 711 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 712 } 713 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 714 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 715 } 716 717 gotLock := locks.Provider(beepProvider) 718 wantLock := depsfile.NewProviderLock( 719 beepProvider, 720 getproviders.MustParseVersion("2.1.0"), 721 getproviders.MustParseVersionConstraints(">= 2.0.0"), 722 []getproviders.Hash{beepProviderHash}, 723 ) 724 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 725 t.Errorf("wrong lock entry\n%s", diff) 726 } 727 728 gotEntry := dir.ProviderLatestVersion(beepProvider) 729 wantEntry := &CachedProvider{ 730 Provider: beepProvider, 731 Version: getproviders.MustParseVersion("2.1.0"), 732 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 733 } 734 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 735 t.Errorf("wrong cache entry\n%s", diff) 736 } 737 }, 738 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 739 return map[addrs.Provider][]*testInstallerEventLogItem{ 740 noProvider: { 741 { 742 Event: "PendingProviders", 743 Args: map[addrs.Provider]getproviders.VersionConstraints{ 744 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 745 }, 746 }, 747 { 748 Event: "ProvidersFetched", 749 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 750 beepProvider: nil, 751 }, 752 }, 753 }, 754 beepProvider: { 755 { 756 Event: "QueryPackagesBegin", 757 Provider: beepProvider, 758 Args: struct { 759 Constraints string 760 Locked bool 761 }{">= 2.0.0", true}, 762 }, 763 { 764 Event: "QueryPackagesSuccess", 765 Provider: beepProvider, 766 Args: "2.1.0", 767 }, 768 { 769 Event: "FetchPackageMeta", 770 Provider: beepProvider, 771 Args: "2.1.0", 772 }, 773 { 774 Event: "FetchPackageBegin", 775 Provider: beepProvider, 776 Args: struct { 777 Version string 778 Location getproviders.PackageLocation 779 }{ 780 "2.1.0", 781 beepProviderDir, 782 }, 783 }, 784 { 785 Event: "ProvidersLockUpdated", 786 Provider: beepProvider, 787 Args: struct { 788 Version string 789 Local []getproviders.Hash 790 Signed []getproviders.Hash 791 Prior []getproviders.Hash 792 }{ 793 "2.1.0", 794 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 795 nil, 796 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 797 }, 798 }, 799 { 800 Event: "FetchPackageSuccess", 801 Provider: beepProvider, 802 Args: struct { 803 Version string 804 LocalDir string 805 AuthResult string 806 }{ 807 "2.1.0", 808 filepath.Join(dir.BasePath(), "/example.com/foo/beep/2.1.0/bleep_bloop"), 809 "unauthenticated", 810 }, 811 }, 812 }, 813 } 814 }, 815 }, 816 "successful initial install of one provider through a warm global cache without a lock file entry but allowing the cache to break the lock file": { 817 Source: getproviders.NewMockSource( 818 []getproviders.PackageMeta{ 819 { 820 Provider: beepProvider, 821 Version: getproviders.MustParseVersion("2.0.0"), 822 TargetPlatform: fakePlatform, 823 Location: beepProviderDir, 824 }, 825 { 826 Provider: beepProvider, 827 Version: getproviders.MustParseVersion("2.1.0"), 828 TargetPlatform: fakePlatform, 829 Location: beepProviderDir, 830 }, 831 }, 832 nil, 833 ), 834 LockFile: ` 835 # (intentionally empty) 836 `, 837 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 838 globalCacheDirPath := tmpDir(t) 839 globalCacheDir := NewDirWithPlatform(globalCacheDirPath, fakePlatform) 840 _, err := globalCacheDir.InstallPackage( 841 context.Background(), 842 getproviders.PackageMeta{ 843 Provider: beepProvider, 844 Version: getproviders.MustParseVersion("2.1.0"), 845 TargetPlatform: fakePlatform, 846 Location: beepProviderDir, 847 }, 848 nil, 849 ) 850 if err != nil { 851 t.Fatalf("failed to populate global cache: %s", err) 852 } 853 inst.SetGlobalCacheDir(globalCacheDir) 854 inst.SetGlobalCacheDirMayBreakDependencyLockFile(true) 855 }, 856 Mode: InstallNewProvidersOnly, 857 Reqs: getproviders.Requirements{ 858 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 859 }, 860 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 861 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 862 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 863 } 864 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 865 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 866 } 867 868 gotLock := locks.Provider(beepProvider) 869 wantLock := depsfile.NewProviderLock( 870 beepProvider, 871 getproviders.MustParseVersion("2.1.0"), 872 getproviders.MustParseVersionConstraints(">= 2.0.0"), 873 []getproviders.Hash{beepProviderHash}, 874 ) 875 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 876 t.Errorf("wrong lock entry\n%s", diff) 877 } 878 879 gotEntry := dir.ProviderLatestVersion(beepProvider) 880 wantEntry := &CachedProvider{ 881 Provider: beepProvider, 882 Version: getproviders.MustParseVersion("2.1.0"), 883 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 884 } 885 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 886 t.Errorf("wrong cache entry\n%s", diff) 887 } 888 }, 889 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 890 return map[addrs.Provider][]*testInstallerEventLogItem{ 891 noProvider: { 892 { 893 Event: "PendingProviders", 894 Args: map[addrs.Provider]getproviders.VersionConstraints{ 895 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 896 }, 897 }, 898 }, 899 beepProvider: { 900 { 901 Event: "QueryPackagesBegin", 902 Provider: beepProvider, 903 Args: struct { 904 Constraints string 905 Locked bool 906 }{">= 2.0.0", false}, 907 }, 908 { 909 Event: "QueryPackagesSuccess", 910 Provider: beepProvider, 911 Args: "2.1.0", 912 }, 913 { 914 Event: "LinkFromCacheBegin", 915 Provider: beepProvider, 916 Args: struct { 917 Version string 918 CacheRoot string 919 }{ 920 "2.1.0", 921 inst.globalCacheDir.BasePath(), 922 }, 923 }, 924 { 925 Event: "ProvidersLockUpdated", 926 Provider: beepProvider, 927 Args: struct { 928 Version string 929 Local []getproviders.Hash 930 Signed []getproviders.Hash 931 Prior []getproviders.Hash 932 }{ 933 "2.1.0", 934 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 935 nil, 936 nil, 937 }, 938 }, 939 { 940 Event: "LinkFromCacheSuccess", 941 Provider: beepProvider, 942 Args: struct { 943 Version string 944 LocalDir string 945 }{ 946 "2.1.0", 947 filepath.Join(dir.BasePath(), "/example.com/foo/beep/2.1.0/bleep_bloop"), 948 }, 949 }, 950 }, 951 } 952 }, 953 }, 954 "failing install of one provider through a warm global cache with an incorrect locked checksum while allowing the cache to break the lock file": { 955 Source: getproviders.NewMockSource( 956 []getproviders.PackageMeta{ 957 { 958 Provider: beepProvider, 959 Version: getproviders.MustParseVersion("2.0.0"), 960 TargetPlatform: fakePlatform, 961 Location: beepProviderDir, 962 }, 963 { 964 Provider: beepProvider, 965 Version: getproviders.MustParseVersion("2.1.0"), 966 TargetPlatform: fakePlatform, 967 Location: beepProviderDir, 968 }, 969 }, 970 nil, 971 ), 972 LockFile: ` 973 # The existing cache entry is valid only if it matches a 974 # checksum already recorded in the lock file, but this 975 # test is overriding that rule using a special setting. 976 provider "example.com/foo/beep" { 977 version = "2.1.0" 978 constraints = ">= 1.0.0" 979 hashes = [ 980 "h1:wrong-not-matchy", 981 ] 982 } 983 `, 984 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 985 globalCacheDirPath := tmpDir(t) 986 globalCacheDir := NewDirWithPlatform(globalCacheDirPath, fakePlatform) 987 _, err := globalCacheDir.InstallPackage( 988 context.Background(), 989 getproviders.PackageMeta{ 990 Provider: beepProvider, 991 Version: getproviders.MustParseVersion("2.1.0"), 992 TargetPlatform: fakePlatform, 993 Location: beepProviderDir, 994 }, 995 nil, 996 ) 997 if err != nil { 998 t.Fatalf("failed to populate global cache: %s", err) 999 } 1000 inst.SetGlobalCacheDir(globalCacheDir) 1001 inst.SetGlobalCacheDirMayBreakDependencyLockFile(true) 1002 }, 1003 Mode: InstallNewProvidersOnly, 1004 Reqs: getproviders.Requirements{ 1005 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1006 }, 1007 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1008 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 1009 t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached)) 1010 } 1011 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 1012 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 1013 } 1014 1015 gotLock := locks.Provider(beepProvider) 1016 wantLock := depsfile.NewProviderLock( 1017 // The lock file entry hasn't changed because the cache 1018 // entry didn't match the existing lock file entry. 1019 beepProvider, 1020 getproviders.MustParseVersion("2.1.0"), 1021 getproviders.MustParseVersionConstraints(">= 1.0.0"), 1022 []getproviders.Hash{"h1:wrong-not-matchy"}, 1023 ) 1024 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 1025 t.Errorf("wrong lock entry\n%s", diff) 1026 } 1027 1028 // The provider wasn't installed into the local cache directory 1029 // because that would make the local cache mismatch the 1030 // lock file. 1031 gotEntry := dir.ProviderLatestVersion(beepProvider) 1032 wantEntry := (*CachedProvider)(nil) 1033 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 1034 t.Errorf("wrong cache entry\n%s", diff) 1035 } 1036 }, 1037 WantErr: `doesn't match any of the checksums`, 1038 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1039 return map[addrs.Provider][]*testInstallerEventLogItem{ 1040 noProvider: { 1041 { 1042 Event: "PendingProviders", 1043 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1044 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1045 }, 1046 }, 1047 }, 1048 beepProvider: { 1049 { 1050 Event: "QueryPackagesBegin", 1051 Provider: beepProvider, 1052 Args: struct { 1053 Constraints string 1054 Locked bool 1055 }{">= 2.0.0", true}, 1056 }, 1057 { 1058 Event: "QueryPackagesSuccess", 1059 Provider: beepProvider, 1060 Args: "2.1.0", 1061 }, 1062 { 1063 Event: "LinkFromCacheBegin", 1064 Provider: beepProvider, 1065 Args: struct { 1066 Version string 1067 CacheRoot string 1068 }{ 1069 "2.1.0", 1070 inst.globalCacheDir.BasePath(), 1071 }, 1072 }, 1073 { 1074 Event: "LinkFromCacheFailure", 1075 Provider: beepProvider, 1076 Args: struct { 1077 Version string 1078 Error string 1079 }{ 1080 "2.1.0", 1081 fmt.Sprintf( 1082 "the provider cache at %s has a copy of example.com/foo/beep 2.1.0 that doesn't match any of the checksums recorded in the dependency lock file", 1083 dir.BasePath(), 1084 ), 1085 }, 1086 }, 1087 }, 1088 } 1089 }, 1090 }, 1091 "successful reinstall of one previously-locked provider": { 1092 Source: getproviders.NewMockSource( 1093 []getproviders.PackageMeta{ 1094 { 1095 Provider: beepProvider, 1096 Version: getproviders.MustParseVersion("1.0.0"), 1097 TargetPlatform: fakePlatform, 1098 Location: beepProviderDir, 1099 }, 1100 { 1101 Provider: beepProvider, 1102 Version: getproviders.MustParseVersion("2.0.0"), 1103 TargetPlatform: fakePlatform, 1104 Location: beepProviderDir, 1105 }, 1106 { 1107 Provider: beepProvider, 1108 Version: getproviders.MustParseVersion("2.1.0"), 1109 TargetPlatform: fakePlatform, 1110 Location: beepProviderDir, 1111 }, 1112 }, 1113 nil, 1114 ), 1115 LockFile: ` 1116 provider "example.com/foo/beep" { 1117 version = "2.0.0" 1118 constraints = ">= 2.0.0" 1119 hashes = [ 1120 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 1121 ] 1122 } 1123 `, 1124 Mode: InstallNewProvidersOnly, 1125 Reqs: getproviders.Requirements{ 1126 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1127 }, 1128 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1129 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 1130 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 1131 } 1132 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 1133 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 1134 } 1135 1136 gotLock := locks.Provider(beepProvider) 1137 wantLock := depsfile.NewProviderLock( 1138 beepProvider, 1139 getproviders.MustParseVersion("2.0.0"), 1140 getproviders.MustParseVersionConstraints(">= 2.0.0"), 1141 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1142 ) 1143 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 1144 t.Errorf("wrong lock entry\n%s", diff) 1145 } 1146 1147 gotEntry := dir.ProviderLatestVersion(beepProvider) 1148 wantEntry := &CachedProvider{ 1149 Provider: beepProvider, 1150 Version: getproviders.MustParseVersion("2.0.0"), 1151 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.0.0/bleep_bloop"), 1152 } 1153 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 1154 t.Errorf("wrong cache entry\n%s", diff) 1155 } 1156 }, 1157 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1158 return map[addrs.Provider][]*testInstallerEventLogItem{ 1159 noProvider: { 1160 { 1161 Event: "PendingProviders", 1162 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1163 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1164 }, 1165 }, 1166 { 1167 Event: "ProvidersFetched", 1168 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 1169 beepProvider: nil, 1170 }, 1171 }, 1172 }, 1173 beepProvider: { 1174 { 1175 Event: "QueryPackagesBegin", 1176 Provider: beepProvider, 1177 Args: struct { 1178 Constraints string 1179 Locked bool 1180 }{">= 2.0.0", true}, 1181 }, 1182 { 1183 Event: "QueryPackagesSuccess", 1184 Provider: beepProvider, 1185 Args: "2.0.0", 1186 }, 1187 { 1188 Event: "FetchPackageMeta", 1189 Provider: beepProvider, 1190 Args: "2.0.0", 1191 }, 1192 { 1193 Event: "FetchPackageBegin", 1194 Provider: beepProvider, 1195 Args: struct { 1196 Version string 1197 Location getproviders.PackageLocation 1198 }{"2.0.0", beepProviderDir}, 1199 }, 1200 { 1201 Event: "ProvidersLockUpdated", 1202 Provider: beepProvider, 1203 Args: struct { 1204 Version string 1205 Local []getproviders.Hash 1206 Signed []getproviders.Hash 1207 Prior []getproviders.Hash 1208 }{ 1209 "2.0.0", 1210 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1211 nil, 1212 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1213 }, 1214 }, 1215 { 1216 Event: "FetchPackageSuccess", 1217 Provider: beepProvider, 1218 Args: struct { 1219 Version string 1220 LocalDir string 1221 AuthResult string 1222 }{ 1223 "2.0.0", 1224 filepath.Join(dir.BasePath(), "example.com/foo/beep/2.0.0/bleep_bloop"), 1225 "unauthenticated", 1226 }, 1227 }, 1228 }, 1229 } 1230 }, 1231 }, 1232 "skipped install of one previously-locked and installed provider": { 1233 Source: getproviders.NewMockSource( 1234 []getproviders.PackageMeta{ 1235 { 1236 Provider: beepProvider, 1237 Version: getproviders.MustParseVersion("2.0.0"), 1238 TargetPlatform: fakePlatform, 1239 Location: beepProviderDir, 1240 }, 1241 }, 1242 nil, 1243 ), 1244 LockFile: ` 1245 provider "example.com/foo/beep" { 1246 version = "2.0.0" 1247 constraints = ">= 2.0.0" 1248 hashes = [ 1249 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 1250 ] 1251 } 1252 `, 1253 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 1254 _, err := dir.InstallPackage( 1255 context.Background(), 1256 getproviders.PackageMeta{ 1257 Provider: beepProvider, 1258 Version: getproviders.MustParseVersion("2.0.0"), 1259 TargetPlatform: fakePlatform, 1260 Location: beepProviderDir, 1261 }, 1262 nil, 1263 ) 1264 if err != nil { 1265 t.Fatalf("installation to the test dir failed: %s", err) 1266 } 1267 }, 1268 Mode: InstallNewProvidersOnly, 1269 Reqs: getproviders.Requirements{ 1270 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1271 }, 1272 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1273 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 1274 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 1275 } 1276 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 1277 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 1278 } 1279 1280 gotLock := locks.Provider(beepProvider) 1281 wantLock := depsfile.NewProviderLock( 1282 beepProvider, 1283 getproviders.MustParseVersion("2.0.0"), 1284 getproviders.MustParseVersionConstraints(">= 2.0.0"), 1285 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1286 ) 1287 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 1288 t.Errorf("wrong lock entry\n%s", diff) 1289 } 1290 1291 gotEntry := dir.ProviderLatestVersion(beepProvider) 1292 wantEntry := &CachedProvider{ 1293 Provider: beepProvider, 1294 Version: getproviders.MustParseVersion("2.0.0"), 1295 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.0.0/bleep_bloop"), 1296 } 1297 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 1298 t.Errorf("wrong cache entry\n%s", diff) 1299 } 1300 }, 1301 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1302 return map[addrs.Provider][]*testInstallerEventLogItem{ 1303 noProvider: { 1304 { 1305 Event: "PendingProviders", 1306 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1307 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1308 }, 1309 }, 1310 }, 1311 beepProvider: { 1312 { 1313 Event: "QueryPackagesBegin", 1314 Provider: beepProvider, 1315 Args: struct { 1316 Constraints string 1317 Locked bool 1318 }{">= 2.0.0", true}, 1319 }, 1320 { 1321 Event: "QueryPackagesSuccess", 1322 Provider: beepProvider, 1323 Args: "2.0.0", 1324 }, 1325 { 1326 Event: "ProviderAlreadyInstalled", 1327 Provider: beepProvider, 1328 Args: versions.Version{Major: 2, Minor: 0, Patch: 0}, 1329 }, 1330 }, 1331 } 1332 }, 1333 }, 1334 "successful upgrade of one previously-locked provider": { 1335 Source: getproviders.NewMockSource( 1336 []getproviders.PackageMeta{ 1337 { 1338 Provider: beepProvider, 1339 Version: getproviders.MustParseVersion("1.0.0"), 1340 TargetPlatform: fakePlatform, 1341 Location: beepProviderDir, 1342 }, 1343 { 1344 Provider: beepProvider, 1345 Version: getproviders.MustParseVersion("2.0.0"), 1346 TargetPlatform: fakePlatform, 1347 Location: beepProviderDir, 1348 }, 1349 { 1350 Provider: beepProvider, 1351 Version: getproviders.MustParseVersion("2.1.0"), 1352 TargetPlatform: fakePlatform, 1353 Location: beepProviderDir, 1354 }, 1355 }, 1356 nil, 1357 ), 1358 LockFile: ` 1359 provider "example.com/foo/beep" { 1360 version = "2.0.0" 1361 constraints = ">= 2.0.0" 1362 hashes = [ 1363 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 1364 ] 1365 } 1366 `, 1367 Mode: InstallUpgrades, 1368 Reqs: getproviders.Requirements{ 1369 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1370 }, 1371 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1372 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 1373 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 1374 } 1375 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 1376 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 1377 } 1378 1379 gotLock := locks.Provider(beepProvider) 1380 wantLock := depsfile.NewProviderLock( 1381 beepProvider, 1382 getproviders.MustParseVersion("2.1.0"), 1383 getproviders.MustParseVersionConstraints(">= 2.0.0"), 1384 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1385 ) 1386 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 1387 t.Errorf("wrong lock entry\n%s", diff) 1388 } 1389 1390 gotEntry := dir.ProviderLatestVersion(beepProvider) 1391 wantEntry := &CachedProvider{ 1392 Provider: beepProvider, 1393 Version: getproviders.MustParseVersion("2.1.0"), 1394 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 1395 } 1396 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 1397 t.Errorf("wrong cache entry\n%s", diff) 1398 } 1399 }, 1400 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1401 return map[addrs.Provider][]*testInstallerEventLogItem{ 1402 noProvider: { 1403 { 1404 Event: "PendingProviders", 1405 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1406 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1407 }, 1408 }, 1409 { 1410 Event: "ProvidersFetched", 1411 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 1412 beepProvider: nil, 1413 }, 1414 }, 1415 }, 1416 beepProvider: { 1417 { 1418 Event: "QueryPackagesBegin", 1419 Provider: beepProvider, 1420 Args: struct { 1421 Constraints string 1422 Locked bool 1423 }{">= 2.0.0", false}, 1424 }, 1425 { 1426 Event: "QueryPackagesSuccess", 1427 Provider: beepProvider, 1428 Args: "2.1.0", 1429 }, 1430 { 1431 Event: "FetchPackageMeta", 1432 Provider: beepProvider, 1433 Args: "2.1.0", 1434 }, 1435 { 1436 Event: "FetchPackageBegin", 1437 Provider: beepProvider, 1438 Args: struct { 1439 Version string 1440 Location getproviders.PackageLocation 1441 }{"2.1.0", beepProviderDir}, 1442 }, 1443 { 1444 Event: "ProvidersLockUpdated", 1445 Provider: beepProvider, 1446 Args: struct { 1447 Version string 1448 Local []getproviders.Hash 1449 Signed []getproviders.Hash 1450 Prior []getproviders.Hash 1451 }{ 1452 "2.1.0", 1453 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1454 nil, 1455 nil, 1456 }, 1457 }, 1458 { 1459 Event: "FetchPackageSuccess", 1460 Provider: beepProvider, 1461 Args: struct { 1462 Version string 1463 LocalDir string 1464 AuthResult string 1465 }{ 1466 "2.1.0", 1467 filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 1468 "unauthenticated", 1469 }, 1470 }, 1471 }, 1472 } 1473 }, 1474 }, 1475 "successful install of a built-in provider": { 1476 Source: getproviders.NewMockSource( 1477 []getproviders.PackageMeta{}, 1478 nil, 1479 ), 1480 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 1481 inst.SetBuiltInProviderTypes([]string{"terraform"}) 1482 }, 1483 Mode: InstallNewProvidersOnly, 1484 Reqs: getproviders.Requirements{ 1485 terraformProvider: nil, 1486 }, 1487 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1488 // Built-in providers are neither included in the cache 1489 // directory nor mentioned in the lock file, because they 1490 // are compiled directly into the Terraform executable. 1491 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 1492 t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached)) 1493 } 1494 if allLocked := locks.AllProviders(); len(allLocked) != 0 { 1495 t.Errorf("wrong number of provider lock entries; want none\n%s", spew.Sdump(allLocked)) 1496 } 1497 }, 1498 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1499 return map[addrs.Provider][]*testInstallerEventLogItem{ 1500 noProvider: { 1501 { 1502 Event: "PendingProviders", 1503 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1504 terraformProvider: constraints.IntersectionSpec(nil), 1505 }, 1506 }, 1507 }, 1508 terraformProvider: { 1509 { 1510 Event: "BuiltInProviderAvailable", 1511 Provider: terraformProvider, 1512 }, 1513 }, 1514 } 1515 }, 1516 }, 1517 "remove no-longer-needed provider from lock file": { 1518 Source: getproviders.NewMockSource( 1519 []getproviders.PackageMeta{ 1520 { 1521 Provider: beepProvider, 1522 Version: getproviders.MustParseVersion("1.0.0"), 1523 TargetPlatform: fakePlatform, 1524 Location: beepProviderDir, 1525 }, 1526 }, 1527 nil, 1528 ), 1529 LockFile: ` 1530 provider "example.com/foo/beep" { 1531 version = "1.0.0" 1532 constraints = ">= 1.0.0" 1533 hashes = [ 1534 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 1535 ] 1536 } 1537 provider "example.com/foo/obsolete" { 1538 version = "2.0.0" 1539 constraints = ">= 2.0.0" 1540 hashes = [ 1541 "no:irrelevant", 1542 ] 1543 } 1544 `, 1545 Mode: InstallNewProvidersOnly, 1546 Reqs: getproviders.Requirements{ 1547 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1548 }, 1549 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1550 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 1551 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 1552 } 1553 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 1554 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 1555 } 1556 1557 gotLock := locks.Provider(beepProvider) 1558 wantLock := depsfile.NewProviderLock( 1559 beepProvider, 1560 getproviders.MustParseVersion("1.0.0"), 1561 getproviders.MustParseVersionConstraints(">= 1.0.0"), 1562 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1563 ) 1564 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 1565 t.Errorf("wrong lock entry\n%s", diff) 1566 } 1567 1568 gotEntry := dir.ProviderLatestVersion(beepProvider) 1569 wantEntry := &CachedProvider{ 1570 Provider: beepProvider, 1571 Version: getproviders.MustParseVersion("1.0.0"), 1572 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/1.0.0/bleep_bloop"), 1573 } 1574 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 1575 t.Errorf("wrong cache entry\n%s", diff) 1576 } 1577 }, 1578 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1579 return map[addrs.Provider][]*testInstallerEventLogItem{ 1580 noProvider: { 1581 { 1582 Event: "PendingProviders", 1583 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1584 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1585 }, 1586 }, 1587 { 1588 Event: "ProvidersFetched", 1589 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 1590 beepProvider: nil, 1591 }, 1592 }, 1593 }, 1594 // Note: intentionally no entries for example.com/foo/obsolete 1595 // here, because it's no longer needed and therefore not 1596 // installed. 1597 beepProvider: { 1598 { 1599 Event: "QueryPackagesBegin", 1600 Provider: beepProvider, 1601 Args: struct { 1602 Constraints string 1603 Locked bool 1604 }{">= 1.0.0", true}, 1605 }, 1606 { 1607 Event: "QueryPackagesSuccess", 1608 Provider: beepProvider, 1609 Args: "1.0.0", 1610 }, 1611 { 1612 Event: "FetchPackageMeta", 1613 Provider: beepProvider, 1614 Args: "1.0.0", 1615 }, 1616 { 1617 Event: "FetchPackageBegin", 1618 Provider: beepProvider, 1619 Args: struct { 1620 Version string 1621 Location getproviders.PackageLocation 1622 }{"1.0.0", beepProviderDir}, 1623 }, 1624 { 1625 Event: "ProvidersLockUpdated", 1626 Provider: beepProvider, 1627 Args: struct { 1628 Version string 1629 Local []getproviders.Hash 1630 Signed []getproviders.Hash 1631 Prior []getproviders.Hash 1632 }{ 1633 "1.0.0", 1634 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1635 nil, 1636 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1637 }, 1638 }, 1639 { 1640 Event: "FetchPackageSuccess", 1641 Provider: beepProvider, 1642 Args: struct { 1643 Version string 1644 LocalDir string 1645 AuthResult string 1646 }{ 1647 "1.0.0", 1648 filepath.Join(dir.BasePath(), "example.com/foo/beep/1.0.0/bleep_bloop"), 1649 "unauthenticated", 1650 }, 1651 }, 1652 }, 1653 } 1654 }, 1655 }, 1656 "failed install of a non-existing built-in provider": { 1657 Source: getproviders.NewMockSource( 1658 []getproviders.PackageMeta{}, 1659 nil, 1660 ), 1661 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 1662 // NOTE: We're intentionally not calling 1663 // inst.SetBuiltInProviderTypes to make the "terraform" 1664 // built-in provider available here, so requests for it 1665 // should fail. 1666 }, 1667 Mode: InstallNewProvidersOnly, 1668 Reqs: getproviders.Requirements{ 1669 terraformProvider: nil, 1670 }, 1671 WantErr: `some providers could not be installed: 1672 - terraform.io/builtin/terraform: this Terraform release has no built-in provider named "terraform"`, 1673 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1674 return map[addrs.Provider][]*testInstallerEventLogItem{ 1675 noProvider: { 1676 { 1677 Event: "PendingProviders", 1678 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1679 terraformProvider: constraints.IntersectionSpec(nil), 1680 }, 1681 }, 1682 }, 1683 terraformProvider: { 1684 { 1685 Event: "BuiltInProviderFailure", 1686 Provider: terraformProvider, 1687 Args: `this Terraform release has no built-in provider named "terraform"`, 1688 }, 1689 }, 1690 } 1691 }, 1692 }, 1693 "failed install when a built-in provider has a version constraint": { 1694 Source: getproviders.NewMockSource( 1695 []getproviders.PackageMeta{}, 1696 nil, 1697 ), 1698 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 1699 inst.SetBuiltInProviderTypes([]string{"terraform"}) 1700 }, 1701 Mode: InstallNewProvidersOnly, 1702 Reqs: getproviders.Requirements{ 1703 terraformProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1704 }, 1705 WantErr: `some providers could not be installed: 1706 - terraform.io/builtin/terraform: built-in providers do not support explicit version constraints`, 1707 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1708 return map[addrs.Provider][]*testInstallerEventLogItem{ 1709 noProvider: { 1710 { 1711 Event: "PendingProviders", 1712 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1713 terraformProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1714 }, 1715 }, 1716 }, 1717 terraformProvider: { 1718 { 1719 Event: "BuiltInProviderFailure", 1720 Provider: terraformProvider, 1721 Args: `built-in providers do not support explicit version constraints`, 1722 }, 1723 }, 1724 } 1725 }, 1726 }, 1727 "locked version is excluded by new version constraint": { 1728 Source: getproviders.NewMockSource( 1729 []getproviders.PackageMeta{ 1730 { 1731 Provider: beepProvider, 1732 Version: getproviders.MustParseVersion("1.0.0"), 1733 TargetPlatform: fakePlatform, 1734 Location: beepProviderDir, 1735 }, 1736 { 1737 Provider: beepProvider, 1738 Version: getproviders.MustParseVersion("2.0.0"), 1739 TargetPlatform: fakePlatform, 1740 Location: beepProviderDir, 1741 }, 1742 }, 1743 nil, 1744 ), 1745 LockFile: ` 1746 provider "example.com/foo/beep" { 1747 version = "1.0.0" 1748 constraints = ">= 1.0.0" 1749 hashes = [ 1750 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 1751 ] 1752 } 1753 `, 1754 Mode: InstallNewProvidersOnly, 1755 Reqs: getproviders.Requirements{ 1756 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1757 }, 1758 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1759 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 1760 t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached)) 1761 } 1762 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 1763 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 1764 } 1765 1766 gotLock := locks.Provider(beepProvider) 1767 wantLock := depsfile.NewProviderLock( 1768 beepProvider, 1769 getproviders.MustParseVersion("1.0.0"), 1770 getproviders.MustParseVersionConstraints(">= 1.0.0"), 1771 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1772 ) 1773 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 1774 t.Errorf("wrong lock entry\n%s", diff) 1775 } 1776 }, 1777 WantErr: `some providers could not be installed: 1778 - example.com/foo/beep: locked provider example.com/foo/beep 1.0.0 does not match configured version constraint >= 2.0.0; must use terraform init -upgrade to allow selection of new versions`, 1779 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1780 return map[addrs.Provider][]*testInstallerEventLogItem{ 1781 noProvider: { 1782 { 1783 Event: "PendingProviders", 1784 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1785 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1786 }, 1787 }, 1788 }, 1789 beepProvider: { 1790 { 1791 Event: "QueryPackagesBegin", 1792 Provider: beepProvider, 1793 Args: struct { 1794 Constraints string 1795 Locked bool 1796 }{">= 2.0.0", true}, 1797 }, 1798 { 1799 Event: "QueryPackagesFailure", 1800 Provider: beepProvider, 1801 Args: `locked provider example.com/foo/beep 1.0.0 does not match configured version constraint >= 2.0.0; must use terraform init -upgrade to allow selection of new versions`, 1802 }, 1803 }, 1804 } 1805 }, 1806 }, 1807 "locked version is no longer available": { 1808 Source: getproviders.NewMockSource( 1809 []getproviders.PackageMeta{ 1810 { 1811 Provider: beepProvider, 1812 Version: getproviders.MustParseVersion("1.0.0"), 1813 TargetPlatform: fakePlatform, 1814 Location: beepProviderDir, 1815 }, 1816 { 1817 Provider: beepProvider, 1818 Version: getproviders.MustParseVersion("2.0.0"), 1819 TargetPlatform: fakePlatform, 1820 Location: beepProviderDir, 1821 }, 1822 }, 1823 nil, 1824 ), 1825 LockFile: ` 1826 provider "example.com/foo/beep" { 1827 version = "1.2.0" 1828 constraints = ">= 1.0.0" 1829 hashes = [ 1830 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 1831 ] 1832 } 1833 `, 1834 Mode: InstallNewProvidersOnly, 1835 Reqs: getproviders.Requirements{ 1836 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1837 }, 1838 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1839 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 1840 t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached)) 1841 } 1842 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 1843 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 1844 } 1845 1846 gotLock := locks.Provider(beepProvider) 1847 wantLock := depsfile.NewProviderLock( 1848 beepProvider, 1849 getproviders.MustParseVersion("1.2.0"), 1850 getproviders.MustParseVersionConstraints(">= 1.0.0"), 1851 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1852 ) 1853 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 1854 t.Errorf("wrong lock entry\n%s", diff) 1855 } 1856 }, 1857 WantErr: `some providers could not be installed: 1858 - example.com/foo/beep: the previously-selected version 1.2.0 is no longer available`, 1859 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1860 return map[addrs.Provider][]*testInstallerEventLogItem{ 1861 noProvider: { 1862 { 1863 Event: "PendingProviders", 1864 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1865 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1866 }, 1867 }, 1868 }, 1869 beepProvider: { 1870 { 1871 Event: "QueryPackagesBegin", 1872 Provider: beepProvider, 1873 Args: struct { 1874 Constraints string 1875 Locked bool 1876 }{">= 1.0.0", true}, 1877 }, 1878 { 1879 Event: "QueryPackagesFailure", 1880 Provider: beepProvider, 1881 Args: `the previously-selected version 1.2.0 is no longer available`, 1882 }, 1883 }, 1884 } 1885 }, 1886 }, 1887 "no versions match the version constraint": { 1888 Source: getproviders.NewMockSource( 1889 []getproviders.PackageMeta{ 1890 { 1891 Provider: beepProvider, 1892 Version: getproviders.MustParseVersion("1.0.0"), 1893 TargetPlatform: fakePlatform, 1894 Location: beepProviderDir, 1895 }, 1896 }, 1897 nil, 1898 ), 1899 Mode: InstallNewProvidersOnly, 1900 Reqs: getproviders.Requirements{ 1901 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1902 }, 1903 WantErr: `some providers could not be installed: 1904 - example.com/foo/beep: no available releases match the given constraints >= 2.0.0`, 1905 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1906 return map[addrs.Provider][]*testInstallerEventLogItem{ 1907 noProvider: { 1908 { 1909 Event: "PendingProviders", 1910 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1911 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1912 }, 1913 }, 1914 }, 1915 beepProvider: { 1916 { 1917 Event: "QueryPackagesBegin", 1918 Provider: beepProvider, 1919 Args: struct { 1920 Constraints string 1921 Locked bool 1922 }{">= 2.0.0", false}, 1923 }, 1924 { 1925 Event: "QueryPackagesFailure", 1926 Provider: beepProvider, 1927 Args: `no available releases match the given constraints >= 2.0.0`, 1928 }, 1929 }, 1930 } 1931 }, 1932 }, 1933 "version exists but doesn't support the current platform": { 1934 Source: getproviders.NewMockSource( 1935 []getproviders.PackageMeta{ 1936 { 1937 Provider: beepProvider, 1938 Version: getproviders.MustParseVersion("1.0.0"), 1939 TargetPlatform: wrongPlatform, 1940 Location: beepProviderDir, 1941 }, 1942 }, 1943 nil, 1944 ), 1945 Mode: InstallNewProvidersOnly, 1946 Reqs: getproviders.Requirements{ 1947 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1948 }, 1949 WantErr: `some providers could not be installed: 1950 - example.com/foo/beep: provider example.com/foo/beep 1.0.0 is not available for bleep_bloop`, 1951 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1952 return map[addrs.Provider][]*testInstallerEventLogItem{ 1953 noProvider: { 1954 { 1955 Event: "PendingProviders", 1956 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1957 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1958 }, 1959 }, 1960 }, 1961 beepProvider: { 1962 { 1963 Event: "QueryPackagesBegin", 1964 Provider: beepProvider, 1965 Args: struct { 1966 Constraints string 1967 Locked bool 1968 }{">= 1.0.0", false}, 1969 }, 1970 { 1971 Event: "QueryPackagesSuccess", 1972 Provider: beepProvider, 1973 Args: "1.0.0", 1974 }, 1975 { 1976 Event: "FetchPackageMeta", 1977 Provider: beepProvider, 1978 Args: "1.0.0", 1979 }, 1980 { 1981 Event: "FetchPackageFailure", 1982 Provider: beepProvider, 1983 Args: struct { 1984 Version string 1985 Error string 1986 }{ 1987 "1.0.0", 1988 "provider example.com/foo/beep 1.0.0 is not available for bleep_bloop", 1989 }, 1990 }, 1991 }, 1992 } 1993 }, 1994 }, 1995 "available package doesn't match locked hash": { 1996 Source: getproviders.NewMockSource( 1997 []getproviders.PackageMeta{ 1998 { 1999 Provider: beepProvider, 2000 Version: getproviders.MustParseVersion("1.0.0"), 2001 TargetPlatform: fakePlatform, 2002 Location: beepProviderDir, 2003 }, 2004 }, 2005 nil, 2006 ), 2007 LockFile: ` 2008 provider "example.com/foo/beep" { 2009 version = "1.0.0" 2010 constraints = ">= 1.0.0" 2011 hashes = [ 2012 "h1:does-not-match", 2013 ] 2014 } 2015 `, 2016 Mode: InstallNewProvidersOnly, 2017 Reqs: getproviders.Requirements{ 2018 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 2019 }, 2020 WantErr: `some providers could not be installed: 2021 - example.com/foo/beep: the local package for example.com/foo/beep 1.0.0 doesn't match any of the checksums previously recorded in the dependency lock file (this might be because the available checksums are for packages targeting different platforms); for more information: https://www.terraform.io/language/provider-checksum-verification`, 2022 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 2023 return map[addrs.Provider][]*testInstallerEventLogItem{ 2024 noProvider: { 2025 { 2026 Event: "PendingProviders", 2027 Args: map[addrs.Provider]getproviders.VersionConstraints{ 2028 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 2029 }, 2030 }, 2031 }, 2032 beepProvider: { 2033 { 2034 Event: "QueryPackagesBegin", 2035 Provider: beepProvider, 2036 Args: struct { 2037 Constraints string 2038 Locked bool 2039 }{">= 1.0.0", true}, 2040 }, 2041 { 2042 Event: "QueryPackagesSuccess", 2043 Provider: beepProvider, 2044 Args: "1.0.0", 2045 }, 2046 { 2047 Event: "FetchPackageMeta", 2048 Provider: beepProvider, 2049 Args: "1.0.0", 2050 }, 2051 { 2052 Event: "FetchPackageBegin", 2053 Provider: beepProvider, 2054 Args: struct { 2055 Version string 2056 Location getproviders.PackageLocation 2057 }{"1.0.0", beepProviderDir}, 2058 }, 2059 { 2060 Event: "FetchPackageFailure", 2061 Provider: beepProvider, 2062 Args: struct { 2063 Version string 2064 Error string 2065 }{ 2066 "1.0.0", 2067 `the local package for example.com/foo/beep 1.0.0 doesn't match any of the checksums previously recorded in the dependency lock file (this might be because the available checksums are for packages targeting different platforms); for more information: https://www.terraform.io/language/provider-checksum-verification`, 2068 }, 2069 }, 2070 }, 2071 } 2072 }, 2073 }, 2074 "force mode ignores hashes": { 2075 Source: getproviders.NewMockSource( 2076 []getproviders.PackageMeta{ 2077 { 2078 Provider: beepProvider, 2079 Version: getproviders.MustParseVersion("1.0.0"), 2080 TargetPlatform: fakePlatform, 2081 Location: beepProviderDir, 2082 }, 2083 }, 2084 nil, 2085 ), 2086 LockFile: ` 2087 provider "example.com/foo/beep" { 2088 version = "1.0.0" 2089 constraints = ">= 1.0.0" 2090 hashes = [ 2091 "h1:does-not-match", 2092 ] 2093 } 2094 `, 2095 Mode: InstallNewProvidersForce, 2096 Reqs: getproviders.Requirements{ 2097 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 2098 }, 2099 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 2100 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 2101 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 2102 } 2103 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 2104 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 2105 } 2106 2107 gotLock := locks.Provider(beepProvider) 2108 wantLock := depsfile.NewProviderLock( 2109 beepProvider, 2110 getproviders.MustParseVersion("1.0.0"), 2111 getproviders.MustParseVersionConstraints(">= 1.0.0"), 2112 []getproviders.Hash{beepProviderHash, "h1:does-not-match"}, 2113 ) 2114 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 2115 t.Errorf("wrong lock entry\n%s", diff) 2116 } 2117 2118 gotEntry := dir.ProviderLatestVersion(beepProvider) 2119 wantEntry := &CachedProvider{ 2120 Provider: beepProvider, 2121 Version: getproviders.MustParseVersion("1.0.0"), 2122 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/1.0.0/bleep_bloop"), 2123 } 2124 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 2125 t.Errorf("wrong cache entry\n%s", diff) 2126 } 2127 }, 2128 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 2129 return map[addrs.Provider][]*testInstallerEventLogItem{ 2130 noProvider: { 2131 { 2132 Event: "PendingProviders", 2133 Args: map[addrs.Provider]getproviders.VersionConstraints{ 2134 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 2135 }, 2136 }, 2137 { 2138 Event: "ProvidersFetched", 2139 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 2140 beepProvider: nil, 2141 }, 2142 }, 2143 }, 2144 beepProvider: { 2145 { 2146 Event: "QueryPackagesBegin", 2147 Provider: beepProvider, 2148 Args: struct { 2149 Constraints string 2150 Locked bool 2151 }{">= 1.0.0", true}, 2152 }, 2153 { 2154 Event: "QueryPackagesSuccess", 2155 Provider: beepProvider, 2156 Args: "1.0.0", 2157 }, 2158 { 2159 Event: "FetchPackageMeta", 2160 Provider: beepProvider, 2161 Args: "1.0.0", 2162 }, 2163 { 2164 Event: "FetchPackageBegin", 2165 Provider: beepProvider, 2166 Args: struct { 2167 Version string 2168 Location getproviders.PackageLocation 2169 }{"1.0.0", beepProviderDir}, 2170 }, 2171 { 2172 Event: "ProvidersLockUpdated", 2173 Provider: beepProvider, 2174 Args: struct { 2175 Version string 2176 Local []getproviders.Hash 2177 Signed []getproviders.Hash 2178 Prior []getproviders.Hash 2179 }{ 2180 "1.0.0", 2181 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 2182 nil, 2183 []getproviders.Hash{"h1:does-not-match"}, 2184 }, 2185 }, 2186 { 2187 Event: "FetchPackageSuccess", 2188 Provider: beepProvider, 2189 Args: struct { 2190 Version string 2191 LocalDir string 2192 AuthResult string 2193 }{ 2194 "1.0.0", 2195 filepath.Join(dir.BasePath(), "example.com/foo/beep/1.0.0/bleep_bloop"), 2196 "unauthenticated", 2197 }, 2198 }, 2199 }, 2200 } 2201 }, 2202 }, 2203 } 2204 2205 ctx := context.Background() 2206 2207 for name, test := range tests { 2208 t.Run(name, func(t *testing.T) { 2209 if test.Check == nil && test.WantEvents == nil && test.WantErr == "" { 2210 t.Fatalf("invalid test: must set at least one of Check, WantEvents, or WantErr") 2211 } 2212 2213 outputDir := NewDirWithPlatform(tmpDir(t), fakePlatform) 2214 source := test.Source 2215 if source == nil { 2216 source = getproviders.NewMockSource(nil, nil) 2217 } 2218 inst := NewInstaller(outputDir, source) 2219 if test.Prepare != nil { 2220 test.Prepare(t, inst, outputDir) 2221 } /* boop */ 2222 2223 locks, lockDiags := depsfile.LoadLocksFromBytes([]byte(test.LockFile), "test.lock.hcl") 2224 if lockDiags.HasErrors() { 2225 t.Fatalf("invalid lock file: %s", lockDiags.Err().Error()) 2226 } 2227 2228 providerEvents := make(map[addrs.Provider][]*testInstallerEventLogItem) 2229 eventsCh := make(chan *testInstallerEventLogItem) 2230 var newLocks *depsfile.Locks 2231 var instErr error 2232 go func(ch chan *testInstallerEventLogItem) { 2233 events := installerLogEventsForTests(ch) 2234 ctx := events.OnContext(ctx) 2235 newLocks, instErr = inst.EnsureProviderVersions(ctx, locks, test.Reqs, test.Mode) 2236 close(eventsCh) // exits the event loop below 2237 }(eventsCh) 2238 for evt := range eventsCh { 2239 // We do the event collection in the main goroutine, rather than 2240 // running the installer itself in the main goroutine, so that 2241 // we can safely t.Log in here without violating the testing.T 2242 // usage rules. 2243 if evt.Provider == (addrs.Provider{}) { 2244 t.Logf("%s(%s)", evt.Event, spew.Sdump(evt.Args)) 2245 } else { 2246 t.Logf("%s: %s(%s)", evt.Provider, evt.Event, spew.Sdump(evt.Args)) 2247 } 2248 providerEvents[evt.Provider] = append(providerEvents[evt.Provider], evt) 2249 } 2250 2251 if test.WantErr != "" { 2252 if instErr == nil { 2253 t.Errorf("succeeded; want error\nwant: %s", test.WantErr) 2254 } else if got, want := instErr.Error(), test.WantErr; !strings.Contains(got, want) { 2255 t.Errorf("wrong error\ngot: %s\nwant substring: %s", got, want) 2256 } 2257 } else if instErr != nil { 2258 t.Errorf("unexpected error\ngot: %s", instErr.Error()) 2259 } 2260 2261 if test.Check != nil { 2262 test.Check(t, outputDir, newLocks) 2263 } 2264 2265 if test.WantEvents != nil { 2266 wantEvents := test.WantEvents(inst, outputDir) 2267 if diff := cmp.Diff(wantEvents, providerEvents); diff != "" { 2268 t.Errorf("wrong installer events\n%s", diff) 2269 } 2270 } 2271 }) 2272 } 2273 } 2274 2275 func TestEnsureProviderVersions_local_source(t *testing.T) { 2276 // create filesystem source using the test provider cache dir 2277 source := getproviders.NewFilesystemMirrorSource("testdata/cachedir") 2278 2279 // create a temporary workdir 2280 tmpDirPath := t.TempDir() 2281 2282 // set up the installer using the temporary directory and filesystem source 2283 platform := getproviders.Platform{OS: "linux", Arch: "amd64"} 2284 dir := NewDirWithPlatform(tmpDirPath, platform) 2285 installer := NewInstaller(dir, source) 2286 2287 tests := map[string]struct { 2288 provider string 2289 version string 2290 wantHash getproviders.Hash // getproviders.NilHash if not expected to be installed 2291 err string 2292 }{ 2293 "install-unpacked": { 2294 provider: "null", 2295 version: "2.0.0", 2296 wantHash: getproviders.HashScheme1.New("qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g="), 2297 }, 2298 "invalid-zip-file": { 2299 provider: "null", 2300 version: "2.1.0", 2301 wantHash: getproviders.NilHash, 2302 err: "zip: not a valid zip file", 2303 }, 2304 "version-constraint-unmet": { 2305 provider: "null", 2306 version: "2.2.0", 2307 wantHash: getproviders.NilHash, 2308 err: "no available releases match the given constraints 2.2.0", 2309 }, 2310 "missing-executable": { 2311 provider: "missing/executable", 2312 version: "2.0.0", 2313 wantHash: getproviders.NilHash, // installation fails for a provider with no executable 2314 err: "provider binary not found: could not find executable file starting with terraform-provider-executable", 2315 }, 2316 } 2317 2318 for name, test := range tests { 2319 t.Run(name, func(t *testing.T) { 2320 ctx := context.TODO() 2321 2322 provider := addrs.MustParseProviderSourceString(test.provider) 2323 versionConstraint := getproviders.MustParseVersionConstraints(test.version) 2324 version := getproviders.MustParseVersion(test.version) 2325 reqs := getproviders.Requirements{ 2326 provider: versionConstraint, 2327 } 2328 2329 newLocks, err := installer.EnsureProviderVersions(ctx, depsfile.NewLocks(), reqs, InstallNewProvidersOnly) 2330 gotProviderlocks := newLocks.AllProviders() 2331 wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{ 2332 provider: depsfile.NewProviderLock( 2333 provider, 2334 version, 2335 getproviders.MustParseVersionConstraints("= 2.0.0"), 2336 []getproviders.Hash{ 2337 test.wantHash, 2338 }, 2339 ), 2340 } 2341 if test.wantHash == getproviders.NilHash { 2342 wantProviderLocks = map[addrs.Provider]*depsfile.ProviderLock{} 2343 } 2344 2345 if diff := cmp.Diff(wantProviderLocks, gotProviderlocks, depsfile.ProviderLockComparer); diff != "" { 2346 t.Errorf("wrong selected\n%s", diff) 2347 } 2348 2349 if test.err == "" && err == nil { 2350 return 2351 } 2352 2353 switch err := err.(type) { 2354 case InstallerError: 2355 providerError, ok := err.ProviderErrors[provider] 2356 if !ok { 2357 t.Fatalf("did not get error for provider %s", provider) 2358 } 2359 2360 if got := providerError.Error(); got != test.err { 2361 t.Fatalf("wrong result\ngot: %s\nwant: %s\n", got, test.err) 2362 } 2363 default: 2364 t.Fatalf("wrong error type. Expected InstallerError, got %T", err) 2365 } 2366 }) 2367 } 2368 } 2369 2370 // This test only verifies protocol errors and does not try for successfull 2371 // installation (at the time of writing, the test files aren't signed so the 2372 // signature verification fails); that's left to the e2e tests. 2373 func TestEnsureProviderVersions_protocol_errors(t *testing.T) { 2374 source, _, close := testRegistrySource(t) 2375 defer close() 2376 2377 // create a temporary workdir 2378 tmpDirPath := t.TempDir() 2379 2380 version0 := getproviders.MustParseVersionConstraints("0.1.0") // supports protocol version 1.0 2381 version1 := getproviders.MustParseVersion("1.2.0") // this is the expected result in tests with a match 2382 version2 := getproviders.MustParseVersionConstraints("2.0") // supports protocol version 99 2383 2384 // set up the installer using the temporary directory and mock source 2385 platform := getproviders.Platform{OS: "gameboy", Arch: "lr35902"} 2386 dir := NewDirWithPlatform(tmpDirPath, platform) 2387 installer := NewInstaller(dir, source) 2388 2389 tests := map[string]struct { 2390 provider addrs.Provider 2391 inputVersion getproviders.VersionConstraints 2392 wantVersion getproviders.Version 2393 }{ 2394 "too old": { 2395 addrs.MustParseProviderSourceString("example.com/awesomesauce/happycloud"), 2396 version0, 2397 version1, 2398 }, 2399 "too new": { 2400 addrs.MustParseProviderSourceString("example.com/awesomesauce/happycloud"), 2401 version2, 2402 version1, 2403 }, 2404 "unsupported": { 2405 addrs.MustParseProviderSourceString("example.com/weaksauce/unsupported-protocol"), 2406 version0, 2407 getproviders.UnspecifiedVersion, 2408 }, 2409 } 2410 2411 for name, test := range tests { 2412 t.Run(name, func(t *testing.T) { 2413 reqs := getproviders.Requirements{ 2414 test.provider: test.inputVersion, 2415 } 2416 ctx := context.TODO() 2417 _, err := installer.EnsureProviderVersions(ctx, depsfile.NewLocks(), reqs, InstallNewProvidersOnly) 2418 2419 switch err := err.(type) { 2420 case nil: 2421 t.Fatalf("expected error, got success") 2422 case InstallerError: 2423 providerError, ok := err.ProviderErrors[test.provider] 2424 if !ok { 2425 t.Fatalf("did not get error for provider %s", test.provider) 2426 } 2427 2428 switch providerError := providerError.(type) { 2429 case getproviders.ErrProtocolNotSupported: 2430 if !providerError.Suggestion.Same(test.wantVersion) { 2431 t.Fatalf("wrong result\ngot: %s\nwant: %s\n", providerError.Suggestion, test.wantVersion) 2432 } 2433 default: 2434 t.Fatalf("wrong error type. Expected ErrProtocolNotSupported, got %T", err) 2435 } 2436 default: 2437 t.Fatalf("wrong error type. Expected InstallerError, got %T", err) 2438 } 2439 }) 2440 } 2441 } 2442 2443 // testServices starts up a local HTTP server running a fake provider registry 2444 // service and returns a service discovery object pre-configured to consider 2445 // the host "example.com" to be served by the fake registry service. 2446 // 2447 // The returned discovery object also knows the hostname "not.example.com" 2448 // which does not have a provider registry at all and "too-new.example.com" 2449 // which has a "providers.v99" service that is inoperable but could be useful 2450 // to test the error reporting for detecting an unsupported protocol version. 2451 // It also knows fails.example.com but it refers to an endpoint that doesn't 2452 // correctly speak HTTP, to simulate a protocol error. 2453 // 2454 // The second return value is a function to call at the end of a test function 2455 // to shut down the test server. After you call that function, the discovery 2456 // object becomes useless. 2457 func testServices(t *testing.T) (services *disco.Disco, baseURL string, cleanup func()) { 2458 server := httptest.NewServer(http.HandlerFunc(fakeRegistryHandler)) 2459 2460 services = disco.New() 2461 services.ForceHostServices(svchost.Hostname("example.com"), map[string]interface{}{ 2462 "providers.v1": server.URL + "/providers/v1/", 2463 }) 2464 services.ForceHostServices(svchost.Hostname("not.example.com"), map[string]interface{}{}) 2465 services.ForceHostServices(svchost.Hostname("too-new.example.com"), map[string]interface{}{ 2466 // This service doesn't actually work; it's here only to be 2467 // detected as "too new" by the discovery logic. 2468 "providers.v99": server.URL + "/providers/v99/", 2469 }) 2470 services.ForceHostServices(svchost.Hostname("fails.example.com"), map[string]interface{}{ 2471 "providers.v1": server.URL + "/fails-immediately/", 2472 }) 2473 2474 // We'll also permit registry.terraform.io here just because it's our 2475 // default and has some unique features that are not allowed on any other 2476 // hostname. It behaves the same as example.com, which should be preferred 2477 // if you're not testing something specific to the default registry in order 2478 // to ensure that most things are hostname-agnostic. 2479 services.ForceHostServices(svchost.Hostname("registry.terraform.io"), map[string]interface{}{ 2480 "providers.v1": server.URL + "/providers/v1/", 2481 }) 2482 2483 return services, server.URL, func() { 2484 server.Close() 2485 } 2486 } 2487 2488 // testRegistrySource is a wrapper around testServices that uses the created 2489 // discovery object to produce a Source instance that is ready to use with the 2490 // fake registry services. 2491 // 2492 // As with testServices, the second return value is a function to call at the end 2493 // of your test in order to shut down the test server. 2494 func testRegistrySource(t *testing.T) (source *getproviders.RegistrySource, baseURL string, cleanup func()) { 2495 services, baseURL, close := testServices(t) 2496 source = getproviders.NewRegistrySource(services) 2497 return source, baseURL, close 2498 } 2499 2500 func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) { 2501 path := req.URL.EscapedPath() 2502 if strings.HasPrefix(path, "/fails-immediately/") { 2503 // Here we take over the socket and just close it immediately, to 2504 // simulate one possible way a server might not be an HTTP server. 2505 hijacker, ok := resp.(http.Hijacker) 2506 if !ok { 2507 // Not hijackable, so we'll just fail normally. 2508 // If this happens, tests relying on this will fail. 2509 resp.WriteHeader(500) 2510 resp.Write([]byte(`cannot hijack`)) 2511 return 2512 } 2513 conn, _, err := hijacker.Hijack() 2514 if err != nil { 2515 resp.WriteHeader(500) 2516 resp.Write([]byte(`hijack failed`)) 2517 return 2518 } 2519 conn.Close() 2520 return 2521 } 2522 2523 if strings.HasPrefix(path, "/pkg/") { 2524 switch path { 2525 case "/pkg/awesomesauce/happycloud_1.2.0.zip": 2526 resp.Write([]byte("some zip file")) 2527 case "/pkg/awesomesauce/happycloud_1.2.0_SHA256SUMS": 2528 resp.Write([]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n")) 2529 case "/pkg/awesomesauce/happycloud_1.2.0_SHA256SUMS.sig": 2530 resp.Write([]byte("GPG signature")) 2531 default: 2532 resp.WriteHeader(404) 2533 resp.Write([]byte("unknown package file download")) 2534 } 2535 return 2536 } 2537 2538 if !strings.HasPrefix(path, "/providers/v1/") { 2539 resp.WriteHeader(404) 2540 resp.Write([]byte(`not a provider registry endpoint`)) 2541 return 2542 } 2543 2544 pathParts := strings.Split(path, "/")[3:] 2545 if len(pathParts) < 2 { 2546 resp.WriteHeader(404) 2547 resp.Write([]byte(`unexpected number of path parts`)) 2548 return 2549 } 2550 log.Printf("[TRACE] fake provider registry request for %#v", pathParts) 2551 if len(pathParts) == 2 { 2552 switch pathParts[0] + "/" + pathParts[1] { 2553 2554 case "-/legacy": 2555 // NOTE: This legacy lookup endpoint is specific to 2556 // registry.terraform.io and not expected to work on any other 2557 // registry host. 2558 resp.Header().Set("Content-Type", "application/json") 2559 resp.WriteHeader(200) 2560 resp.Write([]byte(`{"namespace":"legacycorp"}`)) 2561 2562 default: 2563 resp.WriteHeader(404) 2564 resp.Write([]byte(`unknown namespace or provider type for direct lookup`)) 2565 } 2566 } 2567 2568 if len(pathParts) < 3 { 2569 resp.WriteHeader(404) 2570 resp.Write([]byte(`unexpected number of path parts`)) 2571 return 2572 } 2573 2574 if pathParts[2] == "versions" { 2575 if len(pathParts) != 3 { 2576 resp.WriteHeader(404) 2577 resp.Write([]byte(`extraneous path parts`)) 2578 return 2579 } 2580 2581 switch pathParts[0] + "/" + pathParts[1] { 2582 case "awesomesauce/happycloud": 2583 resp.Header().Set("Content-Type", "application/json") 2584 resp.WriteHeader(200) 2585 // Note that these version numbers are intentionally misordered 2586 // so we can test that the client-side code places them in the 2587 // correct order (lowest precedence first). 2588 resp.Write([]byte(`{"versions":[{"version":"0.1.0","protocols":["1.0"]},{"version":"2.0.0","protocols":["99.0"]},{"version":"1.2.0","protocols":["5.0"]}, {"version":"1.0.0","protocols":["5.0"]}]}`)) 2589 case "weaksauce/unsupported-protocol": 2590 resp.Header().Set("Content-Type", "application/json") 2591 resp.WriteHeader(200) 2592 resp.Write([]byte(`{"versions":[{"version":"0.1.0","protocols":["0.1"]}]}`)) 2593 case "weaksauce/no-versions": 2594 resp.Header().Set("Content-Type", "application/json") 2595 resp.WriteHeader(200) 2596 resp.Write([]byte(`{"versions":[]}`)) 2597 default: 2598 resp.WriteHeader(404) 2599 resp.Write([]byte(`unknown namespace or provider type`)) 2600 } 2601 return 2602 } 2603 2604 if len(pathParts) == 6 && pathParts[3] == "download" { 2605 switch pathParts[0] + "/" + pathParts[1] { 2606 case "awesomesauce/happycloud": 2607 if pathParts[4] == "nonexist" { 2608 resp.WriteHeader(404) 2609 resp.Write([]byte(`unsupported OS`)) 2610 return 2611 } 2612 version := pathParts[2] 2613 body := map[string]interface{}{ 2614 "protocols": []string{"99.0"}, 2615 "os": pathParts[4], 2616 "arch": pathParts[5], 2617 "filename": "happycloud_" + version + ".zip", 2618 "shasum": "000000000000000000000000000000000000000000000000000000000000f00d", 2619 "download_url": "/pkg/awesomesauce/happycloud_" + version + ".zip", 2620 "shasums_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS", 2621 "shasums_signature_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS.sig", 2622 "signing_keys": map[string]interface{}{ 2623 "gpg_public_keys": []map[string]interface{}{ 2624 { 2625 "ascii_armor": getproviders.HashicorpPublicKey, 2626 }, 2627 }, 2628 }, 2629 } 2630 enc, err := json.Marshal(body) 2631 if err != nil { 2632 resp.WriteHeader(500) 2633 resp.Write([]byte("failed to encode body")) 2634 } 2635 resp.Header().Set("Content-Type", "application/json") 2636 resp.WriteHeader(200) 2637 resp.Write(enc) 2638 case "weaksauce/unsupported-protocol": 2639 var protocols []string 2640 version := pathParts[2] 2641 switch version { 2642 case "0.1.0": 2643 protocols = []string{"1.0"} 2644 case "2.0.0": 2645 protocols = []string{"99.0"} 2646 default: 2647 protocols = []string{"5.0"} 2648 } 2649 2650 body := map[string]interface{}{ 2651 "protocols": protocols, 2652 "os": pathParts[4], 2653 "arch": pathParts[5], 2654 "filename": "happycloud_" + version + ".zip", 2655 "shasum": "000000000000000000000000000000000000000000000000000000000000f00d", 2656 "download_url": "/pkg/awesomesauce/happycloud_" + version + ".zip", 2657 "shasums_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS", 2658 "shasums_signature_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS.sig", 2659 "signing_keys": map[string]interface{}{ 2660 "gpg_public_keys": []map[string]interface{}{ 2661 { 2662 "ascii_armor": getproviders.HashicorpPublicKey, 2663 }, 2664 }, 2665 }, 2666 } 2667 enc, err := json.Marshal(body) 2668 if err != nil { 2669 resp.WriteHeader(500) 2670 resp.Write([]byte("failed to encode body")) 2671 } 2672 resp.Header().Set("Content-Type", "application/json") 2673 resp.WriteHeader(200) 2674 resp.Write(enc) 2675 default: 2676 resp.WriteHeader(404) 2677 resp.Write([]byte(`unknown namespace/provider/version/architecture`)) 2678 } 2679 return 2680 } 2681 2682 resp.WriteHeader(404) 2683 resp.Write([]byte(`unrecognized path scheme`)) 2684 } 2685 2686 // In order to be able to compare the recorded temp dir paths, we need to 2687 // normalize the path to match what the installer would report. 2688 func tmpDir(t *testing.T) string { 2689 unlinked, err := filepath.EvalSymlinks(t.TempDir()) 2690 if err != nil { 2691 t.Fatal(err) 2692 } 2693 return filepath.Clean(unlinked) 2694 }