github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/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/hashicorp/terraform/internal/addrs" 22 "github.com/hashicorp/terraform/internal/depsfile" 23 "github.com/hashicorp/terraform/internal/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 "failed install of a non-existing built-in provider": { 828 Source: getproviders.NewMockSource( 829 []getproviders.PackageMeta{}, 830 nil, 831 ), 832 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 833 // NOTE: We're intentionally not calling 834 // inst.SetBuiltInProviderTypes to make the "terraform" 835 // built-in provider available here, so requests for it 836 // should fail. 837 }, 838 Mode: InstallNewProvidersOnly, 839 Reqs: getproviders.Requirements{ 840 terraformProvider: nil, 841 }, 842 WantErr: `some providers could not be installed: 843 - terraform.io/builtin/terraform: this Terraform release has no built-in provider named "terraform"`, 844 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 845 return map[addrs.Provider][]*testInstallerEventLogItem{ 846 noProvider: { 847 { 848 Event: "PendingProviders", 849 Args: map[addrs.Provider]getproviders.VersionConstraints{ 850 terraformProvider: constraints.IntersectionSpec(nil), 851 }, 852 }, 853 }, 854 terraformProvider: { 855 { 856 Event: "BuiltInProviderFailure", 857 Provider: terraformProvider, 858 Args: `this Terraform release has no built-in provider named "terraform"`, 859 }, 860 }, 861 } 862 }, 863 }, 864 "failed install when a built-in provider has a version constraint": { 865 Source: getproviders.NewMockSource( 866 []getproviders.PackageMeta{}, 867 nil, 868 ), 869 Prepare: func(t *testing.T, inst *Installer, dir *Dir) { 870 inst.SetBuiltInProviderTypes([]string{"terraform"}) 871 }, 872 Mode: InstallNewProvidersOnly, 873 Reqs: getproviders.Requirements{ 874 terraformProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 875 }, 876 WantErr: `some providers could not be installed: 877 - terraform.io/builtin/terraform: built-in providers do not support explicit version constraints`, 878 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 879 return map[addrs.Provider][]*testInstallerEventLogItem{ 880 noProvider: { 881 { 882 Event: "PendingProviders", 883 Args: map[addrs.Provider]getproviders.VersionConstraints{ 884 terraformProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 885 }, 886 }, 887 }, 888 terraformProvider: { 889 { 890 Event: "BuiltInProviderFailure", 891 Provider: terraformProvider, 892 Args: `built-in providers do not support explicit version constraints`, 893 }, 894 }, 895 } 896 }, 897 }, 898 "locked version is excluded by new version constraint": { 899 Source: getproviders.NewMockSource( 900 []getproviders.PackageMeta{ 901 { 902 Provider: beepProvider, 903 Version: getproviders.MustParseVersion("1.0.0"), 904 TargetPlatform: fakePlatform, 905 Location: beepProviderDir, 906 }, 907 { 908 Provider: beepProvider, 909 Version: getproviders.MustParseVersion("2.0.0"), 910 TargetPlatform: fakePlatform, 911 Location: beepProviderDir, 912 }, 913 }, 914 nil, 915 ), 916 LockFile: ` 917 provider "example.com/foo/beep" { 918 version = "1.0.0" 919 constraints = ">= 1.0.0" 920 hashes = [ 921 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 922 ] 923 } 924 `, 925 Mode: InstallNewProvidersOnly, 926 Reqs: getproviders.Requirements{ 927 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 928 }, 929 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 930 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 931 t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached)) 932 } 933 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 934 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 935 } 936 937 gotLock := locks.Provider(beepProvider) 938 wantLock := depsfile.NewProviderLock( 939 beepProvider, 940 getproviders.MustParseVersion("1.0.0"), 941 getproviders.MustParseVersionConstraints(">= 1.0.0"), 942 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 943 ) 944 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 945 t.Errorf("wrong lock entry\n%s", diff) 946 } 947 }, 948 WantErr: `some providers could not be installed: 949 - 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`, 950 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 951 return map[addrs.Provider][]*testInstallerEventLogItem{ 952 noProvider: { 953 { 954 Event: "PendingProviders", 955 Args: map[addrs.Provider]getproviders.VersionConstraints{ 956 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 957 }, 958 }, 959 }, 960 beepProvider: { 961 { 962 Event: "QueryPackagesBegin", 963 Provider: beepProvider, 964 Args: struct { 965 Constraints string 966 Locked bool 967 }{">= 2.0.0", true}, 968 }, 969 { 970 Event: "QueryPackagesFailure", 971 Provider: beepProvider, 972 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`, 973 }, 974 }, 975 } 976 }, 977 }, 978 "locked version is no longer available": { 979 Source: getproviders.NewMockSource( 980 []getproviders.PackageMeta{ 981 { 982 Provider: beepProvider, 983 Version: getproviders.MustParseVersion("1.0.0"), 984 TargetPlatform: fakePlatform, 985 Location: beepProviderDir, 986 }, 987 { 988 Provider: beepProvider, 989 Version: getproviders.MustParseVersion("2.0.0"), 990 TargetPlatform: fakePlatform, 991 Location: beepProviderDir, 992 }, 993 }, 994 nil, 995 ), 996 LockFile: ` 997 provider "example.com/foo/beep" { 998 version = "1.2.0" 999 constraints = ">= 1.0.0" 1000 hashes = [ 1001 "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=", 1002 ] 1003 } 1004 `, 1005 Mode: InstallNewProvidersOnly, 1006 Reqs: getproviders.Requirements{ 1007 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1008 }, 1009 Check: func(t *testing.T, dir *Dir, locks *depsfile.Locks) { 1010 if allCached := dir.AllAvailablePackages(); len(allCached) != 0 { 1011 t.Errorf("wrong number of cache directory entries; want none\n%s", spew.Sdump(allCached)) 1012 } 1013 if allLocked := locks.AllProviders(); len(allLocked) != 1 { 1014 t.Errorf("wrong number of provider lock entries; want only one\n%s", spew.Sdump(allLocked)) 1015 } 1016 1017 gotLock := locks.Provider(beepProvider) 1018 wantLock := depsfile.NewProviderLock( 1019 beepProvider, 1020 getproviders.MustParseVersion("1.2.0"), 1021 getproviders.MustParseVersionConstraints(">= 1.0.0"), 1022 []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, 1023 ) 1024 if diff := cmp.Diff(wantLock, gotLock, depsfile.ProviderLockComparer); diff != "" { 1025 t.Errorf("wrong lock entry\n%s", diff) 1026 } 1027 }, 1028 WantErr: `some providers could not be installed: 1029 - example.com/foo/beep: the previously-selected version 1.2.0 is no longer available`, 1030 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1031 return map[addrs.Provider][]*testInstallerEventLogItem{ 1032 noProvider: { 1033 { 1034 Event: "PendingProviders", 1035 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1036 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1037 }, 1038 }, 1039 }, 1040 beepProvider: { 1041 { 1042 Event: "QueryPackagesBegin", 1043 Provider: beepProvider, 1044 Args: struct { 1045 Constraints string 1046 Locked bool 1047 }{">= 1.0.0", true}, 1048 }, 1049 { 1050 Event: "QueryPackagesFailure", 1051 Provider: beepProvider, 1052 Args: `the previously-selected version 1.2.0 is no longer available`, 1053 }, 1054 }, 1055 } 1056 }, 1057 }, 1058 "no versions match the version constraint": { 1059 Source: getproviders.NewMockSource( 1060 []getproviders.PackageMeta{ 1061 { 1062 Provider: beepProvider, 1063 Version: getproviders.MustParseVersion("1.0.0"), 1064 TargetPlatform: fakePlatform, 1065 Location: beepProviderDir, 1066 }, 1067 }, 1068 nil, 1069 ), 1070 Mode: InstallNewProvidersOnly, 1071 Reqs: getproviders.Requirements{ 1072 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1073 }, 1074 WantErr: `some providers could not be installed: 1075 - example.com/foo/beep: no available releases match the given constraints >= 2.0.0`, 1076 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1077 return map[addrs.Provider][]*testInstallerEventLogItem{ 1078 noProvider: { 1079 { 1080 Event: "PendingProviders", 1081 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1082 beepProvider: getproviders.MustParseVersionConstraints(">= 2.0.0"), 1083 }, 1084 }, 1085 }, 1086 beepProvider: { 1087 { 1088 Event: "QueryPackagesBegin", 1089 Provider: beepProvider, 1090 Args: struct { 1091 Constraints string 1092 Locked bool 1093 }{">= 2.0.0", false}, 1094 }, 1095 { 1096 Event: "QueryPackagesFailure", 1097 Provider: beepProvider, 1098 Args: `no available releases match the given constraints >= 2.0.0`, 1099 }, 1100 }, 1101 } 1102 }, 1103 }, 1104 "version exists but doesn't support the current platform": { 1105 Source: getproviders.NewMockSource( 1106 []getproviders.PackageMeta{ 1107 { 1108 Provider: beepProvider, 1109 Version: getproviders.MustParseVersion("1.0.0"), 1110 TargetPlatform: wrongPlatform, 1111 Location: beepProviderDir, 1112 }, 1113 }, 1114 nil, 1115 ), 1116 Mode: InstallNewProvidersOnly, 1117 Reqs: getproviders.Requirements{ 1118 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1119 }, 1120 WantErr: `some providers could not be installed: 1121 - example.com/foo/beep: provider example.com/foo/beep 1.0.0 is not available for bleep_bloop`, 1122 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1123 return map[addrs.Provider][]*testInstallerEventLogItem{ 1124 noProvider: { 1125 { 1126 Event: "PendingProviders", 1127 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1128 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1129 }, 1130 }, 1131 }, 1132 beepProvider: { 1133 { 1134 Event: "QueryPackagesBegin", 1135 Provider: beepProvider, 1136 Args: struct { 1137 Constraints string 1138 Locked bool 1139 }{">= 1.0.0", false}, 1140 }, 1141 { 1142 Event: "QueryPackagesSuccess", 1143 Provider: beepProvider, 1144 Args: "1.0.0", 1145 }, 1146 { 1147 Event: "FetchPackageMeta", 1148 Provider: beepProvider, 1149 Args: "1.0.0", 1150 }, 1151 { 1152 Event: "FetchPackageFailure", 1153 Provider: beepProvider, 1154 Args: struct { 1155 Version string 1156 Error string 1157 }{ 1158 "1.0.0", 1159 "provider example.com/foo/beep 1.0.0 is not available for bleep_bloop", 1160 }, 1161 }, 1162 }, 1163 } 1164 }, 1165 }, 1166 "available package doesn't match locked hash": { 1167 Source: getproviders.NewMockSource( 1168 []getproviders.PackageMeta{ 1169 { 1170 Provider: beepProvider, 1171 Version: getproviders.MustParseVersion("1.0.0"), 1172 TargetPlatform: fakePlatform, 1173 Location: beepProviderDir, 1174 }, 1175 }, 1176 nil, 1177 ), 1178 LockFile: ` 1179 provider "example.com/foo/beep" { 1180 version = "1.0.0" 1181 constraints = ">= 1.0.0" 1182 hashes = [ 1183 "h1:does-not-match", 1184 ] 1185 } 1186 `, 1187 Mode: InstallNewProvidersOnly, 1188 Reqs: getproviders.Requirements{ 1189 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1190 }, 1191 WantErr: `some providers could not be installed: 1192 - 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)`, 1193 WantEvents: func(inst *Installer, dir *Dir) map[addrs.Provider][]*testInstallerEventLogItem { 1194 return map[addrs.Provider][]*testInstallerEventLogItem{ 1195 noProvider: { 1196 { 1197 Event: "PendingProviders", 1198 Args: map[addrs.Provider]getproviders.VersionConstraints{ 1199 beepProvider: getproviders.MustParseVersionConstraints(">= 1.0.0"), 1200 }, 1201 }, 1202 }, 1203 beepProvider: { 1204 { 1205 Event: "QueryPackagesBegin", 1206 Provider: beepProvider, 1207 Args: struct { 1208 Constraints string 1209 Locked bool 1210 }{">= 1.0.0", true}, 1211 }, 1212 { 1213 Event: "QueryPackagesSuccess", 1214 Provider: beepProvider, 1215 Args: "1.0.0", 1216 }, 1217 { 1218 Event: "FetchPackageMeta", 1219 Provider: beepProvider, 1220 Args: "1.0.0", 1221 }, 1222 { 1223 Event: "FetchPackageBegin", 1224 Provider: beepProvider, 1225 Args: struct { 1226 Version string 1227 Location getproviders.PackageLocation 1228 }{"1.0.0", beepProviderDir}, 1229 }, 1230 { 1231 Event: "FetchPackageFailure", 1232 Provider: beepProvider, 1233 Args: struct { 1234 Version string 1235 Error string 1236 }{ 1237 "1.0.0", 1238 `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)`, 1239 }, 1240 }, 1241 }, 1242 } 1243 }, 1244 }, 1245 } 1246 1247 ctx := context.Background() 1248 1249 for name, test := range tests { 1250 t.Run(name, func(t *testing.T) { 1251 if test.Check == nil && test.WantEvents == nil && test.WantErr == "" { 1252 t.Fatalf("invalid test: must set at least one of Check, WantEvents, or WantErr") 1253 } 1254 1255 outputDir := NewDirWithPlatform(tmpDir(t), fakePlatform) 1256 source := test.Source 1257 if source == nil { 1258 source = getproviders.NewMockSource(nil, nil) 1259 } 1260 inst := NewInstaller(outputDir, source) 1261 if test.Prepare != nil { 1262 test.Prepare(t, inst, outputDir) 1263 } 1264 1265 locks, lockDiags := depsfile.LoadLocksFromBytes([]byte(test.LockFile), "test.lock.hcl") 1266 if lockDiags.HasErrors() { 1267 t.Fatalf("invalid lock file: %s", lockDiags.Err().Error()) 1268 } 1269 1270 providerEvents := make(map[addrs.Provider][]*testInstallerEventLogItem) 1271 eventsCh := make(chan *testInstallerEventLogItem) 1272 var newLocks *depsfile.Locks 1273 var instErr error 1274 go func(ch chan *testInstallerEventLogItem) { 1275 events := installerLogEventsForTests(ch) 1276 ctx := events.OnContext(ctx) 1277 newLocks, instErr = inst.EnsureProviderVersions(ctx, locks, test.Reqs, test.Mode) 1278 close(eventsCh) // exits the event loop below 1279 }(eventsCh) 1280 for evt := range eventsCh { 1281 // We do the event collection in the main goroutine, rather than 1282 // running the installer itself in the main goroutine, so that 1283 // we can safely t.Log in here without violating the testing.T 1284 // usage rules. 1285 if evt.Provider == (addrs.Provider{}) { 1286 t.Logf("%s(%s)", evt.Event, spew.Sdump(evt.Args)) 1287 } else { 1288 t.Logf("%s: %s(%s)", evt.Provider, evt.Event, spew.Sdump(evt.Args)) 1289 } 1290 providerEvents[evt.Provider] = append(providerEvents[evt.Provider], evt) 1291 } 1292 1293 if test.WantErr != "" { 1294 if instErr == nil { 1295 t.Errorf("succeeded; want error\nwant: %s", test.WantErr) 1296 } else if got, want := instErr.Error(), test.WantErr; got != want { 1297 t.Errorf("wrong error\ngot: %s\nwant: %s", got, want) 1298 } 1299 } else if instErr != nil { 1300 t.Errorf("unexpected error\ngot: %s", instErr.Error()) 1301 } 1302 1303 if test.Check != nil { 1304 test.Check(t, outputDir, newLocks) 1305 } 1306 1307 if test.WantEvents != nil { 1308 wantEvents := test.WantEvents(inst, outputDir) 1309 if diff := cmp.Diff(wantEvents, providerEvents); diff != "" { 1310 t.Errorf("wrong installer events\n%s", diff) 1311 } 1312 } 1313 }) 1314 } 1315 } 1316 1317 func TestEnsureProviderVersions_local_source(t *testing.T) { 1318 // create filesystem source using the test provider cache dir 1319 source := getproviders.NewFilesystemMirrorSource("testdata/cachedir") 1320 1321 // create a temporary workdir 1322 tmpDirPath, err := ioutil.TempDir("", "terraform-test-providercache") 1323 if err != nil { 1324 t.Fatal(err) 1325 } 1326 defer os.RemoveAll(tmpDirPath) 1327 1328 // set up the installer using the temporary directory and filesystem source 1329 platform := getproviders.Platform{OS: "linux", Arch: "amd64"} 1330 dir := NewDirWithPlatform(tmpDirPath, platform) 1331 installer := NewInstaller(dir, source) 1332 1333 tests := map[string]struct { 1334 provider string 1335 version string 1336 wantHash getproviders.Hash // getproviders.NilHash if not expected to be installed 1337 err string 1338 }{ 1339 "install-unpacked": { 1340 provider: "null", 1341 version: "2.0.0", 1342 wantHash: getproviders.HashScheme1.New("qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g="), 1343 }, 1344 "invalid-zip-file": { 1345 provider: "null", 1346 version: "2.1.0", 1347 wantHash: getproviders.NilHash, 1348 err: "zip: not a valid zip file", 1349 }, 1350 "version-constraint-unmet": { 1351 provider: "null", 1352 version: "2.2.0", 1353 wantHash: getproviders.NilHash, 1354 err: "no available releases match the given constraints 2.2.0", 1355 }, 1356 "missing-executable": { 1357 provider: "missing/executable", 1358 version: "2.0.0", 1359 wantHash: getproviders.NilHash, // installation fails for a provider with no executable 1360 err: "provider binary not found: could not find executable file starting with terraform-provider-executable", 1361 }, 1362 } 1363 1364 for name, test := range tests { 1365 t.Run(name, func(t *testing.T) { 1366 ctx := context.TODO() 1367 1368 provider := addrs.MustParseProviderSourceString(test.provider) 1369 versionConstraint := getproviders.MustParseVersionConstraints(test.version) 1370 version := getproviders.MustParseVersion(test.version) 1371 reqs := getproviders.Requirements{ 1372 provider: versionConstraint, 1373 } 1374 1375 newLocks, err := installer.EnsureProviderVersions(ctx, depsfile.NewLocks(), reqs, InstallNewProvidersOnly) 1376 gotProviderlocks := newLocks.AllProviders() 1377 wantProviderLocks := map[addrs.Provider]*depsfile.ProviderLock{ 1378 provider: depsfile.NewProviderLock( 1379 provider, 1380 version, 1381 getproviders.MustParseVersionConstraints("= 2.0.0"), 1382 []getproviders.Hash{ 1383 test.wantHash, 1384 }, 1385 ), 1386 } 1387 if test.wantHash == getproviders.NilHash { 1388 wantProviderLocks = map[addrs.Provider]*depsfile.ProviderLock{} 1389 } 1390 1391 if diff := cmp.Diff(wantProviderLocks, gotProviderlocks, depsfile.ProviderLockComparer); diff != "" { 1392 t.Errorf("wrong selected\n%s", diff) 1393 } 1394 1395 if test.err == "" && err == nil { 1396 return 1397 } 1398 1399 switch err := err.(type) { 1400 case InstallerError: 1401 providerError, ok := err.ProviderErrors[provider] 1402 if !ok { 1403 t.Fatalf("did not get error for provider %s", provider) 1404 } 1405 1406 if got := providerError.Error(); got != test.err { 1407 t.Fatalf("wrong result\ngot: %s\nwant: %s\n", got, test.err) 1408 } 1409 default: 1410 t.Fatalf("wrong error type. Expected InstallerError, got %T", err) 1411 } 1412 }) 1413 } 1414 } 1415 1416 // This test only verifies protocol errors and does not try for successfull 1417 // installation (at the time of writing, the test files aren't signed so the 1418 // signature verification fails); that's left to the e2e tests. 1419 func TestEnsureProviderVersions_protocol_errors(t *testing.T) { 1420 source, _, close := testRegistrySource(t) 1421 defer close() 1422 1423 // create a temporary workdir 1424 tmpDirPath, err := ioutil.TempDir("", "terraform-test-providercache") 1425 if err != nil { 1426 t.Fatal(err) 1427 } 1428 defer os.RemoveAll(tmpDirPath) 1429 1430 version0 := getproviders.MustParseVersionConstraints("0.1.0") // supports protocol version 1.0 1431 version1 := getproviders.MustParseVersion("1.2.0") // this is the expected result in tests with a match 1432 version2 := getproviders.MustParseVersionConstraints("2.0") // supports protocol version 99 1433 1434 // set up the installer using the temporary directory and mock source 1435 platform := getproviders.Platform{OS: "gameboy", Arch: "lr35902"} 1436 dir := NewDirWithPlatform(tmpDirPath, platform) 1437 installer := NewInstaller(dir, source) 1438 1439 tests := map[string]struct { 1440 provider addrs.Provider 1441 inputVersion getproviders.VersionConstraints 1442 wantVersion getproviders.Version 1443 }{ 1444 "too old": { 1445 addrs.MustParseProviderSourceString("example.com/awesomesauce/happycloud"), 1446 version0, 1447 version1, 1448 }, 1449 "too new": { 1450 addrs.MustParseProviderSourceString("example.com/awesomesauce/happycloud"), 1451 version2, 1452 version1, 1453 }, 1454 "unsupported": { 1455 addrs.MustParseProviderSourceString("example.com/weaksauce/unsupported-protocol"), 1456 version0, 1457 getproviders.UnspecifiedVersion, 1458 }, 1459 } 1460 1461 for name, test := range tests { 1462 t.Run(name, func(t *testing.T) { 1463 reqs := getproviders.Requirements{ 1464 test.provider: test.inputVersion, 1465 } 1466 ctx := context.TODO() 1467 _, err := installer.EnsureProviderVersions(ctx, depsfile.NewLocks(), reqs, InstallNewProvidersOnly) 1468 1469 switch err := err.(type) { 1470 case nil: 1471 t.Fatalf("expected error, got success") 1472 case InstallerError: 1473 providerError, ok := err.ProviderErrors[test.provider] 1474 if !ok { 1475 t.Fatalf("did not get error for provider %s", test.provider) 1476 } 1477 1478 switch providerError := providerError.(type) { 1479 case getproviders.ErrProtocolNotSupported: 1480 if !providerError.Suggestion.Same(test.wantVersion) { 1481 t.Fatalf("wrong result\ngot: %s\nwant: %s\n", providerError.Suggestion, test.wantVersion) 1482 } 1483 default: 1484 t.Fatalf("wrong error type. Expected ErrProtocolNotSupported, got %T", err) 1485 } 1486 default: 1487 t.Fatalf("wrong error type. Expected InstallerError, got %T", err) 1488 } 1489 }) 1490 } 1491 } 1492 1493 // testServices starts up a local HTTP server running a fake provider registry 1494 // service and returns a service discovery object pre-configured to consider 1495 // the host "example.com" to be served by the fake registry service. 1496 // 1497 // The returned discovery object also knows the hostname "not.example.com" 1498 // which does not have a provider registry at all and "too-new.example.com" 1499 // which has a "providers.v99" service that is inoperable but could be useful 1500 // to test the error reporting for detecting an unsupported protocol version. 1501 // It also knows fails.example.com but it refers to an endpoint that doesn't 1502 // correctly speak HTTP, to simulate a protocol error. 1503 // 1504 // The second return value is a function to call at the end of a test function 1505 // to shut down the test server. After you call that function, the discovery 1506 // object becomes useless. 1507 func testServices(t *testing.T) (services *disco.Disco, baseURL string, cleanup func()) { 1508 server := httptest.NewServer(http.HandlerFunc(fakeRegistryHandler)) 1509 1510 services = disco.New() 1511 services.ForceHostServices(svchost.Hostname("example.com"), map[string]interface{}{ 1512 "providers.v1": server.URL + "/providers/v1/", 1513 }) 1514 services.ForceHostServices(svchost.Hostname("not.example.com"), map[string]interface{}{}) 1515 services.ForceHostServices(svchost.Hostname("too-new.example.com"), map[string]interface{}{ 1516 // This service doesn't actually work; it's here only to be 1517 // detected as "too new" by the discovery logic. 1518 "providers.v99": server.URL + "/providers/v99/", 1519 }) 1520 services.ForceHostServices(svchost.Hostname("fails.example.com"), map[string]interface{}{ 1521 "providers.v1": server.URL + "/fails-immediately/", 1522 }) 1523 1524 // We'll also permit registry.terraform.io here just because it's our 1525 // default and has some unique features that are not allowed on any other 1526 // hostname. It behaves the same as example.com, which should be preferred 1527 // if you're not testing something specific to the default registry in order 1528 // to ensure that most things are hostname-agnostic. 1529 services.ForceHostServices(svchost.Hostname("registry.terraform.io"), map[string]interface{}{ 1530 "providers.v1": server.URL + "/providers/v1/", 1531 }) 1532 1533 return services, server.URL, func() { 1534 server.Close() 1535 } 1536 } 1537 1538 // testRegistrySource is a wrapper around testServices that uses the created 1539 // discovery object to produce a Source instance that is ready to use with the 1540 // fake registry services. 1541 // 1542 // As with testServices, the second return value is a function to call at the end 1543 // of your test in order to shut down the test server. 1544 func testRegistrySource(t *testing.T) (source *getproviders.RegistrySource, baseURL string, cleanup func()) { 1545 services, baseURL, close := testServices(t) 1546 source = getproviders.NewRegistrySource(services) 1547 return source, baseURL, close 1548 } 1549 1550 func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) { 1551 path := req.URL.EscapedPath() 1552 if strings.HasPrefix(path, "/fails-immediately/") { 1553 // Here we take over the socket and just close it immediately, to 1554 // simulate one possible way a server might not be an HTTP server. 1555 hijacker, ok := resp.(http.Hijacker) 1556 if !ok { 1557 // Not hijackable, so we'll just fail normally. 1558 // If this happens, tests relying on this will fail. 1559 resp.WriteHeader(500) 1560 resp.Write([]byte(`cannot hijack`)) 1561 return 1562 } 1563 conn, _, err := hijacker.Hijack() 1564 if err != nil { 1565 resp.WriteHeader(500) 1566 resp.Write([]byte(`hijack failed`)) 1567 return 1568 } 1569 conn.Close() 1570 return 1571 } 1572 1573 if strings.HasPrefix(path, "/pkg/") { 1574 switch path { 1575 case "/pkg/awesomesauce/happycloud_1.2.0.zip": 1576 resp.Write([]byte("some zip file")) 1577 case "/pkg/awesomesauce/happycloud_1.2.0_SHA256SUMS": 1578 resp.Write([]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n")) 1579 case "/pkg/awesomesauce/happycloud_1.2.0_SHA256SUMS.sig": 1580 resp.Write([]byte("GPG signature")) 1581 default: 1582 resp.WriteHeader(404) 1583 resp.Write([]byte("unknown package file download")) 1584 } 1585 return 1586 } 1587 1588 if !strings.HasPrefix(path, "/providers/v1/") { 1589 resp.WriteHeader(404) 1590 resp.Write([]byte(`not a provider registry endpoint`)) 1591 return 1592 } 1593 1594 pathParts := strings.Split(path, "/")[3:] 1595 if len(pathParts) < 2 { 1596 resp.WriteHeader(404) 1597 resp.Write([]byte(`unexpected number of path parts`)) 1598 return 1599 } 1600 log.Printf("[TRACE] fake provider registry request for %#v", pathParts) 1601 if len(pathParts) == 2 { 1602 switch pathParts[0] + "/" + pathParts[1] { 1603 1604 case "-/legacy": 1605 // NOTE: This legacy lookup endpoint is specific to 1606 // registry.terraform.io and not expected to work on any other 1607 // registry host. 1608 resp.Header().Set("Content-Type", "application/json") 1609 resp.WriteHeader(200) 1610 resp.Write([]byte(`{"namespace":"legacycorp"}`)) 1611 1612 default: 1613 resp.WriteHeader(404) 1614 resp.Write([]byte(`unknown namespace or provider type for direct lookup`)) 1615 } 1616 } 1617 1618 if len(pathParts) < 3 { 1619 resp.WriteHeader(404) 1620 resp.Write([]byte(`unexpected number of path parts`)) 1621 return 1622 } 1623 1624 if pathParts[2] == "versions" { 1625 if len(pathParts) != 3 { 1626 resp.WriteHeader(404) 1627 resp.Write([]byte(`extraneous path parts`)) 1628 return 1629 } 1630 1631 switch pathParts[0] + "/" + pathParts[1] { 1632 case "awesomesauce/happycloud": 1633 resp.Header().Set("Content-Type", "application/json") 1634 resp.WriteHeader(200) 1635 // Note that these version numbers are intentionally misordered 1636 // so we can test that the client-side code places them in the 1637 // correct order (lowest precedence first). 1638 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"]}]}`)) 1639 case "weaksauce/unsupported-protocol": 1640 resp.Header().Set("Content-Type", "application/json") 1641 resp.WriteHeader(200) 1642 resp.Write([]byte(`{"versions":[{"version":"0.1.0","protocols":["0.1"]}]}`)) 1643 case "weaksauce/no-versions": 1644 resp.Header().Set("Content-Type", "application/json") 1645 resp.WriteHeader(200) 1646 resp.Write([]byte(`{"versions":[]}`)) 1647 default: 1648 resp.WriteHeader(404) 1649 resp.Write([]byte(`unknown namespace or provider type`)) 1650 } 1651 return 1652 } 1653 1654 if len(pathParts) == 6 && pathParts[3] == "download" { 1655 switch pathParts[0] + "/" + pathParts[1] { 1656 case "awesomesauce/happycloud": 1657 if pathParts[4] == "nonexist" { 1658 resp.WriteHeader(404) 1659 resp.Write([]byte(`unsupported OS`)) 1660 return 1661 } 1662 version := pathParts[2] 1663 body := map[string]interface{}{ 1664 "protocols": []string{"99.0"}, 1665 "os": pathParts[4], 1666 "arch": pathParts[5], 1667 "filename": "happycloud_" + version + ".zip", 1668 "shasum": "000000000000000000000000000000000000000000000000000000000000f00d", 1669 "download_url": "/pkg/awesomesauce/happycloud_" + version + ".zip", 1670 "shasums_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS", 1671 "shasums_signature_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS.sig", 1672 "signing_keys": map[string]interface{}{ 1673 "gpg_public_keys": []map[string]interface{}{ 1674 { 1675 "ascii_armor": getproviders.HashicorpPublicKey, 1676 }, 1677 }, 1678 }, 1679 } 1680 enc, err := json.Marshal(body) 1681 if err != nil { 1682 resp.WriteHeader(500) 1683 resp.Write([]byte("failed to encode body")) 1684 } 1685 resp.Header().Set("Content-Type", "application/json") 1686 resp.WriteHeader(200) 1687 resp.Write(enc) 1688 case "weaksauce/unsupported-protocol": 1689 var protocols []string 1690 version := pathParts[2] 1691 switch version { 1692 case "0.1.0": 1693 protocols = []string{"1.0"} 1694 case "2.0.0": 1695 protocols = []string{"99.0"} 1696 default: 1697 protocols = []string{"5.0"} 1698 } 1699 1700 body := map[string]interface{}{ 1701 "protocols": protocols, 1702 "os": pathParts[4], 1703 "arch": pathParts[5], 1704 "filename": "happycloud_" + version + ".zip", 1705 "shasum": "000000000000000000000000000000000000000000000000000000000000f00d", 1706 "download_url": "/pkg/awesomesauce/happycloud_" + version + ".zip", 1707 "shasums_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS", 1708 "shasums_signature_url": "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS.sig", 1709 "signing_keys": map[string]interface{}{ 1710 "gpg_public_keys": []map[string]interface{}{ 1711 { 1712 "ascii_armor": getproviders.HashicorpPublicKey, 1713 }, 1714 }, 1715 }, 1716 } 1717 enc, err := json.Marshal(body) 1718 if err != nil { 1719 resp.WriteHeader(500) 1720 resp.Write([]byte("failed to encode body")) 1721 } 1722 resp.Header().Set("Content-Type", "application/json") 1723 resp.WriteHeader(200) 1724 resp.Write(enc) 1725 default: 1726 resp.WriteHeader(404) 1727 resp.Write([]byte(`unknown namespace/provider/version/architecture`)) 1728 } 1729 return 1730 } 1731 1732 resp.WriteHeader(404) 1733 resp.Write([]byte(`unrecognized path scheme`)) 1734 } 1735 1736 // In order to be able to compare the recorded temp dir paths, we need to 1737 // normalize the path to match what the installer would report. 1738 func tmpDir(t *testing.T) string { 1739 d := t.TempDir() 1740 unlinked, err := filepath.EvalSymlinks(d) 1741 if err != nil { 1742 t.Fatal(err) 1743 } 1744 return filepath.Clean(unlinked) 1745 }