github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/providercache/installer_test.go (about) 1 package providercache 2 3 import ( 4 "context" 5 "encoding/json" 6 "io/ioutil" 7 "log" 8 "net/http" 9 "net/http/httptest" 10 "os" 11 "path/filepath" 12 "strings" 13 "testing" 14 15 "github.com/apparentlymart/go-versions/versions" 16 "github.com/apparentlymart/go-versions/versions/constraints" 17 "github.com/davecgh/go-spew/spew" 18 "github.com/google/go-cmp/cmp" 19 svchost "github.com/hashicorp/terraform-svchost" 20 "github.com/hashicorp/terraform-svchost/disco" 21 "github.com/cycloidio/terraform/addrs" 22 "github.com/cycloidio/terraform/depsfile" 23 "github.com/cycloidio/terraform/getproviders" 24 ) 25 26 func TestEnsureProviderVersions(t *testing.T) { 27 // This is a sort of hybrid between table-driven and imperative-style 28 // testing, because the overall sequence of steps is the same for all 29 // of the test cases but the setup and verification have enough different 30 // permutations that it ends up being more concise to express them as 31 // normal code. 32 type Test struct { 33 Source getproviders.Source 34 Prepare func(*testing.T, *Installer, *Dir) 35 LockFile string 36 Reqs getproviders.Requirements 37 Mode InstallMode 38 Check func(*testing.T, *Dir, *depsfile.Locks) 39 WantErr string 40 WantEvents func(*Installer, *Dir) map[addrs.Provider][]*testInstallerEventLogItem 41 } 42 43 // noProvider is just the zero value of addrs.Provider, which we're 44 // using in this test as the key for installer events that are not 45 // specific to a particular provider. 46 var noProvider addrs.Provider 47 beepProvider := addrs.MustParseProviderSourceString("example.com/foo/beep") 48 beepProviderDir := getproviders.PackageLocalDir("testdata/beep-provider") 49 fakePlatform := getproviders.Platform{OS: "bleep", Arch: "bloop"} 50 wrongPlatform := getproviders.Platform{OS: "wrong", Arch: "wrong"} 51 beepProviderHash := getproviders.HashScheme1.New("2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=") 52 terraformProvider := addrs.MustParseProviderSourceString("terraform.io/builtin/terraform") 53 54 tests := map[string]Test{ 55 "no dependencies": { 56 Mode: InstallNewProvidersOnly, 57 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 58 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 59 t.Errorf("unexpected cache directory entries\n%s", spew.Sdump(allCached)) 60 } 61 if allLocked := locks.AllProviders(); len(allLocked) != 0 { 62 t.Errorf("unexpected provider lock entries\n%s", spew.Sdump(allLocked)) 63 } 64 }, 65 WantEvents: func(*Installer, *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 66 return map[addrs.Provider][]*testInstallerEventLogItem{ 67 noProvider: { 68 { 69 Event: "PendingProviders", 70 Args: map[addrs.Provider]getproviders.VersionConstraints(nil), 71 }, 72 }, 73 } 74 }, 75 }, 76 "successful initial install of one provider": { 77 Source: getproviders.NewMockSource( 78 []getproviders.PackageMeta{ 79 { 80 Provider: beepProvider, 81 Version: getproviders.MustParseVersion("1.0.0"), 82 TargetPlatform: fakePlatform, 83 Location: beepProviderDir, 84 }, 85 { 86 Provider: beepProvider, 87 Version: getproviders.MustParseVersion("2.0.0"), 88 TargetPlatform: fakePlatform, 89 Location: beepProviderDir, 90 }, 91 { 92 Provider: beepProvider, 93 Version: getproviders.MustParseVersion("2.1.0"), 94 TargetPlatform: fakePlatform, 95 Location: beepProviderDir, 96 }, 97 }, 98 nil, 99 ), 100 Mode: InstallNewProvidersOnly, 101 Reqs: getproviders.Requirements{ 102 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 103 }, 104 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 105 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 106 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 107 } 108 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 109 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 110 } 111 112 gotLock := locks.Provider(beepProvider) 113 wantLock := depsfile.NewProviderLock( 114 beepProvider, 115 getproviders.MustParseVersion("2.1.0"), 116 getproviders.MustParseVersionConstraints(">= 2.0.0"), 117 []getproviders.Hash{beepProviderHash}, 118 ) 119 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 120 t.Errorf("wrong lock entry\n%s", diff) 121 } 122 123 gotEntry := dir.ProviderLatestVersion(beepProvider) 124 wantEntry := &CachedProvider{ 125 Provider: beepProvider, 126 Version: getproviders.MustParseVersion("2.1.0"), 127 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 128 } 129 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 130 t.Errorf("wrong cache entry\n%s", diff) 131 } 132 }, 133 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 134 return map[addrs.Provider][]*testInstallerEventLogItem{ 135 noProvider: { 136 { 137 Event: "PendingProviders", 138 Args: map[addrs.Provider]getproviders.VersionConstraints{ 139 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 140 }, 141 }, 142 { 143 Event: "ProvidersFetched", 144 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 145 beepProvider: nil, 146 }, 147 }, 148 }, 149 beepProvider: { 150 { 151 Event: "QueryPackagesBegin", 152 Provider: beepProvider, 153 Args: struct { 154 Constraints string 155 Locked bool 156 }{">= 2.0.0", false}, 157 }, 158 { 159 Event: "QueryPackagesSuccess", 160 Provider: beepProvider, 161 Args: "2.1.0", 162 }, 163 { 164 Event: "FetchPackageMeta", 165 Provider: beepProvider, 166 Args: "2.1.0", 167 }, 168 { 169 Event: "FetchPackageBegin", 170 Provider: beepProvider, 171 Args: struct { 172 Version string 173 Location getproviders.PackageLocation 174 }{"2.1.0", beepProviderDir}, 175 }, 176 { 177 Event: "FetchPackageSuccess", 178 Provider: beepProvider, 179 Args: struct { 180 Version string 181 LocalDir string 182 AuthResult string 183 }{ 184 "2.1.0", 185 filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 186 "unauthenticated", 187 }, 188 }, 189 }, 190 } 191 }, 192 }, 193 "successful initial install of one provider through a cold global cache": { 194 Source: getproviders.NewMockSource( 195 []getproviders.PackageMeta{ 196 { 197 Provider: beepProvider, 198 Version: getproviders.MustParseVersion("2.0.0"), 199 TargetPlatform: fakePlatform, 200 Location: beepProviderDir, 201 }, 202 { 203 Provider: beepProvider, 204 Version: getproviders.MustParseVersion("2.1.0"), 205 TargetPlatform: fakePlatform, 206 Location: beepProviderDir, 207 }, 208 }, 209 nil, 210 ), 211 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 212 globalCacheDirPath := tmpDir(t) 213 globalCacheDir := NewDirWithPlatform(globalCacheDirPath, fakePlatform) 214 inst.SetGlobalCacheDir(globalCacheDir) 215 }, 216 Mode: InstallNewProvidersOnly, 217 Reqs: getproviders.Requirements{ 218 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 219 }, 220 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 221 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 222 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 223 } 224 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 225 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 226 } 227 228 gotLock := locks.Provider(beepProvider) 229 wantLock := depsfile.NewProviderLock( 230 beepProvider, 231 getproviders.MustParseVersion("2.1.0"), 232 getproviders.MustParseVersionConstraints(">= 2.0.0"), 233 []getproviders.Hash{beepProviderHash}, 234 ) 235 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 236 t.Errorf("wrong lock entry\n%s", diff) 237 } 238 239 gotEntry := dir.ProviderLatestVersion(beepProvider) 240 wantEntry := &CachedProvider{ 241 Provider: beepProvider, 242 Version: getproviders.MustParseVersion("2.1.0"), 243 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 244 } 245 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 246 t.Errorf("wrong cache entry\n%s", diff) 247 } 248 }, 249 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 250 return map[addrs.Provider][]*testInstallerEventLogItem{ 251 noProvider: { 252 { 253 Event: "PendingProviders", 254 Args: map[addrs.Provider]getproviders.VersionConstraints{ 255 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 256 }, 257 }, 258 { 259 Event: "ProvidersFetched", 260 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 261 beepProvider: nil, 262 }, 263 }, 264 }, 265 beepProvider: { 266 { 267 Event: "QueryPackagesBegin", 268 Provider: beepProvider, 269 Args: struct { 270 Constraints string 271 Locked bool 272 }{">= 2.0.0", false}, 273 }, 274 { 275 Event: "QueryPackagesSuccess", 276 Provider: beepProvider, 277 Args: "2.1.0", 278 }, 279 { 280 Event: "FetchPackageMeta", 281 Provider: beepProvider, 282 Args: "2.1.0", 283 }, 284 { 285 Event: "FetchPackageBegin", 286 Provider: beepProvider, 287 Args: struct { 288 Version string 289 Location getproviders.PackageLocation 290 }{"2.1.0", beepProviderDir}, 291 }, 292 { 293 Event: "FetchPackageSuccess", 294 Provider: beepProvider, 295 Args: struct { 296 Version string 297 LocalDir string 298 AuthResult string 299 }{ 300 "2.1.0", 301 // NOTE: With global cache enabled, the fetch 302 // goes into the global cache dir and 303 // we then to it from the local cache dir. 304 filepath.Join(inst.globalCacheDir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 305 "unauthenticated", 306 }, 307 }, 308 }, 309 } 310 }, 311 }, 312 "successful initial install of one provider through a warm global cache": { 313 Source: getproviders.NewMockSource( 314 []getproviders.PackageMeta{ 315 { 316 Provider: beepProvider, 317 Version: getproviders.MustParseVersion("2.0.0"), 318 TargetPlatform: fakePlatform, 319 Location: beepProviderDir, 320 }, 321 { 322 Provider: beepProvider, 323 Version: getproviders.MustParseVersion("2.1.0"), 324 TargetPlatform: fakePlatform, 325 Location: beepProviderDir, 326 }, 327 }, 328 nil, 329 ), 330 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 331 globalCacheDirPath := tmpDir(t) 332 globalCacheDir := NewDirWithPlatform(globalCacheDirPath, fakePlatform) 333 _, err := globalCacheDir.InstallPackage( 334 context.Background(), 335 getproviders.PackageMeta{ 336 Provider: beepProvider, 337 Version: getproviders.MustParseVersion("2.1.0"), 338 TargetPlatform: fakePlatform, 339 Location: beepProviderDir, 340 }, 341 nil, 342 ) 343 if err != nil { 344 t.Fatalf("failed to populate global cache: %s", err) 345 } 346 inst.SetGlobalCacheDir(globalCacheDir) 347 }, 348 Mode: InstallNewProvidersOnly, 349 Reqs: getproviders.Requirements{ 350 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 351 }, 352 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 353 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 354 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 355 } 356 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 357 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 358 } 359 360 gotLock := locks.Provider(beepProvider) 361 wantLock := depsfile.NewProviderLock( 362 beepProvider, 363 getproviders.MustParseVersion("2.1.0"), 364 getproviders.MustParseVersionConstraints(">= 2.0.0"), 365 []getproviders.Hash{beepProviderHash}, 366 ) 367 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 368 t.Errorf("wrong lock entry\n%s", diff) 369 } 370 371 gotEntry := dir.ProviderLatestVersion(beepProvider) 372 wantEntry := &CachedProvider{ 373 Provider: beepProvider, 374 Version: getproviders.MustParseVersion("2.1.0"), 375 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 376 } 377 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 378 t.Errorf("wrong cache entry\n%s", diff) 379 } 380 }, 381 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 382 return map[addrs.Provider][]*testInstallerEventLogItem{ 383 noProvider: { 384 { 385 Event: "PendingProviders", 386 Args: map[addrs.Provider]getproviders.VersionConstraints{ 387 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 388 }, 389 }, 390 }, 391 beepProvider: { 392 { 393 Event: "QueryPackagesBegin", 394 Provider: beepProvider, 395 Args: struct { 396 Constraints string 397 Locked bool 398 }{">= 2.0.0", false}, 399 }, 400 { 401 Event: "QueryPackagesSuccess", 402 Provider: beepProvider, 403 Args: "2.1.0", 404 }, 405 { 406 Event: "LinkFromCacheBegin", 407 Provider: beepProvider, 408 Args: struct { 409 Version string 410 CacheRoot string 411 }{ 412 "2.1.0", 413 inst.globalCacheDir.BasePath(), 414 }, 415 }, 416 { 417 Event: "LinkFromCacheSuccess", 418 Provider: beepProvider, 419 Args: struct { 420 Version string 421 LocalDir string 422 }{ 423 "2.1.0", 424 filepath.Join(dir.BasePath(), "/example.com/foo/beep/2.1.0/bleep_bloop"), 425 }, 426 }, 427 }, 428 } 429 }, 430 }, 431 "successful reinstall of one previously-locked provider": { 432 Source: getproviders.NewMockSource( 433 []getproviders.PackageMeta{ 434 { 435 Provider: beepProvider, 436 Version: getproviders.MustParseVersion("1.0.0"), 437 TargetPlatform: fakePlatform, 438 Location: beepProviderDir, 439 }, 440 { 441 Provider: beepProvider, 442 Version: getproviders.MustParseVersion("2.0.0"), 443 TargetPlatform: fakePlatform, 444 Location: beepProviderDir, 445 }, 446 { 447 Provider: beepProvider, 448 Version: getproviders.MustParseVersion("2.1.0"), 449 TargetPlatform: fakePlatform, 450 Location: beepProviderDir, 451 }, 452 }, 453 nil, 454 ), 455 LockFile: ` 456 provider "example.com/foo/beep" { 457 version = "2.0.0" 458 constraints = ">= 2.0.0" 459 hashes = [ 460 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 461 ] 462 } 463 `, 464 Mode: InstallNewProvidersOnly, 465 Reqs: getproviders.Requirements{ 466 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 467 }, 468 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 469 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 470 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 471 } 472 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 473 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 474 } 475 476 gotLock := locks.Provider(beepProvider) 477 wantLock := depsfile.NewProviderLock( 478 beepProvider, 479 getproviders.MustParseVersion("2.0.0"), 480 getproviders.MustParseVersionConstraints(">= 2.0.0"), 481 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 482 ) 483 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 484 t.Errorf("wrong lock entry\n%s", diff) 485 } 486 487 gotEntry := dir.ProviderLatestVersion(beepProvider) 488 wantEntry := &CachedProvider{ 489 Provider: beepProvider, 490 Version: getproviders.MustParseVersion("2.0.0"), 491 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.0.0/bleep_bloop"), 492 } 493 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 494 t.Errorf("wrong cache entry\n%s", diff) 495 } 496 }, 497 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 498 return map[addrs.Provider][]*testInstallerEventLogItem{ 499 noProvider: { 500 { 501 Event: "PendingProviders", 502 Args: map[addrs.Provider]getproviders.VersionConstraints{ 503 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 504 }, 505 }, 506 { 507 Event: "ProvidersFetched", 508 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 509 beepProvider: nil, 510 }, 511 }, 512 }, 513 beepProvider: { 514 { 515 Event: "QueryPackagesBegin", 516 Provider: beepProvider, 517 Args: struct { 518 Constraints string 519 Locked bool 520 }{">= 2.0.0", true}, 521 }, 522 { 523 Event: "QueryPackagesSuccess", 524 Provider: beepProvider, 525 Args: "2.0.0", 526 }, 527 { 528 Event: "FetchPackageMeta", 529 Provider: beepProvider, 530 Args: "2.0.0", 531 }, 532 { 533 Event: "FetchPackageBegin", 534 Provider: beepProvider, 535 Args: struct { 536 Version string 537 Location getproviders.PackageLocation 538 }{"2.0.0", beepProviderDir}, 539 }, 540 { 541 Event: "FetchPackageSuccess", 542 Provider: beepProvider, 543 Args: struct { 544 Version string 545 LocalDir string 546 AuthResult string 547 }{ 548 "2.0.0", 549 filepath.Join(dir.BasePath(), "example.com/foo/beep/2.0.0/bleep_bloop"), 550 "unauthenticated", 551 }, 552 }, 553 }, 554 } 555 }, 556 }, 557 "skipped install of one previously-locked and installed provider": { 558 Source: getproviders.NewMockSource( 559 []getproviders.PackageMeta{ 560 { 561 Provider: beepProvider, 562 Version: getproviders.MustParseVersion("2.0.0"), 563 TargetPlatform: fakePlatform, 564 Location: beepProviderDir, 565 }, 566 }, 567 nil, 568 ), 569 LockFile: ` 570 provider "example.com/foo/beep" { 571 version = "2.0.0" 572 constraints = ">= 2.0.0" 573 hashes = [ 574 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 575 ] 576 } 577 `, 578 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 579 _, err := dir.InstallPackage( 580 context.Background(), 581 getproviders.PackageMeta{ 582 Provider: beepProvider, 583 Version: getproviders.MustParseVersion("2.0.0"), 584 TargetPlatform: fakePlatform, 585 Location: beepProviderDir, 586 }, 587 nil, 588 ) 589 if err != nil { 590 t.Fatalf("installation to the test dir failed: %s", err) 591 } 592 }, 593 Mode: InstallNewProvidersOnly, 594 Reqs: getproviders.Requirements{ 595 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 596 }, 597 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 598 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 599 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 600 } 601 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 602 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 603 } 604 605 gotLock := locks.Provider(beepProvider) 606 wantLock := depsfile.NewProviderLock( 607 beepProvider, 608 getproviders.MustParseVersion("2.0.0"), 609 getproviders.MustParseVersionConstraints(">= 2.0.0"), 610 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 611 ) 612 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 613 t.Errorf("wrong lock entry\n%s", diff) 614 } 615 616 gotEntry := dir.ProviderLatestVersion(beepProvider) 617 wantEntry := &CachedProvider{ 618 Provider: beepProvider, 619 Version: getproviders.MustParseVersion("2.0.0"), 620 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.0.0/bleep_bloop"), 621 } 622 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 623 t.Errorf("wrong cache entry\n%s", diff) 624 } 625 }, 626 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 627 return map[addrs.Provider][]*testInstallerEventLogItem{ 628 noProvider: { 629 { 630 Event: "PendingProviders", 631 Args: map[addrs.Provider]getproviders.VersionConstraints{ 632 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 633 }, 634 }, 635 }, 636 beepProvider: { 637 { 638 Event: "QueryPackagesBegin", 639 Provider: beepProvider, 640 Args: struct { 641 Constraints string 642 Locked bool 643 }{">= 2.0.0", true}, 644 }, 645 { 646 Event: "QueryPackagesSuccess", 647 Provider: beepProvider, 648 Args: "2.0.0", 649 }, 650 { 651 Event: "ProviderAlreadyInstalled", 652 Provider: beepProvider, 653 Args: versions.Version{Major: 2, Minor: 0, Patch: 0}, 654 }, 655 }, 656 } 657 }, 658 }, 659 "successful upgrade of one previously-locked provider": { 660 Source: getproviders.NewMockSource( 661 []getproviders.PackageMeta{ 662 { 663 Provider: beepProvider, 664 Version: getproviders.MustParseVersion("1.0.0"), 665 TargetPlatform: fakePlatform, 666 Location: beepProviderDir, 667 }, 668 { 669 Provider: beepProvider, 670 Version: getproviders.MustParseVersion("2.0.0"), 671 TargetPlatform: fakePlatform, 672 Location: beepProviderDir, 673 }, 674 { 675 Provider: beepProvider, 676 Version: getproviders.MustParseVersion("2.1.0"), 677 TargetPlatform: fakePlatform, 678 Location: beepProviderDir, 679 }, 680 }, 681 nil, 682 ), 683 LockFile: ` 684 provider "example.com/foo/beep" { 685 version = "2.0.0" 686 constraints = ">= 2.0.0" 687 hashes = [ 688 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 689 ] 690 } 691 `, 692 Mode: InstallUpgrades, 693 Reqs: getproviders.Requirements{ 694 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 695 }, 696 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 697 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 698 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 699 } 700 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 701 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 702 } 703 704 gotLock := locks.Provider(beepProvider) 705 wantLock := depsfile.NewProviderLock( 706 beepProvider, 707 getproviders.MustParseVersion("2.1.0"), 708 getproviders.MustParseVersionConstraints(">= 2.0.0"), 709 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 710 ) 711 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 712 t.Errorf("wrong lock entry\n%s", diff) 713 } 714 715 gotEntry := dir.ProviderLatestVersion(beepProvider) 716 wantEntry := &CachedProvider{ 717 Provider: beepProvider, 718 Version: getproviders.MustParseVersion("2.1.0"), 719 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 720 } 721 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 722 t.Errorf("wrong cache entry\n%s", diff) 723 } 724 }, 725 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 726 return map[addrs.Provider][]*testInstallerEventLogItem{ 727 noProvider: { 728 { 729 Event: "PendingProviders", 730 Args: map[addrs.Provider]getproviders.VersionConstraints{ 731 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 732 }, 733 }, 734 { 735 Event: "ProvidersFetched", 736 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 737 beepProvider: nil, 738 }, 739 }, 740 }, 741 beepProvider: { 742 { 743 Event: "QueryPackagesBegin", 744 Provider: beepProvider, 745 Args: struct { 746 Constraints string 747 Locked bool 748 }{">= 2.0.0", false}, 749 }, 750 { 751 Event: "QueryPackagesSuccess", 752 Provider: beepProvider, 753 Args: "2.1.0", 754 }, 755 { 756 Event: "FetchPackageMeta", 757 Provider: beepProvider, 758 Args: "2.1.0", 759 }, 760 { 761 Event: "FetchPackageBegin", 762 Provider: beepProvider, 763 Args: struct { 764 Version string 765 Location getproviders.PackageLocation 766 }{"2.1.0", beepProviderDir}, 767 }, 768 { 769 Event: "FetchPackageSuccess", 770 Provider: beepProvider, 771 Args: struct { 772 Version string 773 LocalDir string 774 AuthResult string 775 }{ 776 "2.1.0", 777 filepath.Join(dir.BasePath(), "example.com/foo/beep/2.1.0/bleep_bloop"), 778 "unauthenticated", 779 }, 780 }, 781 }, 782 } 783 }, 784 }, 785 "successful install of a built-in provider": { 786 Source: getproviders.NewMockSource( 787 []getproviders.PackageMeta{}, 788 nil, 789 ), 790 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 791 inst.SetBuiltInProviderTypes([]string{"terraform"}) 792 }, 793 Mode: InstallNewProvidersOnly, 794 Reqs: getproviders.Requirements{ 795 terraformProvider: nil, 796 }, 797 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 798 // Built-in providers are neither included in the cache 799 // directory nor mentioned in the lock file, because they 800 // are compiled directly into the Terraform executable. 801 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 802 t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached)) 803 } 804 if allLocked := locks.AllProviders(); len(allLocked) != 0 { 805 t.Errorf("wrong number of provider lock entries; want none\n%s", spew.Sdump(allLocked)) 806 } 807 }, 808 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 809 return map[addrs.Provider][]*testInstallerEventLogItem{ 810 noProvider: { 811 { 812 Event: "PendingProviders", 813 Args: map[addrs.Provider]getproviders.VersionConstraints{ 814 terraformProvider: constraints.IntersectionSpec(nil), 815 }, 816 }, 817 }, 818 terraformProvider: { 819 { 820 Event: "BuiltInProviderAvailable", 821 Provider: terraformProvider, 822 }, 823 }, 824 } 825 }, 826 }, 827 "remove no-longer-needed provider from lock file": { 828 Source: getproviders.NewMockSource( 829 []getproviders.PackageMeta{ 830 { 831 Provider: beepProvider, 832 Version: getproviders.MustParseVersion("1.0.0"), 833 TargetPlatform: fakePlatform, 834 Location: beepProviderDir, 835 }, 836 }, 837 nil, 838 ), 839 LockFile: ` 840 provider "example.com/foo/beep" { 841 version = "1.0.0" 842 constraints = ">= 1.0.0" 843 hashes = [ 844 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 845 ] 846 } 847 provider "example.com/foo/obsolete" { 848 version = "2.0.0" 849 constraints = ">= 2.0.0" 850 hashes = [ 851 "no:irrelevant", 852 ] 853 } 854 `, 855 Mode: InstallNewProvidersOnly, 856 Reqs: getproviders.Requirements{ 857 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 858 }, 859 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 860 if allCached := dir.AllAvailablePackages(); len(allCached) != 1 { 861 t.Errorf("wrong number of cache directory entries; want only one\n%s", spew.Sdump(allCached)) 862 } 863 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 864 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 865 } 866 867 gotLock := locks.Provider(beepProvider) 868 wantLock := depsfile.NewProviderLock( 869 beepProvider, 870 getproviders.MustParseVersion("1.0.0"), 871 getproviders.MustParseVersionConstraints(">= 1.0.0"), 872 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 873 ) 874 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 875 t.Errorf("wrong lock entry\n%s", diff) 876 } 877 878 gotEntry := dir.ProviderLatestVersion(beepProvider) 879 wantEntry := &CachedProvider{ 880 Provider: beepProvider, 881 Version: getproviders.MustParseVersion("1.0.0"), 882 PackageDir: filepath.Join(dir.BasePath(), "example.com/foo/beep/1.0.0/bleep_bloop"), 883 } 884 if diff := cmp.Diff(wantEntry, gotEntry); diff != "" { 885 t.Errorf("wrong cache entry\n%s", diff) 886 } 887 }, 888 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 889 return map[addrs.Provider][]*testInstallerEventLogItem{ 890 noProvider: { 891 { 892 Event: "PendingProviders", 893 Args: map[addrs.Provider]getproviders.VersionConstraints{ 894 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 895 }, 896 }, 897 { 898 Event: "ProvidersFetched", 899 Args: map[addrs.Provider]*getproviders.PackageAuthenticationResult{ 900 beepProvider: nil, 901 }, 902 }, 903 }, 904 // Note: intentionally no entries for example.com/foo/obsolete 905 // here, because it's no longer needed and therefore not 906 // installed. 907 beepProvider: { 908 { 909 Event: "QueryPackagesBegin", 910 Provider: beepProvider, 911 Args: struct { 912 Constraints string 913 Locked bool 914 }{">= 1.0.0", true}, 915 }, 916 { 917 Event: "QueryPackagesSuccess", 918 Provider: beepProvider, 919 Args: "1.0.0", 920 }, 921 { 922 Event: "FetchPackageMeta", 923 Provider: beepProvider, 924 Args: "1.0.0", 925 }, 926 { 927 Event: "FetchPackageBegin", 928 Provider: beepProvider, 929 Args: struct { 930 Version string 931 Location getproviders.PackageLocation 932 }{"1.0.0", beepProviderDir}, 933 }, 934 { 935 Event: "FetchPackageSuccess", 936 Provider: beepProvider, 937 Args: struct { 938 Version string 939 LocalDir string 940 AuthResult string 941 }{ 942 "1.0.0", 943 filepath.Join(dir.BasePath(), "example.com/foo/beep/1.0.0/bleep_bloop"), 944 "unauthenticated", 945 }, 946 }, 947 }, 948 } 949 }, 950 }, 951 "failed install of a non-existing built-in provider": { 952 Source: getproviders.NewMockSource( 953 []getproviders.PackageMeta{}, 954 nil, 955 ), 956 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 957 // NOTE: We're intentionally not calling 958 // inst.SetBuiltInProviderTypes to make the "terraform" 959 // built-in provider available here, so requests for it 960 // should fail. 961 }, 962 Mode: InstallNewProvidersOnly, 963 Reqs: getproviders.Requirements{ 964 terraformProvider: nil, 965 }, 966 WantErr: `some providers could not be installed: 967 - terraform.io/builtin/terraform: this Terraform release has no built-in provider named "terraform"`, 968 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 969 return map[addrs.Provider][]*testInstallerEventLogItem{ 970 noProvider: { 971 { 972 Event: "PendingProviders", 973 Args: map[addrs.Provider]getproviders.VersionConstraints{ 974 terraformProvider: constraints.IntersectionSpec(nil), 975 }, 976 }, 977 }, 978 terraformProvider: { 979 { 980 Event: "BuiltInProviderFailure", 981 Provider: terraformProvider, 982 Args: `this Terraform release has no built-in provider named "terraform"`, 983 }, 984 }, 985 } 986 }, 987 }, 988 "failed install when a built-in provider has a version constraint": { 989 Source: getproviders.NewMockSource( 990 []getproviders.PackageMeta{}, 991 nil, 992 ), 993 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 994 inst.SetBuiltInProviderTypes([]string{"terraform"}) 995 }, 996 Mode: InstallNewProvidersOnly, 997 Reqs: getproviders.Requirements{ 998 terraformProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 999 }, 1000 WantErr: `some providers could not be installed: 1001 - terraform.io/builtin/terraform: built-in providers do not support explicit version constraints`, 1002 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1003 return map[addrs.Provider][]*testInstallerEventLogItem{ 1004 noProvider: { 1005 { 1006 Event: "PendingProviders", 1007 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1008 terraformProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1009 }, 1010 }, 1011 }, 1012 terraformProvider: { 1013 { 1014 Event: "BuiltInProviderFailure", 1015 Provider: terraformProvider, 1016 Args: `built-in providers do not support explicit version constraints`, 1017 }, 1018 }, 1019 } 1020 }, 1021 }, 1022 "locked version is excluded by new version constraint": { 1023 Source: getproviders.NewMockSource( 1024 []getproviders.PackageMeta{ 1025 { 1026 Provider: beepProvider, 1027 Version: getproviders.MustParseVersion("1.0.0"), 1028 TargetPlatform: fakePlatform, 1029 Location: beepProviderDir, 1030 }, 1031 { 1032 Provider: beepProvider, 1033 Version: getproviders.MustParseVersion("2.0.0"), 1034 TargetPlatform: fakePlatform, 1035 Location: beepProviderDir, 1036 }, 1037 }, 1038 nil, 1039 ), 1040 LockFile: ` 1041 provider "example.com/foo/beep" { 1042 version = "1.0.0" 1043 constraints = ">= 1.0.0" 1044 hashes = [ 1045 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 1046 ] 1047 } 1048 `, 1049 Mode: InstallNewProvidersOnly, 1050 Reqs: getproviders.Requirements{ 1051 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1052 }, 1053 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1054 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 1055 t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached)) 1056 } 1057 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 1058 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 1059 } 1060 1061 gotLock := locks.Provider(beepProvider) 1062 wantLock := depsfile.NewProviderLock( 1063 beepProvider, 1064 getproviders.MustParseVersion("1.0.0"), 1065 getproviders.MustParseVersionConstraints(">= 1.0.0"), 1066 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1067 ) 1068 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 1069 t.Errorf("wrong lock entry\n%s", diff) 1070 } 1071 }, 1072 WantErr: `some providers could not be installed: 1073 - 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`, 1074 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1075 return map[addrs.Provider][]*testInstallerEventLogItem{ 1076 noProvider: { 1077 { 1078 Event: "PendingProviders", 1079 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1080 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1081 }, 1082 }, 1083 }, 1084 beepProvider: { 1085 { 1086 Event: "QueryPackagesBegin", 1087 Provider: beepProvider, 1088 Args: struct { 1089 Constraints string 1090 Locked bool 1091 }{">= 2.0.0", true}, 1092 }, 1093 { 1094 Event: "QueryPackagesFailure", 1095 Provider: beepProvider, 1096 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`, 1097 }, 1098 }, 1099 } 1100 }, 1101 }, 1102 "locked version is no longer available": { 1103 Source: getproviders.NewMockSource( 1104 []getproviders.PackageMeta{ 1105 { 1106 Provider: beepProvider, 1107 Version: getproviders.MustParseVersion("1.0.0"), 1108 TargetPlatform: fakePlatform, 1109 Location: beepProviderDir, 1110 }, 1111 { 1112 Provider: beepProvider, 1113 Version: getproviders.MustParseVersion("2.0.0"), 1114 TargetPlatform: fakePlatform, 1115 Location: beepProviderDir, 1116 }, 1117 }, 1118 nil, 1119 ), 1120 LockFile: ` 1121 provider "example.com/foo/beep" { 1122 version = "1.2.0" 1123 constraints = ">= 1.0.0" 1124 hashes = [ 1125 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 1126 ] 1127 } 1128 `, 1129 Mode: InstallNewProvidersOnly, 1130 Reqs: getproviders.Requirements{ 1131 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1132 }, 1133 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1134 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 1135 t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached)) 1136 } 1137 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 1138 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 1139 } 1140 1141 gotLock := locks.Provider(beepProvider) 1142 wantLock := depsfile.NewProviderLock( 1143 beepProvider, 1144 getproviders.MustParseVersion("1.2.0"), 1145 getproviders.MustParseVersionConstraints(">= 1.0.0"), 1146 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1147 ) 1148 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 1149 t.Errorf("wrong lock entry\n%s", diff) 1150 } 1151 }, 1152 WantErr: `some providers could not be installed: 1153 - example.com/foo/beep: the previously-selected version 1.2.0 is no longer available`, 1154 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1155 return map[addrs.Provider][]*testInstallerEventLogItem{ 1156 noProvider: { 1157 { 1158 Event: "PendingProviders", 1159 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1160 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1161 }, 1162 }, 1163 }, 1164 beepProvider: { 1165 { 1166 Event: "QueryPackagesBegin", 1167 Provider: beepProvider, 1168 Args: struct { 1169 Constraints string 1170 Locked bool 1171 }{">= 1.0.0", true}, 1172 }, 1173 { 1174 Event: "QueryPackagesFailure", 1175 Provider: beepProvider, 1176 Args: `the previously-selected version 1.2.0 is no longer available`, 1177 }, 1178 }, 1179 } 1180 }, 1181 }, 1182 "no versions match the version constraint": { 1183 Source: getproviders.NewMockSource( 1184 []getproviders.PackageMeta{ 1185 { 1186 Provider: beepProvider, 1187 Version: getproviders.MustParseVersion("1.0.0"), 1188 TargetPlatform: fakePlatform, 1189 Location: beepProviderDir, 1190 }, 1191 }, 1192 nil, 1193 ), 1194 Mode: InstallNewProvidersOnly, 1195 Reqs: getproviders.Requirements{ 1196 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1197 }, 1198 WantErr: `some providers could not be installed: 1199 - example.com/foo/beep: no available releases match the given constraints >= 2.0.0`, 1200 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1201 return map[addrs.Provider][]*testInstallerEventLogItem{ 1202 noProvider: { 1203 { 1204 Event: "PendingProviders", 1205 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1206 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1207 }, 1208 }, 1209 }, 1210 beepProvider: { 1211 { 1212 Event: "QueryPackagesBegin", 1213 Provider: beepProvider, 1214 Args: struct { 1215 Constraints string 1216 Locked bool 1217 }{">= 2.0.0", false}, 1218 }, 1219 { 1220 Event: "QueryPackagesFailure", 1221 Provider: beepProvider, 1222 Args: `no available releases match the given constraints >= 2.0.0`, 1223 }, 1224 }, 1225 } 1226 }, 1227 }, 1228 "version exists but doesn't support the current platform": { 1229 Source: getproviders.NewMockSource( 1230 []getproviders.PackageMeta{ 1231 { 1232 Provider: beepProvider, 1233 Version: getproviders.MustParseVersion("1.0.0"), 1234 TargetPlatform: wrongPlatform, 1235 Location: beepProviderDir, 1236 }, 1237 }, 1238 nil, 1239 ), 1240 Mode: InstallNewProvidersOnly, 1241 Reqs: getproviders.Requirements{ 1242 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1243 }, 1244 WantErr: `some providers could not be installed: 1245 - example.com/foo/beep: provider example.com/foo/beep 1.0.0 is not available for bleep_bloop`, 1246 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1247 return map[addrs.Provider][]*testInstallerEventLogItem{ 1248 noProvider: { 1249 { 1250 Event: "PendingProviders", 1251 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1252 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1253 }, 1254 }, 1255 }, 1256 beepProvider: { 1257 { 1258 Event: "QueryPackagesBegin", 1259 Provider: beepProvider, 1260 Args: struct { 1261 Constraints string 1262 Locked bool 1263 }{">= 1.0.0", false}, 1264 }, 1265 { 1266 Event: "QueryPackagesSuccess", 1267 Provider: beepProvider, 1268 Args: "1.0.0", 1269 }, 1270 { 1271 Event: "FetchPackageMeta", 1272 Provider: beepProvider, 1273 Args: "1.0.0", 1274 }, 1275 { 1276 Event: "FetchPackageFailure", 1277 Provider: beepProvider, 1278 Args: struct { 1279 Version string 1280 Error string 1281 }{ 1282 "1.0.0", 1283 "provider example.com/foo/beep 1.0.0 is not available for bleep_bloop", 1284 }, 1285 }, 1286 }, 1287 } 1288 }, 1289 }, 1290 "available package doesn't match locked hash": { 1291 Source: getproviders.NewMockSource( 1292 []getproviders.PackageMeta{ 1293 { 1294 Provider: beepProvider, 1295 Version: getproviders.MustParseVersion("1.0.0"), 1296 TargetPlatform: fakePlatform, 1297 Location: beepProviderDir, 1298 }, 1299 }, 1300 nil, 1301 ), 1302 LockFile: ` 1303 provider "example.com/foo/beep" { 1304 version = "1.0.0" 1305 constraints = ">= 1.0.0" 1306 hashes = [ 1307 "h1:does-not-match", 1308 ] 1309 } 1310 `, 1311 Mode: InstallNewProvidersOnly, 1312 Reqs: getproviders.Requirements{ 1313 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1314 }, 1315 WantErr: `some providers could not be installed: 1316 - 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)`, 1317 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1318 return map[addrs.Provider][]*testInstallerEventLogItem{ 1319 noProvider: { 1320 { 1321 Event: "PendingProviders", 1322 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1323 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1324 }, 1325 }, 1326 }, 1327 beepProvider: { 1328 { 1329 Event: "QueryPackagesBegin", 1330 Provider: beepProvider, 1331 Args: struct { 1332 Constraints string 1333 Locked bool 1334 }{">= 1.0.0", true}, 1335 }, 1336 { 1337 Event: "QueryPackagesSuccess", 1338 Provider: beepProvider, 1339 Args: "1.0.0", 1340 }, 1341 { 1342 Event: "FetchPackageMeta", 1343 Provider: beepProvider, 1344 Args: "1.0.0", 1345 }, 1346 { 1347 Event: "FetchPackageBegin", 1348 Provider: beepProvider, 1349 Args: struct { 1350 Version string 1351 Location getproviders.PackageLocation 1352 }{"1.0.0", beepProviderDir}, 1353 }, 1354 { 1355 Event: "FetchPackageFailure", 1356 Provider: beepProvider, 1357 Args: struct { 1358 Version string 1359 Error string 1360 }{ 1361 "1.0.0", 1362 `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)`, 1363 }, 1364 }, 1365 }, 1366 } 1367 }, 1368 }, 1369 } 1370 1371 ctx := context.Background() 1372 1373 for name, test := range tests { 1374 t.Run(name, func(t *testing.T) { 1375 if test.Check == nil && test.WantEvents == nil && test.WantErr == "" { 1376 t.Fatalf("invalid test: must set at least one of Check, WantEvents, or WantErr") 1377 } 1378 1379 outputDir := NewDirWithPlatform(tmpDir(t), fakePlatform) 1380 source := test.Source 1381 if source == nil { 1382 source = getproviders.NewMockSource(nil, nil) 1383 } 1384 inst := NewInstaller(outputDir, source) 1385 if test.Prepare != nil { 1386 test.Prepare(t, inst, outputDir) 1387 } 1388 1389 locks, lockDiags := depsfile.LoadLocksFromBytes([]byte(test.LockFile), "test.lock.hcl") 1390 if lockDiags.HasErrors() { 1391 t.Fatalf("invalid lock file: %s", lockDiags.Err().Error()) 1392 } 1393 1394 providerEvents := make(map[addrs.Provider][]*testInstallerEventLogItem) 1395 eventsCh := make(chan *testInstallerEventLogItem) 1396 var newLocks *depsfile.Locks 1397 var instErr error 1398 go func(ch chan *testInstallerEventLogItem) { 1399 events := installerLogEventsForTests(ch) 1400 ctx := events.OnContext(ctx) 1401 newLocks, instErr = inst.EnsureProviderVersions(ctx, locks, test.Reqs, test.Mode) 1402 close(eventsCh) // exits the event loop below 1403 }(eventsCh) 1404 for evt := range eventsCh { 1405 // We do the event collection in the main goroutine, rather than 1406 // running the installer itself in the main goroutine, so that 1407 // we can safely t.Log in here without violating the testing.T 1408 // usage rules. 1409 if evt.Provider == (addrs.Provider{}) { 1410 t.Logf("%s(%s)", evt.Event, spew.Sdump(evt.Args)) 1411 } else { 1412 t.Logf("%s: %s(%s)", evt.Provider, evt.Event, spew.Sdump(evt.Args)) 1413 } 1414 providerEvents[evt.Provider] = append(providerEvents[evt.Provider], evt) 1415 } 1416 1417 if test.WantErr != "" { 1418 if instErr == nil { 1419 t.Errorf("succeeded; want error\nwant: %s", test.WantErr) 1420 } else if got, want := instErr.Error(), test.WantErr; got != want { 1421 t.Errorf("wrong error\ngot: %s\nwant: %s", got, want) 1422 } 1423 } else if instErr != nil { 1424 t.Errorf("unexpected error\ngot: %s", instErr.Error()) 1425 } 1426 1427 if test.Check != nil { 1428 test.Check(t, outputDir, newLocks) 1429 } 1430 1431 if test.WantEvents != nil { 1432 wantEvents := test.WantEvents(inst, outputDir) 1433 if diff := cmp.Diff(wantEvents, providerEvents); diff != "" { 1434 t.Errorf("wrong installer events\n%s", diff) 1435 } 1436 } 1437 }) 1438 } 1439 } 1440 1441 func TestEnsureProviderVersions_local_source(t *testing.T) { 1442 // create filesystem source using the test provider cache dir 1443 source := getproviders.NewFilesystemMirrorSource("testdata/cachedir") 1444 1445 // create a temporary workdir 1446 tmpDirPath, err := ioutil.TempDir("", "terraform-test-providercache") 1447 if err != nil { 1448 t.Fatal(err) 1449 } 1450 defer os.RemoveAll(tmpDirPath) 1451 1452 // set up the installer using the temporary directory and filesystem source 1453 platform := getproviders.Platform{OS: "linux", Arch: "amd64"} 1454 dir := NewDirWithPlatform(tmpDirPath, platform) 1455 installer := NewInstaller(dir, source) 1456 1457 tests := map[string]struct { 1458 provider string 1459 version string 1460 wantHash getproviders.Hash // getproviders.NilHash if not expected to be installed 1461 err string 1462 }{ 1463 "install-unpacked": { 1464 provider: "null", 1465 version: "2.0.0", 1466 wantHash: getproviders.HashScheme1.New("qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g="), 1467 }, 1468 "invalid-zip-file": { 1469 provider: "null", 1470 version: "2.1.0", 1471 wantHash: getproviders.NilHash, 1472 err: "zip: not a valid zip file", 1473 }, 1474 "version-constraint-unmet": { 1475 provider: "null", 1476 version: "2.2.0", 1477 wantHash: getproviders.NilHash, 1478 err: "no available releases match the given constraints 2.2.0", 1479 }, 1480 "missing-executable": { 1481 provider: "missing/executable", 1482 version: "2.0.0", 1483 wantHash: getproviders.NilHash, // installation fails for a provider with no executable 1484 err: "provider binary not found: could not find executable file starting with terraform-provider-executable", 1485 }, 1486 } 1487 1488 for name, test := range tests { 1489 t.Run(name, func(t *testing.T) { 1490 ctx := context.TODO() 1491 1492 provider := addrs.MustParseProviderSourceString(test.provider) 1493 versionConstraint := getproviders.MustParseVersionConstraints(test.version) 1494 version := getproviders.MustParseVersion(test.version) 1495 reqs := getproviders.Requirements{ 1496 provider: versionConstraint, 1497 } 1498 1499 newLocks, err := installer.EnsureProviderVersions(ctx, depsfile.NewLocks(), reqs, InstallNewProvidersOnly) 1500 gotProviderlocks := newLocks.AllProviders() 1501 wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{ 1502 provider: depsfile.NewProviderLock( 1503 provider, 1504 version, 1505 getproviders.MustParseVersionConstraints("= 2.0.0"), 1506 []getproviders.Hash{ 1507 test.wantHash, 1508 }, 1509 ), 1510 } 1511 if test.wantHash == getproviders.NilHash { 1512 wantProviderLocks = map[addrs.Provider]*depsfile.ProviderLock{} 1513 } 1514 1515 if diff := cmp.Diff(wantProviderLocks, gotProviderlocks, depsfile.ProviderLockComparer); diff != "" { 1516 t.Errorf("wrong selected\n%s", diff) 1517 } 1518 1519 if test.err == "" && err == nil { 1520 return 1521 } 1522 1523 switch err := err.(type) { 1524 case InstallerError: 1525 providerError, ok := err.ProviderErrors[provider] 1526 if !ok { 1527 t.Fatalf("did not get error for provider %s", provider) 1528 } 1529 1530 if got := providerError.Error(); got != test.err { 1531 t.Fatalf("wrong result\ngot: %s\nwant: %s\n", got, test.err) 1532 } 1533 default: 1534 t.Fatalf("wrong error type. Expected InstallerError, got %T", err) 1535 } 1536 }) 1537 } 1538 } 1539 1540 // This test only verifies protocol errors and does not try for successfull 1541 // installation (at the time of writing, the test files aren't signed so the 1542 // signature verification fails); that's left to the e2e tests. 1543 func TestEnsureProviderVersions_protocol_errors(t *testing.T) { 1544 source, _, close := testRegistrySource(t) 1545 defer close() 1546 1547 // create a temporary workdir 1548 tmpDirPath, err := ioutil.TempDir("", "terraform-test-providercache") 1549 if err != nil { 1550 t.Fatal(err) 1551 } 1552 defer os.RemoveAll(tmpDirPath) 1553 1554 version0 := getproviders.MustParseVersionConstraints("0.1.0") // supports protocol version 1.0 1555 version1 := getproviders.MustParseVersion("1.2.0") // this is the expected result in tests with a match 1556 version2 := getproviders.MustParseVersionConstraints("2.0") // supports protocol version 99 1557 1558 // set up the installer using the temporary directory and mock source 1559 platform := getproviders.Platform{OS: "gameboy", Arch: "lr35902"} 1560 dir := NewDirWithPlatform(tmpDirPath, platform) 1561 installer := NewInstaller(dir, source) 1562 1563 tests := map[string]struct { 1564 provider addrs.Provider 1565 inputVersion getproviders.VersionConstraints 1566 wantVersion getproviders.Version 1567 }{ 1568 "too old": { 1569 addrs.MustParseProviderSourceString("example.com/awesomesauce/happycloud"), 1570 version0, 1571 version1, 1572 }, 1573 "too new": { 1574 addrs.MustParseProviderSourceString("example.com/awesomesauce/happycloud"), 1575 version2, 1576 version1, 1577 }, 1578 "unsupported": { 1579 addrs.MustParseProviderSourceString("example.com/weaksauce/unsupported-protocol"), 1580 version0, 1581 getproviders.UnspecifiedVersion, 1582 }, 1583 } 1584 1585 for name, test := range tests { 1586 t.Run(name, func(t *testing.T) { 1587 reqs := getproviders.Requirements{ 1588 test.provider: test.inputVersion, 1589 } 1590 ctx := context.TODO() 1591 _, err := installer.EnsureProviderVersions(ctx, depsfile.NewLocks(), reqs, InstallNewProvidersOnly) 1592 1593 switch err := err.(type) { 1594 case nil: 1595 t.Fatalf("expected error, got success") 1596 case InstallerError: 1597 providerError, ok := err.ProviderErrors[test.provider] 1598 if !ok { 1599 t.Fatalf("did not get error for provider %s", test.provider) 1600 } 1601 1602 switch providerError := providerError.(type) { 1603 case getproviders.ErrProtocolNotSupported: 1604 if !providerError.Suggestion.Same(test.wantVersion) { 1605 t.Fatalf("wrong result\ngot: %s\nwant: %s\n", providerError.Suggestion, test.wantVersion) 1606 } 1607 default: 1608 t.Fatalf("wrong error type. Expected ErrProtocolNotSupported, got %T", err) 1609 } 1610 default: 1611 t.Fatalf("wrong error type. Expected InstallerError, got %T", err) 1612 } 1613 }) 1614 } 1615 } 1616 1617 // testServices starts up a local HTTP server running a fake provider registry 1618 // service and returns a service discovery object pre-configured to consider 1619 // the host "example.com" to be served by the fake registry service. 1620 // 1621 // The returned discovery object also knows the hostname "not.example.com" 1622 // which does not have a provider registry at all and "too-new.example.com" 1623 // which has a "providers.v99" service that is inoperable but could be useful 1624 // to test the error reporting for detecting an unsupported protocol version. 1625 // It also knows fails.example.com but it refers to an endpoint that doesn't 1626 // correctly speak HTTP, to simulate a protocol error. 1627 // 1628 // The second return value is a function to call at the end of a test function 1629 // to shut down the test server. After you call that function, the discovery 1630 // object becomes useless. 1631 func testServices(t *testing.T) (services *disco.Disco, baseURL string, cleanup func()) { 1632 server := httptest.NewServer(http.HandlerFunc(fakeRegistryHandler)) 1633 1634 services = disco.New() 1635 services.ForceHostServices(svchost.Hostname("example.com"), map[string]interface{}{ 1636 "providers.v1": server.URL + "/providers/v1/", 1637 }) 1638 services.ForceHostServices(svchost.Hostname("not.example.com"), map[string]interface{}{}) 1639 services.ForceHostServices(svchost.Hostname("too-new.example.com"), map[string]interface{}{ 1640 // This service doesn't actually work; it's here only to be 1641 // detected as "too new" by the discovery logic. 1642 "providers.v99": server.URL + "/providers/v99/", 1643 }) 1644 services.ForceHostServices(svchost.Hostname("fails.example.com"), map[string]interface{}{ 1645 "providers.v1": server.URL + "/fails-immediately/", 1646 }) 1647 1648 // We'll also permit registry.terraform.io here just because it's our 1649 // default and has some unique features that are not allowed on any other 1650 // hostname. It behaves the same as example.com, which should be preferred 1651 // if you're not testing something specific to the default registry in order 1652 // to ensure that most things are hostname-agnostic. 1653 services.ForceHostServices(svchost.Hostname("registry.terraform.io"), map[string]interface{}{ 1654 "providers.v1": server.URL + "/providers/v1/", 1655 }) 1656 1657 return services, server.URL, func() { 1658 server.Close() 1659 } 1660 } 1661 1662 // testRegistrySource is a wrapper around testServices that uses the created 1663 // discovery object to produce a Source instance that is ready to use with the 1664 // fake registry services. 1665 // 1666 // As with testServices, the second return value is a function to call at the end 1667 // of your test in order to shut down the test server. 1668 func testRegistrySource(t *testing.T) (source *getproviders.RegistrySource, baseURL string, cleanup func()) { 1669 services, baseURL, close := testServices(t) 1670 source = getproviders.NewRegistrySource(services) 1671 return source, baseURL, close 1672 } 1673 1674 func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) { 1675 path := req.URL.EscapedPath() 1676 if strings.HasPrefix(path, "/fails-immediately/") { 1677 // Here we take over the socket and just close it immediately, to 1678 // simulate one possible way a server might not be an HTTP server. 1679 hijacker, ok := resp.(http.Hijacker) 1680 if !ok { 1681 // Not hijackable, so we'll just fail normally. 1682 // If this happens, tests relying on this will fail. 1683 resp.WriteHeader(500) 1684 resp.Write([]byte(`cannot hijack`)) 1685 return 1686 } 1687 conn, _, err := hijacker.Hijack() 1688 if err != nil { 1689 resp.WriteHeader(500) 1690 resp.Write([]byte(`hijack failed`)) 1691 return 1692 } 1693 conn.Close() 1694 return 1695 } 1696 1697 if strings.HasPrefix(path, "/pkg/") { 1698 switch path { 1699 case "/pkg/awesomesauce/happycloud_1.2.0.zip": 1700 resp.Write([]byte("some zip file")) 1701 case "/pkg/awesomesauce/happycloud_1.2.0_SHA256SUMS": 1702 resp.Write([]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n")) 1703 case "/pkg/awesomesauce/happycloud_1.2.0_SHA256SUMS.sig": 1704 resp.Write([]byte("GPG signature")) 1705 default: 1706 resp.WriteHeader(404) 1707 resp.Write([]byte("unknown package file download")) 1708 } 1709 return 1710 } 1711 1712 if !strings.HasPrefix(path, "/providers/v1/") { 1713 resp.WriteHeader(404) 1714 resp.Write([]byte(`not a provider registry endpoint`)) 1715 return 1716 } 1717 1718 pathParts := strings.Split(path, "/")[3:] 1719 if len(pathParts) < 2 { 1720 resp.WriteHeader(404) 1721 resp.Write([]byte(`unexpected number of path parts`)) 1722 return 1723 } 1724 log.Printf("[TRACE] fake provider registry request for %#v", pathParts) 1725 if len(pathParts) == 2 { 1726 switch pathParts[0] + "/" + pathParts[1] { 1727 1728 case "-/legacy": 1729 // NOTE: This legacy lookup endpoint is specific to 1730 // registry.terraform.io and not expected to work on any other 1731 // registry host. 1732 resp.Header().Set("Content-Type", "application/json") 1733 resp.WriteHeader(200) 1734 resp.Write([]byte(`{"namespace":"legacycorp"}`)) 1735 1736 default: 1737 resp.WriteHeader(404) 1738 resp.Write([]byte(`unknown namespace or provider type for direct lookup`)) 1739 } 1740 } 1741 1742 if len(pathParts) < 3 { 1743 resp.WriteHeader(404) 1744 resp.Write([]byte(`unexpected number of path parts`)) 1745 return 1746 } 1747 1748 if pathParts[2] == "versions" { 1749 if len(pathParts) != 3 { 1750 resp.WriteHeader(404) 1751 resp.Write([]byte(`extraneous path parts`)) 1752 return 1753 } 1754 1755 switch pathParts[0] + "/" + pathParts[1] { 1756 case "awesomesauce/happycloud": 1757 resp.Header().Set("Content-Type", "application/json") 1758 resp.WriteHeader(200) 1759 // Note that these version numbers are intentionally misordered 1760 // so we can test that the client-side code places them in the 1761 // correct order (lowest precedence first). 1762 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"]}]}`)) 1763 case "weaksauce/unsupported-protocol": 1764 resp.Header().Set("Content-Type", "application/json") 1765 resp.WriteHeader(200) 1766 resp.Write([]byte(`{"versions":[{"version":"0.1.0","protocols":["0.1"]}]}`)) 1767 case "weaksauce/no-versions": 1768 resp.Header().Set("Content-Type", "application/json") 1769 resp.WriteHeader(200) 1770 resp.Write([]byte(`{"versions":[]}`)) 1771 default: 1772 resp.WriteHeader(404) 1773 resp.Write([]byte(`unknown namespace or provider type`)) 1774 } 1775 return 1776 } 1777 1778 if len(pathParts) == 6 && pathParts[3] == "download" { 1779 switch pathParts[0] + "/" + pathParts[1] { 1780 case "awesomesauce/happycloud": 1781 if pathParts[4] == "nonexist" { 1782 resp.WriteHeader(404) 1783 resp.Write([]byte(`unsupported OS`)) 1784 return 1785 } 1786 version := pathParts[2] 1787 body := map[string]interface{}{ 1788 "protocols": []string{"99.0"}, 1789 "os": pathParts[4], 1790 "arch": pathParts[5], 1791 "filename": "happycloud_" + version + ".zip", 1792 "shasum": "000000000000000000000000000000000000000000000000000000000000f00d", 1793 "download_url": "/pkg/awesomesauce/happycloud_" + version + ".zip", 1794 "shasums_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS", 1795 "shasums_signature_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS.sig", 1796 "signing_keys": map[string]interface{}{ 1797 "gpg_public_keys": []map[string]interface{}{ 1798 { 1799 "ascii_armor": getproviders.HashicorpPublicKey, 1800 }, 1801 }, 1802 }, 1803 } 1804 enc, err := json.Marshal(body) 1805 if err != nil { 1806 resp.WriteHeader(500) 1807 resp.Write([]byte("failed to encode body")) 1808 } 1809 resp.Header().Set("Content-Type", "application/json") 1810 resp.WriteHeader(200) 1811 resp.Write(enc) 1812 case "weaksauce/unsupported-protocol": 1813 var protocols []string 1814 version := pathParts[2] 1815 switch version { 1816 case "0.1.0": 1817 protocols = []string{"1.0"} 1818 case "2.0.0": 1819 protocols = []string{"99.0"} 1820 default: 1821 protocols = []string{"5.0"} 1822 } 1823 1824 body := map[string]interface{}{ 1825 "protocols": protocols, 1826 "os": pathParts[4], 1827 "arch": pathParts[5], 1828 "filename": "happycloud_" + version + ".zip", 1829 "shasum": "000000000000000000000000000000000000000000000000000000000000f00d", 1830 "download_url": "/pkg/awesomesauce/happycloud_" + version + ".zip", 1831 "shasums_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS", 1832 "shasums_signature_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS.sig", 1833 "signing_keys": map[string]interface{}{ 1834 "gpg_public_keys": []map[string]interface{}{ 1835 { 1836 "ascii_armor": getproviders.HashicorpPublicKey, 1837 }, 1838 }, 1839 }, 1840 } 1841 enc, err := json.Marshal(body) 1842 if err != nil { 1843 resp.WriteHeader(500) 1844 resp.Write([]byte("failed to encode body")) 1845 } 1846 resp.Header().Set("Content-Type", "application/json") 1847 resp.WriteHeader(200) 1848 resp.Write(enc) 1849 default: 1850 resp.WriteHeader(404) 1851 resp.Write([]byte(`unknown namespace/provider/version/architecture`)) 1852 } 1853 return 1854 } 1855 1856 resp.WriteHeader(404) 1857 resp.Write([]byte(`unrecognized path scheme`)) 1858 } 1859 1860 // In order to be able to compare the recorded temp dir paths, we need to 1861 // normalize the path to match what the installer would report. 1862 func tmpDir(t *testing.T) string { 1863 d := t.TempDir() 1864 unlinked, err := filepath.EvalSymlinks(d) 1865 if err != nil { 1866 t.Fatal(err) 1867 } 1868 return filepath.Clean(unlinked) 1869 }