2019-12-20 02:24:14 +01:00
package getproviders
import (
"fmt"
"regexp"
"strings"
"testing"
"github.com/apparentlymart/go-versions/versions"
"github.com/google/go-cmp/cmp"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform/addrs"
)
func TestSourceAvailableVersions ( t * testing . T ) {
2020-01-09 01:47:49 +01:00
source , baseURL , close := testRegistrySource ( t )
2019-12-20 02:24:14 +01:00
defer close ( )
tests := [ ] struct {
provider string
wantVersions [ ] string
wantErr string
} {
// These test cases are relying on behaviors of the fake provider
internal: Verify provider signatures on install
Providers installed from the registry are accompanied by a list of
checksums (the "SHA256SUMS" file), which is cryptographically signed to
allow package authentication. The process of verifying this has multiple
steps:
- First we must verify that the SHA256 hash of the package archive
matches the expected hash. This could be done for local installations
too, in the future.
- Next we ensure that the expected hash returned as part of the registry
API response matches an entry in the checksum list.
- Finally we verify the cryptographic signature of the checksum list,
using the public keys provided by the registry.
Each of these steps is implemented as a separate PackageAuthentication
type. The local archive installation mechanism uses only the archive
checksum authenticator, and the HTTP installation uses all three in the
order given.
The package authentication system now also returns a result value, which
is used by command/init to display the result of the authentication
process.
There are three tiers of signature, each of which is presented
differently to the user:
- Signatures from the embedded HashiCorp public key indicate that the
provider is officially supported by HashiCorp;
- If the signing key is not from HashiCorp, it may have an associated
trust signature, which indicates that the provider is from one of
HashiCorp's trusted partners;
- Otherwise, if the signature is valid, this is a community provider.
2020-04-08 22:22:07 +02:00
// registry server implemented in registry_client_test.go.
2019-12-20 02:24:14 +01:00
{
"example.com/awesomesauce/happycloud" ,
2020-05-11 19:49:12 +02:00
[ ] string { "0.1.0" , "1.0.0" , "1.2.0" , "2.0.0" } ,
2019-12-20 02:24:14 +01:00
` ` ,
} ,
{
"example.com/weaksauce/no-versions" ,
nil ,
` ` , // having no versions is not an error, it's just odd
} ,
{
"example.com/nonexist/nonexist" ,
nil ,
` provider registry example.com does not have a provider named example.com/nonexist/nonexist ` ,
} ,
{
"not.example.com/foo/bar" ,
nil ,
` host not.example.com does not offer a Terraform provider registry ` ,
} ,
{
"too-new.example.com/foo/bar" ,
nil ,
` host too-new.example.com does not support the provider registry protocol required by this Terraform version, but may be compatible with a different Terraform version ` ,
} ,
{
"fails.example.com/foo/bar" ,
nil ,
2020-05-11 16:05:33 +02:00
` could not query provider registry for fails.example.com/foo/bar: the request failed after 2 attempts, please try again later: Get " ` + baseURL + ` /fails-immediately/foo/bar/versions": EOF ` ,
2019-12-20 02:24:14 +01:00
} ,
}
for _ , test := range tests {
t . Run ( test . provider , func ( t * testing . T ) {
// TEMP: We don't yet have a function for parsing provider
// source addresses so we'll just fake it in here for now.
parts := strings . Split ( test . provider , "/" )
providerAddr := addrs . Provider {
Hostname : svchost . Hostname ( parts [ 0 ] ) ,
Namespace : parts [ 1 ] ,
Type : parts [ 2 ] ,
}
gotVersions , err := source . AvailableVersions ( providerAddr )
if err != nil {
if test . wantErr == "" {
t . Fatalf ( "wrong error\ngot: %s\nwant: <nil>" , err . Error ( ) )
}
2020-01-09 01:47:49 +01:00
if got , want := err . Error ( ) , test . wantErr ; got != want {
2019-12-20 02:24:14 +01:00
t . Fatalf ( "wrong error\ngot: %s\nwant: %s" , got , want )
}
return
}
if test . wantErr != "" {
t . Fatalf ( "wrong error\ngot: <nil>\nwant: %s" , test . wantErr )
}
var gotVersionsStr [ ] string
if gotVersions != nil {
gotVersionsStr = make ( [ ] string , len ( gotVersions ) )
for i , v := range gotVersions {
gotVersionsStr [ i ] = v . String ( )
}
}
if diff := cmp . Diff ( test . wantVersions , gotVersionsStr ) ; diff != "" {
t . Errorf ( "wrong result\n%s" , diff )
}
} )
}
}
2020-01-09 01:24:41 +01:00
func TestSourcePackageMeta ( t * testing . T ) {
2020-01-09 01:47:49 +01:00
source , baseURL , close := testRegistrySource ( t )
2019-12-20 02:24:14 +01:00
defer close ( )
tests := [ ] struct {
provider string
version string
os , arch string
want PackageMeta
wantErr string
} {
// These test cases are relying on behaviors of the fake provider
// registry server implemented in client_test.go.
{
"example.com/awesomesauce/happycloud" ,
"1.2.0" ,
"linux" , "amd64" ,
PackageMeta {
2020-02-21 02:53:11 +01:00
Provider : addrs . NewProvider (
svchost . Hostname ( "example.com" ) , "awesomesauce" , "happycloud" ,
) ,
Version : versions . MustParseVersion ( "1.2.0" ) ,
2019-12-20 02:24:14 +01:00
ProtocolVersions : VersionList { versions . MustParseVersion ( "5.0.0" ) } ,
TargetPlatform : Platform { "linux" , "amd64" } ,
Filename : "happycloud_1.2.0.zip" ,
internal: Verify provider signatures on install
Providers installed from the registry are accompanied by a list of
checksums (the "SHA256SUMS" file), which is cryptographically signed to
allow package authentication. The process of verifying this has multiple
steps:
- First we must verify that the SHA256 hash of the package archive
matches the expected hash. This could be done for local installations
too, in the future.
- Next we ensure that the expected hash returned as part of the registry
API response matches an entry in the checksum list.
- Finally we verify the cryptographic signature of the checksum list,
using the public keys provided by the registry.
Each of these steps is implemented as a separate PackageAuthentication
type. The local archive installation mechanism uses only the archive
checksum authenticator, and the HTTP installation uses all three in the
order given.
The package authentication system now also returns a result value, which
is used by command/init to display the result of the authentication
process.
There are three tiers of signature, each of which is presented
differently to the user:
- Signatures from the embedded HashiCorp public key indicate that the
provider is officially supported by HashiCorp;
- If the signing key is not from HashiCorp, it may have an associated
trust signature, which indicates that the provider is from one of
HashiCorp's trusted partners;
- Otherwise, if the signature is valid, this is a community provider.
2020-04-08 22:22:07 +02:00
Location : PackageHTTPURL ( baseURL + "/pkg/awesomesauce/happycloud_1.2.0.zip" ) ,
Authentication : PackageAuthenticationAll (
NewMatchingChecksumAuthentication (
[ ] byte ( "000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n" ) ,
"happycloud_1.2.0.zip" ,
[ 32 ] byte { 30 : 0xf0 , 31 : 0x0d } ,
) ,
NewArchiveChecksumAuthentication ( [ 32 ] byte { 30 : 0xf0 , 31 : 0x0d } ) ,
NewSignatureAuthentication (
[ ] byte ( "000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n" ) ,
[ ] byte ( "GPG signature" ) ,
[ ] SigningKey {
{ ASCIIArmor : HashicorpPublicKey } ,
} ,
) ,
) ,
2019-12-20 02:24:14 +01:00
} ,
` ` ,
} ,
{
"example.com/awesomesauce/happycloud" ,
"1.2.0" ,
"nonexist" , "amd64" ,
PackageMeta { } ,
` provider example.com/awesomesauce/happycloud 1.2.0 is not available for nonexist_amd64 ` ,
} ,
{
"not.example.com/awesomesauce/happycloud" ,
"1.2.0" ,
"linux" , "amd64" ,
PackageMeta { } ,
` host not.example.com does not offer a Terraform provider registry ` ,
} ,
{
"too-new.example.com/awesomesauce/happycloud" ,
"1.2.0" ,
"linux" , "amd64" ,
PackageMeta { } ,
` host too-new.example.com does not support the provider registry protocol required by this Terraform version, but may be compatible with a different Terraform version ` ,
} ,
{
"fails.example.com/awesomesauce/happycloud" ,
"1.2.0" ,
"linux" , "amd64" ,
PackageMeta { } ,
2020-05-11 16:05:33 +02:00
` could not query provider registry for fails.example.com/awesomesauce/happycloud: the request failed after 2 attempts, please try again later: Get "http://placeholder-origin/fails-immediately/awesomesauce/happycloud/1.2.0/download/linux/amd64": EOF ` ,
2019-12-20 02:24:14 +01:00
} ,
}
// Sometimes error messages contain specific HTTP endpoint URLs, but
// since our test server is on a random port we'd not be able to
// consistently match those. Instead, we'll normalize the URLs.
urlPattern := regexp . MustCompile ( ` http://[^/]+/ ` )
cmpOpts := cmp . Comparer ( Version . Same )
for _ , test := range tests {
t . Run ( fmt . Sprintf ( "%s for %s_%s" , test . provider , test . os , test . arch ) , func ( t * testing . T ) {
// TEMP: We don't yet have a function for parsing provider
// source addresses so we'll just fake it in here for now.
parts := strings . Split ( test . provider , "/" )
providerAddr := addrs . Provider {
Hostname : svchost . Hostname ( parts [ 0 ] ) ,
Namespace : parts [ 1 ] ,
Type : parts [ 2 ] ,
}
version := versions . MustParseVersion ( test . version )
2020-01-09 01:24:41 +01:00
got , err := source . PackageMeta ( providerAddr , version , Platform { test . os , test . arch } )
2019-12-20 02:24:14 +01:00
if err != nil {
if test . wantErr == "" {
t . Fatalf ( "wrong error\ngot: %s\nwant: <nil>" , err . Error ( ) )
}
gotErr := urlPattern . ReplaceAllLiteralString ( err . Error ( ) , "http://placeholder-origin/" )
if got , want := gotErr , test . wantErr ; got != want {
t . Fatalf ( "wrong error\ngot: %s\nwant: %s" , got , want )
}
return
}
if test . wantErr != "" {
t . Fatalf ( "wrong error\ngot: <nil>\nwant: %s" , test . wantErr )
}
if diff := cmp . Diff ( test . want , got , cmpOpts ) ; diff != "" {
t . Errorf ( "wrong result\n%s" , diff )
}
} )
}
}