github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/providercache/installer_test.go (about)

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