2020-04-23 14:21:56 +02:00
package providercache
import (
"context"
2020-05-11 19:49:12 +02:00
"encoding/json"
2020-04-23 14:21:56 +02:00
"io/ioutil"
2020-05-11 19:49:12 +02:00
"log"
"net/http"
"net/http/httptest"
2020-04-23 14:21:56 +02:00
"os"
2020-10-20 23:53:53 +02:00
"path/filepath"
2020-04-23 14:21:56 +02:00
"strings"
"testing"
2021-02-09 17:08:49 +01:00
"github.com/apparentlymart/go-versions/versions"
2020-10-20 23:53:53 +02:00
"github.com/apparentlymart/go-versions/versions/constraints"
"github.com/davecgh/go-spew/spew"
2020-07-07 20:46:23 +02:00
"github.com/google/go-cmp/cmp"
2020-05-11 19:49:12 +02:00
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/disco"
2021-05-17 21:00:50 +02:00
"github.com/hashicorp/terraform/internal/addrs"
2020-10-03 01:41:56 +02:00
"github.com/hashicorp/terraform/internal/depsfile"
2020-04-23 14:21:56 +02:00
"github.com/hashicorp/terraform/internal/getproviders"
)
2020-10-20 23:53:53 +02:00
func TestEnsureProviderVersions ( t * testing . T ) {
// This is a sort of hybrid between table-driven and imperative-style
// testing, because the overall sequence of steps is the same for all
// of the test cases but the setup and verification have enough different
// permutations that it ends up being more concise to express them as
// normal code.
type Test struct {
Source getproviders . Source
Prepare func ( * testing . T , * Installer , * Dir )
LockFile string
Reqs getproviders . Requirements
Mode InstallMode
Check func ( * testing . T , * Dir , * depsfile . Locks )
WantErr string
WantEvents func ( * Installer , * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem
}
// noProvider is just the zero value of addrs.Provider, which we're
// using in this test as the key for installer events that are not
// specific to a particular provider.
var noProvider addrs . Provider
beepProvider := addrs . MustParseProviderSourceString ( "example.com/foo/beep" )
beepProviderDir := getproviders . PackageLocalDir ( "testdata/beep-provider" )
fakePlatform := getproviders . Platform { OS : "bleep" , Arch : "bloop" }
wrongPlatform := getproviders . Platform { OS : "wrong" , Arch : "wrong" }
beepProviderHash := getproviders . HashScheme1 . New ( "2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" )
terraformProvider := addrs . MustParseProviderSourceString ( "terraform.io/builtin/terraform" )
tests := map [ string ] Test {
"no dependencies" : {
Mode : InstallNewProvidersOnly ,
Check : func ( t * testing . T , dir * Dir , locks * depsfile . Locks ) {
if allCached := dir . AllAvailablePackages ( ) ; len ( allCached ) != 0 {
t . Errorf ( "unexpected cache directory entries\n%s" , spew . Sdump ( allCached ) )
}
if allLocked := locks . AllProviders ( ) ; len ( allLocked ) != 0 {
t . Errorf ( "unexpected provider lock entries\n%s" , spew . Sdump ( allLocked ) )
}
} ,
WantEvents : func ( * Installer , * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints ( nil ) ,
} ,
} ,
}
} ,
} ,
"successful initial install of one provider" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "1.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.1.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
Check : func ( t * testing . T , dir * Dir , locks * depsfile . Locks ) {
if allCached := dir . AllAvailablePackages ( ) ; len ( allCached ) != 1 {
t . Errorf ( "wrong number of cache directory entries; want only one\n%s" , spew . Sdump ( allCached ) )
}
if allLocked := locks . AllProviders ( ) ; len ( allLocked ) != 1 {
t . Errorf ( "wrong number of provider lock entries; want only one\n%s" , spew . Sdump ( allLocked ) )
}
gotLock := locks . Provider ( beepProvider )
wantLock := depsfile . NewProviderLock (
beepProvider ,
getproviders . MustParseVersion ( "2.1.0" ) ,
getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
[ ] getproviders . Hash { beepProviderHash } ,
)
if diff := cmp . Diff ( wantLock , gotLock , depsfile . ProviderLockComparer ) ; diff != "" {
t . Errorf ( "wrong lock entry\n%s" , diff )
}
gotEntry := dir . ProviderLatestVersion ( beepProvider )
wantEntry := & CachedProvider {
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.1.0" ) ,
PackageDir : filepath . Join ( dir . BasePath ( ) , "example.com/foo/beep/2.1.0/bleep_bloop" ) ,
}
if diff := cmp . Diff ( wantEntry , gotEntry ) ; diff != "" {
t . Errorf ( "wrong cache entry\n%s" , diff )
}
} ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
} ,
{
Event : "ProvidersFetched" ,
Args : map [ addrs . Provider ] * getproviders . PackageAuthenticationResult {
beepProvider : nil ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 2.0.0" , false } ,
} ,
{
Event : "QueryPackagesSuccess" ,
Provider : beepProvider ,
Args : "2.1.0" ,
} ,
{
Event : "FetchPackageMeta" ,
Provider : beepProvider ,
Args : "2.1.0" ,
} ,
{
Event : "FetchPackageBegin" ,
Provider : beepProvider ,
Args : struct {
Version string
Location getproviders . PackageLocation
} { "2.1.0" , beepProviderDir } ,
} ,
{
Event : "FetchPackageSuccess" ,
Provider : beepProvider ,
Args : struct {
Version string
LocalDir string
AuthResult string
} {
"2.1.0" ,
filepath . Join ( dir . BasePath ( ) , "example.com/foo/beep/2.1.0/bleep_bloop" ) ,
"unauthenticated" ,
} ,
} ,
} ,
}
} ,
} ,
"successful initial install of one provider through a cold global cache" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.1.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
Prepare : func ( t * testing . T , inst * Installer , dir * Dir ) {
2020-11-20 22:32:08 +01:00
globalCacheDirPath := tmpDir ( t )
2020-10-20 23:53:53 +02:00
globalCacheDir := NewDirWithPlatform ( globalCacheDirPath , fakePlatform )
inst . SetGlobalCacheDir ( globalCacheDir )
} ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
Check : func ( t * testing . T , dir * Dir , locks * depsfile . Locks ) {
if allCached := dir . AllAvailablePackages ( ) ; len ( allCached ) != 1 {
t . Errorf ( "wrong number of cache directory entries; want only one\n%s" , spew . Sdump ( allCached ) )
}
if allLocked := locks . AllProviders ( ) ; len ( allLocked ) != 1 {
t . Errorf ( "wrong number of provider lock entries; want only one\n%s" , spew . Sdump ( allLocked ) )
}
gotLock := locks . Provider ( beepProvider )
wantLock := depsfile . NewProviderLock (
beepProvider ,
getproviders . MustParseVersion ( "2.1.0" ) ,
getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
[ ] getproviders . Hash { beepProviderHash } ,
)
if diff := cmp . Diff ( wantLock , gotLock , depsfile . ProviderLockComparer ) ; diff != "" {
t . Errorf ( "wrong lock entry\n%s" , diff )
}
gotEntry := dir . ProviderLatestVersion ( beepProvider )
wantEntry := & CachedProvider {
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.1.0" ) ,
PackageDir : filepath . Join ( dir . BasePath ( ) , "example.com/foo/beep/2.1.0/bleep_bloop" ) ,
}
if diff := cmp . Diff ( wantEntry , gotEntry ) ; diff != "" {
t . Errorf ( "wrong cache entry\n%s" , diff )
}
} ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
} ,
{
Event : "ProvidersFetched" ,
Args : map [ addrs . Provider ] * getproviders . PackageAuthenticationResult {
beepProvider : nil ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 2.0.0" , false } ,
} ,
{
Event : "QueryPackagesSuccess" ,
Provider : beepProvider ,
Args : "2.1.0" ,
} ,
{
Event : "FetchPackageMeta" ,
Provider : beepProvider ,
Args : "2.1.0" ,
} ,
{
Event : "FetchPackageBegin" ,
Provider : beepProvider ,
Args : struct {
Version string
Location getproviders . PackageLocation
} { "2.1.0" , beepProviderDir } ,
} ,
{
Event : "FetchPackageSuccess" ,
Provider : beepProvider ,
Args : struct {
Version string
LocalDir string
AuthResult string
} {
"2.1.0" ,
// NOTE: With global cache enabled, the fetch
// goes into the global cache dir and
// we then to it from the local cache dir.
filepath . Join ( inst . globalCacheDir . BasePath ( ) , "example.com/foo/beep/2.1.0/bleep_bloop" ) ,
"unauthenticated" ,
} ,
} ,
} ,
}
} ,
} ,
"successful initial install of one provider through a warm global cache" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.1.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
Prepare : func ( t * testing . T , inst * Installer , dir * Dir ) {
2020-11-20 22:32:08 +01:00
globalCacheDirPath := tmpDir ( t )
2020-10-20 23:53:53 +02:00
globalCacheDir := NewDirWithPlatform ( globalCacheDirPath , fakePlatform )
_ , err := globalCacheDir . InstallPackage (
context . Background ( ) ,
getproviders . PackageMeta {
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.1.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
nil ,
)
if err != nil {
t . Fatalf ( "failed to populate global cache: %s" , err )
}
inst . SetGlobalCacheDir ( globalCacheDir )
} ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
Check : func ( t * testing . T , dir * Dir , locks * depsfile . Locks ) {
if allCached := dir . AllAvailablePackages ( ) ; len ( allCached ) != 1 {
t . Errorf ( "wrong number of cache directory entries; want only one\n%s" , spew . Sdump ( allCached ) )
}
if allLocked := locks . AllProviders ( ) ; len ( allLocked ) != 1 {
t . Errorf ( "wrong number of provider lock entries; want only one\n%s" , spew . Sdump ( allLocked ) )
}
gotLock := locks . Provider ( beepProvider )
wantLock := depsfile . NewProviderLock (
beepProvider ,
getproviders . MustParseVersion ( "2.1.0" ) ,
getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
[ ] getproviders . Hash { beepProviderHash } ,
)
if diff := cmp . Diff ( wantLock , gotLock , depsfile . ProviderLockComparer ) ; diff != "" {
t . Errorf ( "wrong lock entry\n%s" , diff )
}
gotEntry := dir . ProviderLatestVersion ( beepProvider )
wantEntry := & CachedProvider {
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.1.0" ) ,
PackageDir : filepath . Join ( dir . BasePath ( ) , "example.com/foo/beep/2.1.0/bleep_bloop" ) ,
}
if diff := cmp . Diff ( wantEntry , gotEntry ) ; diff != "" {
t . Errorf ( "wrong cache entry\n%s" , diff )
}
} ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 2.0.0" , false } ,
} ,
{
Event : "QueryPackagesSuccess" ,
Provider : beepProvider ,
Args : "2.1.0" ,
} ,
{
Event : "LinkFromCacheBegin" ,
Provider : beepProvider ,
Args : struct {
Version string
CacheRoot string
} {
"2.1.0" ,
inst . globalCacheDir . BasePath ( ) ,
} ,
} ,
{
Event : "LinkFromCacheSuccess" ,
Provider : beepProvider ,
Args : struct {
Version string
LocalDir string
} {
"2.1.0" ,
filepath . Join ( dir . BasePath ( ) , "/example.com/foo/beep/2.1.0/bleep_bloop" ) ,
} ,
} ,
} ,
}
} ,
} ,
"successful reinstall of one previously-locked provider" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "1.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.1.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
LockFile : `
provider "example.com/foo/beep" {
version = "2.0.0"
constraints = ">= 2.0.0"
hashes = [
"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" ,
]
}
` ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
Check : func ( t * testing . T , dir * Dir , locks * depsfile . Locks ) {
if allCached := dir . AllAvailablePackages ( ) ; len ( allCached ) != 1 {
t . Errorf ( "wrong number of cache directory entries; want only one\n%s" , spew . Sdump ( allCached ) )
}
if allLocked := locks . AllProviders ( ) ; len ( allLocked ) != 1 {
t . Errorf ( "wrong number of provider lock entries; want only one\n%s" , spew . Sdump ( allLocked ) )
}
gotLock := locks . Provider ( beepProvider )
wantLock := depsfile . NewProviderLock (
beepProvider ,
getproviders . MustParseVersion ( "2.0.0" ) ,
getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
[ ] getproviders . Hash { "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" } ,
)
if diff := cmp . Diff ( wantLock , gotLock , depsfile . ProviderLockComparer ) ; diff != "" {
t . Errorf ( "wrong lock entry\n%s" , diff )
}
gotEntry := dir . ProviderLatestVersion ( beepProvider )
wantEntry := & CachedProvider {
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
PackageDir : filepath . Join ( dir . BasePath ( ) , "example.com/foo/beep/2.0.0/bleep_bloop" ) ,
}
if diff := cmp . Diff ( wantEntry , gotEntry ) ; diff != "" {
t . Errorf ( "wrong cache entry\n%s" , diff )
}
} ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
} ,
{
Event : "ProvidersFetched" ,
Args : map [ addrs . Provider ] * getproviders . PackageAuthenticationResult {
beepProvider : nil ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 2.0.0" , true } ,
} ,
{
Event : "QueryPackagesSuccess" ,
Provider : beepProvider ,
Args : "2.0.0" ,
} ,
{
Event : "FetchPackageMeta" ,
Provider : beepProvider ,
Args : "2.0.0" ,
} ,
{
Event : "FetchPackageBegin" ,
Provider : beepProvider ,
Args : struct {
Version string
Location getproviders . PackageLocation
} { "2.0.0" , beepProviderDir } ,
} ,
{
Event : "FetchPackageSuccess" ,
Provider : beepProvider ,
Args : struct {
Version string
LocalDir string
AuthResult string
} {
"2.0.0" ,
filepath . Join ( dir . BasePath ( ) , "example.com/foo/beep/2.0.0/bleep_bloop" ) ,
"unauthenticated" ,
} ,
} ,
} ,
}
} ,
} ,
2021-01-23 18:45:00 +01:00
"skipped install of one previously-locked and installed provider" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
LockFile : `
provider "example.com/foo/beep" {
version = "2.0.0"
constraints = ">= 2.0.0"
hashes = [
"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" ,
]
}
` ,
Prepare : func ( t * testing . T , inst * Installer , dir * Dir ) {
_ , err := dir . InstallPackage (
context . Background ( ) ,
getproviders . PackageMeta {
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
nil ,
)
if err != nil {
t . Fatalf ( "installation to the test dir failed: %s" , err )
}
} ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
Check : func ( t * testing . T , dir * Dir , locks * depsfile . Locks ) {
if allCached := dir . AllAvailablePackages ( ) ; len ( allCached ) != 1 {
t . Errorf ( "wrong number of cache directory entries; want only one\n%s" , spew . Sdump ( allCached ) )
}
if allLocked := locks . AllProviders ( ) ; len ( allLocked ) != 1 {
t . Errorf ( "wrong number of provider lock entries; want only one\n%s" , spew . Sdump ( allLocked ) )
}
gotLock := locks . Provider ( beepProvider )
wantLock := depsfile . NewProviderLock (
beepProvider ,
getproviders . MustParseVersion ( "2.0.0" ) ,
getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
[ ] getproviders . Hash { "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" } ,
)
if diff := cmp . Diff ( wantLock , gotLock , depsfile . ProviderLockComparer ) ; diff != "" {
t . Errorf ( "wrong lock entry\n%s" , diff )
}
gotEntry := dir . ProviderLatestVersion ( beepProvider )
wantEntry := & CachedProvider {
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
PackageDir : filepath . Join ( dir . BasePath ( ) , "example.com/foo/beep/2.0.0/bleep_bloop" ) ,
}
if diff := cmp . Diff ( wantEntry , gotEntry ) ; diff != "" {
t . Errorf ( "wrong cache entry\n%s" , diff )
}
} ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 2.0.0" , true } ,
} ,
{
Event : "QueryPackagesSuccess" ,
Provider : beepProvider ,
Args : "2.0.0" ,
} ,
2021-02-09 17:08:49 +01:00
{
Event : "ProviderAlreadyInstalled" ,
Provider : beepProvider ,
Args : versions . Version { Major : 2 , Minor : 0 , Patch : 0 } ,
} ,
2021-01-23 18:45:00 +01:00
} ,
}
} ,
} ,
2020-10-20 23:53:53 +02:00
"successful upgrade of one previously-locked provider" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "1.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.1.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
LockFile : `
provider "example.com/foo/beep" {
version = "2.0.0"
constraints = ">= 2.0.0"
hashes = [
"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" ,
]
}
` ,
Mode : InstallUpgrades ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
Check : func ( t * testing . T , dir * Dir , locks * depsfile . Locks ) {
if allCached := dir . AllAvailablePackages ( ) ; len ( allCached ) != 1 {
t . Errorf ( "wrong number of cache directory entries; want only one\n%s" , spew . Sdump ( allCached ) )
}
if allLocked := locks . AllProviders ( ) ; len ( allLocked ) != 1 {
t . Errorf ( "wrong number of provider lock entries; want only one\n%s" , spew . Sdump ( allLocked ) )
}
gotLock := locks . Provider ( beepProvider )
wantLock := depsfile . NewProviderLock (
beepProvider ,
getproviders . MustParseVersion ( "2.1.0" ) ,
getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
[ ] getproviders . Hash { "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" } ,
)
if diff := cmp . Diff ( wantLock , gotLock , depsfile . ProviderLockComparer ) ; diff != "" {
t . Errorf ( "wrong lock entry\n%s" , diff )
}
gotEntry := dir . ProviderLatestVersion ( beepProvider )
wantEntry := & CachedProvider {
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.1.0" ) ,
PackageDir : filepath . Join ( dir . BasePath ( ) , "example.com/foo/beep/2.1.0/bleep_bloop" ) ,
}
if diff := cmp . Diff ( wantEntry , gotEntry ) ; diff != "" {
t . Errorf ( "wrong cache entry\n%s" , diff )
}
} ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
} ,
{
Event : "ProvidersFetched" ,
Args : map [ addrs . Provider ] * getproviders . PackageAuthenticationResult {
beepProvider : nil ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 2.0.0" , false } ,
} ,
{
Event : "QueryPackagesSuccess" ,
Provider : beepProvider ,
Args : "2.1.0" ,
} ,
{
Event : "FetchPackageMeta" ,
Provider : beepProvider ,
Args : "2.1.0" ,
} ,
{
Event : "FetchPackageBegin" ,
Provider : beepProvider ,
Args : struct {
Version string
Location getproviders . PackageLocation
} { "2.1.0" , beepProviderDir } ,
} ,
{
Event : "FetchPackageSuccess" ,
Provider : beepProvider ,
Args : struct {
Version string
LocalDir string
AuthResult string
} {
"2.1.0" ,
filepath . Join ( dir . BasePath ( ) , "example.com/foo/beep/2.1.0/bleep_bloop" ) ,
"unauthenticated" ,
} ,
} ,
} ,
}
} ,
} ,
"successful install of a built-in provider" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta { } ,
nil ,
) ,
Prepare : func ( t * testing . T , inst * Installer , dir * Dir ) {
inst . SetBuiltInProviderTypes ( [ ] string { "terraform" } )
} ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
terraformProvider : nil ,
} ,
Check : func ( t * testing . T , dir * Dir , locks * depsfile . Locks ) {
// Built-in providers are neither included in the cache
// directory nor mentioned in the lock file, because they
// are compiled directly into the Terraform executable.
if allCached := dir . AllAvailablePackages ( ) ; len ( allCached ) != 0 {
t . Errorf ( "wrong number of cache directory entries; want none\n%s" , spew . Sdump ( allCached ) )
}
if allLocked := locks . AllProviders ( ) ; len ( allLocked ) != 0 {
t . Errorf ( "wrong number of provider lock entries; want none\n%s" , spew . Sdump ( allLocked ) )
}
} ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
terraformProvider : constraints . IntersectionSpec ( nil ) ,
} ,
} ,
} ,
terraformProvider : {
{
Event : "BuiltInProviderAvailable" ,
Provider : terraformProvider ,
} ,
} ,
}
} ,
} ,
"failed install of a non-existing built-in provider" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta { } ,
nil ,
) ,
Prepare : func ( t * testing . T , inst * Installer , dir * Dir ) {
// NOTE: We're intentionally not calling
// inst.SetBuiltInProviderTypes to make the "terraform"
// built-in provider available here, so requests for it
// should fail.
} ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
terraformProvider : nil ,
} ,
WantErr : ` some providers could not be installed :
- terraform . io / builtin / terraform : this Terraform release has no built - in provider named "terraform" ` ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
terraformProvider : constraints . IntersectionSpec ( nil ) ,
} ,
} ,
} ,
terraformProvider : {
{
Event : "BuiltInProviderFailure" ,
Provider : terraformProvider ,
Args : ` this Terraform release has no built-in provider named "terraform" ` ,
} ,
} ,
}
} ,
} ,
"failed install when a built-in provider has a version constraint" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta { } ,
nil ,
) ,
Prepare : func ( t * testing . T , inst * Installer , dir * Dir ) {
inst . SetBuiltInProviderTypes ( [ ] string { "terraform" } )
} ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
terraformProvider : getproviders . MustParseVersionConstraints ( ">= 1.0.0" ) ,
} ,
WantErr : ` some providers could not be installed :
- terraform . io / builtin / terraform : built - in providers do not support explicit version constraints ` ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
terraformProvider : getproviders . MustParseVersionConstraints ( ">= 1.0.0" ) ,
} ,
} ,
} ,
terraformProvider : {
{
Event : "BuiltInProviderFailure" ,
Provider : terraformProvider ,
Args : ` built-in providers do not support explicit version constraints ` ,
} ,
} ,
}
} ,
} ,
"locked version is excluded by new version constraint" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "1.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
LockFile : `
provider "example.com/foo/beep" {
version = "1.0.0"
constraints = ">= 1.0.0"
hashes = [
"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" ,
]
}
` ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
Check : func ( t * testing . T , dir * Dir , locks * depsfile . Locks ) {
if allCached := dir . AllAvailablePackages ( ) ; len ( allCached ) != 0 {
t . Errorf ( "wrong number of cache directory entries; want none\n%s" , spew . Sdump ( allCached ) )
}
if allLocked := locks . AllProviders ( ) ; len ( allLocked ) != 1 {
t . Errorf ( "wrong number of provider lock entries; want only one\n%s" , spew . Sdump ( allLocked ) )
}
gotLock := locks . Provider ( beepProvider )
wantLock := depsfile . NewProviderLock (
beepProvider ,
getproviders . MustParseVersion ( "1.0.0" ) ,
getproviders . MustParseVersionConstraints ( ">= 1.0.0" ) ,
[ ] getproviders . Hash { "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" } ,
)
if diff := cmp . Diff ( wantLock , gotLock , depsfile . ProviderLockComparer ) ; diff != "" {
t . Errorf ( "wrong lock entry\n%s" , diff )
}
} ,
WantErr : ` some providers could not be installed :
- 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 ` ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 2.0.0" , true } ,
} ,
{
Event : "QueryPackagesFailure" ,
Provider : beepProvider ,
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 ` ,
} ,
} ,
}
} ,
} ,
"locked version is no longer available" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "1.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "2.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
LockFile : `
provider "example.com/foo/beep" {
version = "1.2.0"
constraints = ">= 1.0.0"
hashes = [
"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" ,
]
}
` ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 1.0.0" ) ,
} ,
Check : func ( t * testing . T , dir * Dir , locks * depsfile . Locks ) {
if allCached := dir . AllAvailablePackages ( ) ; len ( allCached ) != 0 {
t . Errorf ( "wrong number of cache directory entries; want none\n%s" , spew . Sdump ( allCached ) )
}
if allLocked := locks . AllProviders ( ) ; len ( allLocked ) != 1 {
t . Errorf ( "wrong number of provider lock entries; want only one\n%s" , spew . Sdump ( allLocked ) )
}
gotLock := locks . Provider ( beepProvider )
wantLock := depsfile . NewProviderLock (
beepProvider ,
getproviders . MustParseVersion ( "1.2.0" ) ,
getproviders . MustParseVersionConstraints ( ">= 1.0.0" ) ,
[ ] getproviders . Hash { "h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84=" } ,
)
if diff := cmp . Diff ( wantLock , gotLock , depsfile . ProviderLockComparer ) ; diff != "" {
t . Errorf ( "wrong lock entry\n%s" , diff )
}
} ,
WantErr : ` some providers could not be installed :
- example . com / foo / beep : the previously - selected version 1.2 .0 is no longer available ` ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 1.0.0" ) ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 1.0.0" , true } ,
} ,
{
Event : "QueryPackagesFailure" ,
Provider : beepProvider ,
Args : ` the previously-selected version 1.2.0 is no longer available ` ,
} ,
} ,
}
} ,
} ,
"no versions match the version constraint" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "1.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
WantErr : ` some providers could not be installed :
- example . com / foo / beep : no available releases match the given constraints >= 2.0 .0 ` ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 2.0.0" ) ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 2.0.0" , false } ,
} ,
{
Event : "QueryPackagesFailure" ,
Provider : beepProvider ,
Args : ` no available releases match the given constraints >= 2.0.0 ` ,
} ,
} ,
}
} ,
} ,
"version exists but doesn't support the current platform" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "1.0.0" ) ,
TargetPlatform : wrongPlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 1.0.0" ) ,
} ,
WantErr : ` some providers could not be installed :
- example . com / foo / beep : provider example . com / foo / beep 1.0 .0 is not available for bleep_bloop ` ,
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 1.0.0" ) ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 1.0.0" , false } ,
} ,
{
Event : "QueryPackagesSuccess" ,
Provider : beepProvider ,
Args : "1.0.0" ,
} ,
{
Event : "FetchPackageMeta" ,
Provider : beepProvider ,
Args : "1.0.0" ,
} ,
{
Event : "FetchPackageFailure" ,
Provider : beepProvider ,
Args : struct {
Version string
Error string
} {
"1.0.0" ,
"provider example.com/foo/beep 1.0.0 is not available for bleep_bloop" ,
} ,
} ,
} ,
}
} ,
} ,
"available package doesn't match locked hash" : {
Source : getproviders . NewMockSource (
[ ] getproviders . PackageMeta {
{
Provider : beepProvider ,
Version : getproviders . MustParseVersion ( "1.0.0" ) ,
TargetPlatform : fakePlatform ,
Location : beepProviderDir ,
} ,
} ,
nil ,
) ,
LockFile : `
provider "example.com/foo/beep" {
version = "1.0.0"
constraints = ">= 1.0.0"
hashes = [
"h1:does-not-match" ,
]
}
` ,
Mode : InstallNewProvidersOnly ,
Reqs : getproviders . Requirements {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 1.0.0" ) ,
} ,
2020-10-28 00:44:37 +01:00
WantErr : ` some providers could not be installed :
- 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 ) ` ,
2020-10-20 23:53:53 +02:00
WantEvents : func ( inst * Installer , dir * Dir ) map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
return map [ addrs . Provider ] [ ] * testInstallerEventLogItem {
noProvider : {
{
Event : "PendingProviders" ,
Args : map [ addrs . Provider ] getproviders . VersionConstraints {
beepProvider : getproviders . MustParseVersionConstraints ( ">= 1.0.0" ) ,
} ,
} ,
} ,
beepProvider : {
{
Event : "QueryPackagesBegin" ,
Provider : beepProvider ,
Args : struct {
Constraints string
Locked bool
} { ">= 1.0.0" , true } ,
} ,
{
Event : "QueryPackagesSuccess" ,
Provider : beepProvider ,
Args : "1.0.0" ,
} ,
{
Event : "FetchPackageMeta" ,
Provider : beepProvider ,
Args : "1.0.0" ,
} ,
{
Event : "FetchPackageBegin" ,
Provider : beepProvider ,
Args : struct {
Version string
Location getproviders . PackageLocation
} { "1.0.0" , beepProviderDir } ,
} ,
{
2020-10-28 00:44:37 +01:00
Event : "FetchPackageFailure" ,
2020-10-20 23:53:53 +02:00
Provider : beepProvider ,
Args : struct {
2020-10-28 00:44:37 +01:00
Version string
Error string
2020-10-20 23:53:53 +02:00
} {
"1.0.0" ,
2020-10-28 00:44:37 +01:00
` 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) ` ,
2020-10-20 23:53:53 +02:00
} ,
} ,
} ,
}
} ,
} ,
}
ctx := context . Background ( )
for name , test := range tests {
t . Run ( name , func ( t * testing . T ) {
if test . Check == nil && test . WantEvents == nil && test . WantErr == "" {
t . Fatalf ( "invalid test: must set at least one of Check, WantEvents, or WantErr" )
}
2020-11-20 22:32:08 +01:00
outputDir := NewDirWithPlatform ( tmpDir ( t ) , fakePlatform )
2020-10-20 23:53:53 +02:00
source := test . Source
if source == nil {
source = getproviders . NewMockSource ( nil , nil )
}
inst := NewInstaller ( outputDir , source )
if test . Prepare != nil {
test . Prepare ( t , inst , outputDir )
}
locks , lockDiags := depsfile . LoadLocksFromBytes ( [ ] byte ( test . LockFile ) , "test.lock.hcl" )
if lockDiags . HasErrors ( ) {
t . Fatalf ( "invalid lock file: %s" , lockDiags . Err ( ) . Error ( ) )
}
providerEvents := make ( map [ addrs . Provider ] [ ] * testInstallerEventLogItem )
eventsCh := make ( chan * testInstallerEventLogItem )
var newLocks * depsfile . Locks
var instErr error
go func ( ch chan * testInstallerEventLogItem ) {
events := installerLogEventsForTests ( ch )
ctx := events . OnContext ( ctx )
newLocks , instErr = inst . EnsureProviderVersions ( ctx , locks , test . Reqs , test . Mode )
close ( eventsCh ) // exits the event loop below
} ( eventsCh )
for evt := range eventsCh {
// We do the event collection in the main goroutine, rather than
// running the installer itself in the main goroutine, so that
// we can safely t.Log in here without violating the testing.T
// usage rules.
if evt . Provider == ( addrs . Provider { } ) {
t . Logf ( "%s(%s)" , evt . Event , spew . Sdump ( evt . Args ) )
} else {
t . Logf ( "%s: %s(%s)" , evt . Provider , evt . Event , spew . Sdump ( evt . Args ) )
}
providerEvents [ evt . Provider ] = append ( providerEvents [ evt . Provider ] , evt )
}
if test . WantErr != "" {
if instErr == nil {
t . Errorf ( "succeeded; want error\nwant: %s" , test . WantErr )
} else if got , want := instErr . Error ( ) , test . WantErr ; got != want {
t . Errorf ( "wrong error\ngot: %s\nwant: %s" , got , want )
}
} else if instErr != nil {
t . Errorf ( "unexpected error\ngot: %s" , instErr . Error ( ) )
}
if test . Check != nil {
test . Check ( t , outputDir , newLocks )
}
if test . WantEvents != nil {
wantEvents := test . WantEvents ( inst , outputDir )
if diff := cmp . Diff ( wantEvents , providerEvents ) ; diff != "" {
t . Errorf ( "wrong installer events\n%s" , diff )
}
}
} )
}
}
2020-07-07 20:46:23 +02:00
func TestEnsureProviderVersions_local_source ( t * testing . T ) {
// create filesystem source using the test provider cache dir
source := getproviders . NewFilesystemMirrorSource ( "testdata/cachedir" )
// create a temporary workdir
tmpDirPath , err := ioutil . TempDir ( "" , "terraform-test-providercache" )
if err != nil {
t . Fatal ( err )
}
defer os . RemoveAll ( tmpDirPath )
// set up the installer using the temporary directory and filesystem source
platform := getproviders . Platform { OS : "linux" , Arch : "amd64" }
dir := NewDirWithPlatform ( tmpDirPath , platform )
installer := NewInstaller ( dir , source )
tests := map [ string ] struct {
2020-10-03 01:41:56 +02:00
provider string
version string
wantHash getproviders . Hash // getproviders.NilHash if not expected to be installed
err string
2020-07-07 20:46:23 +02:00
} {
"install-unpacked" : {
2020-10-03 01:41:56 +02:00
provider : "null" ,
version : "2.0.0" ,
wantHash : getproviders . HashScheme1 . New ( "qjsREM4DqEWECD43FcPqddZ9oxCG+IaMTxvWPciS05g=" ) ,
2020-07-07 20:46:23 +02:00
} ,
"invalid-zip-file" : {
2020-10-03 01:41:56 +02:00
provider : "null" ,
version : "2.1.0" ,
wantHash : getproviders . NilHash ,
err : "zip: not a valid zip file" ,
2020-07-07 20:46:23 +02:00
} ,
"version-constraint-unmet" : {
2020-10-03 01:41:56 +02:00
provider : "null" ,
version : "2.2.0" ,
wantHash : getproviders . NilHash ,
err : "no available releases match the given constraints 2.2.0" ,
2020-07-07 20:46:23 +02:00
} ,
"missing-executable" : {
2020-10-03 01:41:56 +02:00
provider : "missing/executable" ,
version : "2.0.0" ,
wantHash : getproviders . NilHash , // installation fails for a provider with no executable
err : "provider binary not found: could not find executable file starting with terraform-provider-executable" ,
2020-07-07 20:46:23 +02:00
} ,
}
for name , test := range tests {
t . Run ( name , func ( t * testing . T ) {
ctx := context . TODO ( )
provider := addrs . MustParseProviderSourceString ( test . provider )
versionConstraint := getproviders . MustParseVersionConstraints ( test . version )
version := getproviders . MustParseVersion ( test . version )
reqs := getproviders . Requirements {
provider : versionConstraint ,
}
2020-10-03 01:41:56 +02:00
newLocks , err := installer . EnsureProviderVersions ( ctx , depsfile . NewLocks ( ) , reqs , InstallNewProvidersOnly )
gotProviderlocks := newLocks . AllProviders ( )
wantProviderLocks := map [ addrs . Provider ] * depsfile . ProviderLock {
provider : depsfile . NewProviderLock (
provider ,
version ,
getproviders . MustParseVersionConstraints ( "= 2.0.0" ) ,
[ ] getproviders . Hash {
test . wantHash ,
} ,
) ,
}
if test . wantHash == getproviders . NilHash {
wantProviderLocks = map [ addrs . Provider ] * depsfile . ProviderLock { }
}
2020-07-07 20:46:23 +02:00
2020-10-03 01:41:56 +02:00
if diff := cmp . Diff ( wantProviderLocks , gotProviderlocks , depsfile . ProviderLockComparer ) ; diff != "" {
2020-07-07 20:46:23 +02:00
t . Errorf ( "wrong selected\n%s" , diff )
}
if test . err == "" && err == nil {
return
}
switch err := err . ( type ) {
case InstallerError :
providerError , ok := err . ProviderErrors [ provider ]
if ! ok {
t . Fatalf ( "did not get error for provider %s" , provider )
}
if got := providerError . Error ( ) ; got != test . err {
t . Fatalf ( "wrong result\ngot: %s\nwant: %s\n" , got , test . err )
}
default :
t . Fatalf ( "wrong error type. Expected InstallerError, got %T" , err )
}
} )
}
}
2020-05-11 19:49:12 +02:00
// This test only verifies protocol errors and does not try for successfull
// installation (at the time of writing, the test files aren't signed so the
// signature verification fails); that's left to the e2e tests.
func TestEnsureProviderVersions_protocol_errors ( t * testing . T ) {
source , _ , close := testRegistrySource ( t )
defer close ( )
2020-04-23 14:21:56 +02:00
// create a temporary workdir
tmpDirPath , err := ioutil . TempDir ( "" , "terraform-test-providercache" )
if err != nil {
t . Fatal ( err )
}
defer os . RemoveAll ( tmpDirPath )
2020-05-11 19:49:12 +02:00
version0 := getproviders . MustParseVersionConstraints ( "0.1.0" ) // supports protocol version 1.0
version1 := getproviders . MustParseVersion ( "1.2.0" ) // this is the expected result in tests with a match
version2 := getproviders . MustParseVersionConstraints ( "2.0" ) // supports protocol version 99
2020-04-23 14:21:56 +02:00
// set up the installer using the temporary directory and mock source
2020-05-11 19:49:12 +02:00
platform := getproviders . Platform { OS : "gameboy" , Arch : "lr35902" }
2020-04-23 14:33:28 +02:00
dir := NewDirWithPlatform ( tmpDirPath , platform )
2020-04-23 14:21:56 +02:00
installer := NewInstaller ( dir , source )
2020-05-11 19:49:12 +02:00
tests := map [ string ] struct {
provider addrs . Provider
inputVersion getproviders . VersionConstraints
wantVersion getproviders . Version
} {
"too old" : {
addrs . MustParseProviderSourceString ( "example.com/awesomesauce/happycloud" ) ,
version0 ,
version1 ,
} ,
"too new" : {
addrs . MustParseProviderSourceString ( "example.com/awesomesauce/happycloud" ) ,
version2 ,
version1 ,
} ,
"unsupported" : {
addrs . MustParseProviderSourceString ( "example.com/weaksauce/unsupported-protocol" ) ,
version0 ,
getproviders . UnspecifiedVersion ,
} ,
2020-04-23 14:21:56 +02:00
}
2020-05-11 19:49:12 +02:00
for name , test := range tests {
t . Run ( name , func ( t * testing . T ) {
reqs := getproviders . Requirements {
test . provider : test . inputVersion ,
}
ctx := context . TODO ( )
2020-10-03 01:41:56 +02:00
_ , err := installer . EnsureProviderVersions ( ctx , depsfile . NewLocks ( ) , reqs , InstallNewProvidersOnly )
2020-05-11 19:49:12 +02:00
switch err := err . ( type ) {
case nil :
t . Fatalf ( "expected error, got success" )
case InstallerError :
providerError , ok := err . ProviderErrors [ test . provider ]
if ! ok {
t . Fatalf ( "did not get error for provider %s" , test . provider )
}
switch providerError := providerError . ( type ) {
case getproviders . ErrProtocolNotSupported :
if ! providerError . Suggestion . Same ( test . wantVersion ) {
t . Fatalf ( "wrong result\ngot: %s\nwant: %s\n" , providerError . Suggestion , test . wantVersion )
}
default :
t . Fatalf ( "wrong error type. Expected ErrProtocolNotSupported, got %T" , err )
}
default :
t . Fatalf ( "wrong error type. Expected InstallerError, got %T" , err )
}
} )
2020-04-23 14:21:56 +02:00
}
2020-05-11 19:49:12 +02:00
}
// testServices starts up a local HTTP server running a fake provider registry
// service and returns a service discovery object pre-configured to consider
// the host "example.com" to be served by the fake registry service.
//
// The returned discovery object also knows the hostname "not.example.com"
// which does not have a provider registry at all and "too-new.example.com"
// which has a "providers.v99" service that is inoperable but could be useful
// to test the error reporting for detecting an unsupported protocol version.
// It also knows fails.example.com but it refers to an endpoint that doesn't
// correctly speak HTTP, to simulate a protocol error.
//
// The second return value is a function to call at the end of a test function
// to shut down the test server. After you call that function, the discovery
// object becomes useless.
func testServices ( t * testing . T ) ( services * disco . Disco , baseURL string , cleanup func ( ) ) {
server := httptest . NewServer ( http . HandlerFunc ( fakeRegistryHandler ) )
services = disco . New ( )
services . ForceHostServices ( svchost . Hostname ( "example.com" ) , map [ string ] interface { } {
"providers.v1" : server . URL + "/providers/v1/" ,
} )
services . ForceHostServices ( svchost . Hostname ( "not.example.com" ) , map [ string ] interface { } { } )
services . ForceHostServices ( svchost . Hostname ( "too-new.example.com" ) , map [ string ] interface { } {
// This service doesn't actually work; it's here only to be
// detected as "too new" by the discovery logic.
"providers.v99" : server . URL + "/providers/v99/" ,
} )
services . ForceHostServices ( svchost . Hostname ( "fails.example.com" ) , map [ string ] interface { } {
"providers.v1" : server . URL + "/fails-immediately/" ,
} )
// We'll also permit registry.terraform.io here just because it's our
// default and has some unique features that are not allowed on any other
// hostname. It behaves the same as example.com, which should be preferred
// if you're not testing something specific to the default registry in order
// to ensure that most things are hostname-agnostic.
services . ForceHostServices ( svchost . Hostname ( "registry.terraform.io" ) , map [ string ] interface { } {
"providers.v1" : server . URL + "/providers/v1/" ,
} )
return services , server . URL , func ( ) {
server . Close ( )
2020-04-23 14:21:56 +02:00
}
2020-05-11 19:49:12 +02:00
}
2020-04-23 14:21:56 +02:00
2020-05-11 19:49:12 +02:00
// testRegistrySource is a wrapper around testServices that uses the created
// discovery object to produce a Source instance that is ready to use with the
// fake registry services.
//
// As with testServices, the second return value is a function to call at the end
// of your test in order to shut down the test server.
func testRegistrySource ( t * testing . T ) ( source * getproviders . RegistrySource , baseURL string , cleanup func ( ) ) {
services , baseURL , close := testServices ( t )
source = getproviders . NewRegistrySource ( services )
return source , baseURL , close
}
2020-04-23 14:21:56 +02:00
2020-05-11 19:49:12 +02:00
func fakeRegistryHandler ( resp http . ResponseWriter , req * http . Request ) {
path := req . URL . EscapedPath ( )
if strings . HasPrefix ( path , "/fails-immediately/" ) {
// Here we take over the socket and just close it immediately, to
// simulate one possible way a server might not be an HTTP server.
hijacker , ok := resp . ( http . Hijacker )
if ! ok {
// Not hijackable, so we'll just fail normally.
// If this happens, tests relying on this will fail.
resp . WriteHeader ( 500 )
resp . Write ( [ ] byte ( ` cannot hijack ` ) )
return
}
conn , _ , err := hijacker . Hijack ( )
if err != nil {
resp . WriteHeader ( 500 )
resp . Write ( [ ] byte ( ` hijack failed ` ) )
return
}
conn . Close ( )
return
2020-04-23 14:21:56 +02:00
}
2020-05-11 19:49:12 +02:00
if strings . HasPrefix ( path , "/pkg/" ) {
switch path {
case "/pkg/awesomesauce/happycloud_1.2.0.zip" :
resp . Write ( [ ] byte ( "some zip file" ) )
case "/pkg/awesomesauce/happycloud_1.2.0_SHA256SUMS" :
resp . Write ( [ ] byte ( "000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n" ) )
case "/pkg/awesomesauce/happycloud_1.2.0_SHA256SUMS.sig" :
resp . Write ( [ ] byte ( "GPG signature" ) )
default :
resp . WriteHeader ( 404 )
resp . Write ( [ ] byte ( "unknown package file download" ) )
}
return
2020-04-23 14:21:56 +02:00
}
2020-05-11 19:49:12 +02:00
if ! strings . HasPrefix ( path , "/providers/v1/" ) {
resp . WriteHeader ( 404 )
resp . Write ( [ ] byte ( ` not a provider registry endpoint ` ) )
return
2020-04-23 14:21:56 +02:00
}
2020-05-11 19:49:12 +02:00
pathParts := strings . Split ( path , "/" ) [ 3 : ]
if len ( pathParts ) < 2 {
resp . WriteHeader ( 404 )
resp . Write ( [ ] byte ( ` unexpected number of path parts ` ) )
return
}
log . Printf ( "[TRACE] fake provider registry request for %#v" , pathParts )
if len ( pathParts ) == 2 {
switch pathParts [ 0 ] + "/" + pathParts [ 1 ] {
case "-/legacy" :
// NOTE: This legacy lookup endpoint is specific to
// registry.terraform.io and not expected to work on any other
// registry host.
resp . Header ( ) . Set ( "Content-Type" , "application/json" )
resp . WriteHeader ( 200 )
resp . Write ( [ ] byte ( ` { "namespace":"legacycorp"} ` ) )
2020-04-23 14:21:56 +02:00
2020-05-11 19:49:12 +02:00
default :
resp . WriteHeader ( 404 )
resp . Write ( [ ] byte ( ` unknown namespace or provider type for direct lookup ` ) )
}
2020-04-23 14:21:56 +02:00
}
2020-05-11 19:49:12 +02:00
if len ( pathParts ) < 3 {
resp . WriteHeader ( 404 )
resp . Write ( [ ] byte ( ` unexpected number of path parts ` ) )
return
2020-04-23 14:21:56 +02:00
}
2020-05-11 19:49:12 +02:00
if pathParts [ 2 ] == "versions" {
if len ( pathParts ) != 3 {
resp . WriteHeader ( 404 )
resp . Write ( [ ] byte ( ` extraneous path parts ` ) )
return
}
switch pathParts [ 0 ] + "/" + pathParts [ 1 ] {
case "awesomesauce/happycloud" :
resp . Header ( ) . Set ( "Content-Type" , "application/json" )
resp . WriteHeader ( 200 )
// Note that these version numbers are intentionally misordered
// so we can test that the client-side code places them in the
// correct order (lowest precedence first).
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"]}]} ` ) )
case "weaksauce/unsupported-protocol" :
resp . Header ( ) . Set ( "Content-Type" , "application/json" )
resp . WriteHeader ( 200 )
resp . Write ( [ ] byte ( ` { "versions":[ { "version":"0.1.0","protocols":["0.1"]}]} ` ) )
case "weaksauce/no-versions" :
resp . Header ( ) . Set ( "Content-Type" , "application/json" )
resp . WriteHeader ( 200 )
resp . Write ( [ ] byte ( ` { "versions":[]} ` ) )
default :
resp . WriteHeader ( 404 )
resp . Write ( [ ] byte ( ` unknown namespace or provider type ` ) )
}
return
}
if len ( pathParts ) == 6 && pathParts [ 3 ] == "download" {
switch pathParts [ 0 ] + "/" + pathParts [ 1 ] {
case "awesomesauce/happycloud" :
if pathParts [ 4 ] == "nonexist" {
resp . WriteHeader ( 404 )
resp . Write ( [ ] byte ( ` unsupported OS ` ) )
return
}
version := pathParts [ 2 ]
body := map [ string ] interface { } {
"protocols" : [ ] string { "99.0" } ,
"os" : pathParts [ 4 ] ,
"arch" : pathParts [ 5 ] ,
"filename" : "happycloud_" + version + ".zip" ,
"shasum" : "000000000000000000000000000000000000000000000000000000000000f00d" ,
"download_url" : "/pkg/awesomesauce/happycloud_" + version + ".zip" ,
"shasums_url" : "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS" ,
"shasums_signature_url" : "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS.sig" ,
"signing_keys" : map [ string ] interface { } {
"gpg_public_keys" : [ ] map [ string ] interface { } {
{
"ascii_armor" : getproviders . HashicorpPublicKey ,
} ,
} ,
} ,
}
enc , err := json . Marshal ( body )
if err != nil {
resp . WriteHeader ( 500 )
resp . Write ( [ ] byte ( "failed to encode body" ) )
}
resp . Header ( ) . Set ( "Content-Type" , "application/json" )
resp . WriteHeader ( 200 )
resp . Write ( enc )
case "weaksauce/unsupported-protocol" :
var protocols [ ] string
version := pathParts [ 2 ]
switch version {
case "0.1.0" :
protocols = [ ] string { "1.0" }
case "2.0.0" :
protocols = [ ] string { "99.0" }
default :
protocols = [ ] string { "5.0" }
}
body := map [ string ] interface { } {
"protocols" : protocols ,
"os" : pathParts [ 4 ] ,
"arch" : pathParts [ 5 ] ,
"filename" : "happycloud_" + version + ".zip" ,
"shasum" : "000000000000000000000000000000000000000000000000000000000000f00d" ,
"download_url" : "/pkg/awesomesauce/happycloud_" + version + ".zip" ,
"shasums_url" : "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS" ,
"shasums_signature_url" : "/pkg/awesomesauce/happycloud_" + version + "_SHA256SUMS.sig" ,
"signing_keys" : map [ string ] interface { } {
"gpg_public_keys" : [ ] map [ string ] interface { } {
{
"ascii_armor" : getproviders . HashicorpPublicKey ,
} ,
} ,
} ,
}
enc , err := json . Marshal ( body )
if err != nil {
resp . WriteHeader ( 500 )
resp . Write ( [ ] byte ( "failed to encode body" ) )
}
resp . Header ( ) . Set ( "Content-Type" , "application/json" )
resp . WriteHeader ( 200 )
resp . Write ( enc )
default :
resp . WriteHeader ( 404 )
resp . Write ( [ ] byte ( ` unknown namespace/provider/version/architecture ` ) )
}
return
2020-04-23 14:21:56 +02:00
}
2020-05-11 19:49:12 +02:00
resp . WriteHeader ( 404 )
resp . Write ( [ ] byte ( ` unrecognized path scheme ` ) )
2020-04-23 14:21:56 +02:00
}
2020-11-20 22:32:08 +01:00
// In order to be able to compare the recorded temp dir paths, we need to
// normalize the path to match what the installer would report.
func tmpDir ( t * testing . T ) string {
d := t . TempDir ( )
unlinked , err := filepath . EvalSymlinks ( d )
if err != nil {
t . Fatal ( err )
}
return filepath . Clean ( unlinked )
}