github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/providercache/installer_test.go (about)

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