github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/providercache/installer_test.go (about)

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