github.com/opentofu/opentofu@v1.7.1/internal/getproviders/registry_source_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package getproviders
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"regexp"
    12  	"strings"
    13  	"testing"
    14  
    15  	tfaddr "github.com/opentofu/registry-address"
    16  
    17  	"github.com/apparentlymart/go-versions/versions"
    18  	"github.com/google/go-cmp/cmp"
    19  	svchost "github.com/hashicorp/terraform-svchost"
    20  
    21  	"github.com/opentofu/opentofu/internal/addrs"
    22  )
    23  
    24  func TestSourceAvailableVersions(t *testing.T) {
    25  	source, baseURL, close := testRegistrySource(t)
    26  	defer close()
    27  
    28  	tests := []struct {
    29  		provider     string
    30  		wantVersions []string
    31  		wantErr      string
    32  	}{
    33  		// These test cases are relying on behaviors of the fake provider
    34  		// registry server implemented in registry_client_test.go.
    35  		{
    36  			"example.com/awesomesauce/happycloud",
    37  			[]string{"0.1.0", "1.0.0", "1.2.0", "2.0.0"},
    38  			``,
    39  		},
    40  		{
    41  			"example.com/weaksauce/no-versions",
    42  			nil,
    43  			``, // having no versions is not an error, it's just odd
    44  		},
    45  		{
    46  			"example.com/nonexist/nonexist",
    47  			nil,
    48  			`provider registry example.com does not have a provider named example.com/nonexist/nonexist`,
    49  		},
    50  		{
    51  			"not.example.com/foo/bar",
    52  			nil,
    53  			`host not.example.com does not offer a OpenTofu provider registry`,
    54  		},
    55  		{
    56  			"too-new.example.com/foo/bar",
    57  			nil,
    58  			`host too-new.example.com does not support the provider registry protocol required by this OpenTofu version, but may be compatible with a different OpenTofu version`,
    59  		},
    60  		{
    61  			"fails.example.com/foo/bar",
    62  			nil,
    63  			`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`,
    64  		},
    65  	}
    66  
    67  	for _, test := range tests {
    68  		t.Run(test.provider, func(t *testing.T) {
    69  			provider := addrs.MustParseProviderSourceString(test.provider)
    70  			gotVersions, _, err := source.AvailableVersions(context.Background(), provider)
    71  
    72  			if err != nil {
    73  				if test.wantErr == "" {
    74  					t.Fatalf("wrong error\ngot:  %s\nwant: <nil>", err.Error())
    75  				}
    76  				if got, want := err.Error(), test.wantErr; got != want {
    77  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
    78  				}
    79  				return
    80  			}
    81  
    82  			if test.wantErr != "" {
    83  				t.Fatalf("wrong error\ngot:  <nil>\nwant: %s", test.wantErr)
    84  			}
    85  
    86  			var gotVersionsStr []string
    87  			if gotVersions != nil {
    88  				gotVersionsStr = make([]string, len(gotVersions))
    89  				for i, v := range gotVersions {
    90  					gotVersionsStr[i] = v.String()
    91  				}
    92  			}
    93  
    94  			if diff := cmp.Diff(test.wantVersions, gotVersionsStr); diff != "" {
    95  				t.Errorf("wrong result\n%s", diff)
    96  			}
    97  		})
    98  	}
    99  }
   100  
   101  func TestSourceAvailableVersions_warnings(t *testing.T) {
   102  	source, _, close := testRegistrySource(t)
   103  	defer close()
   104  
   105  	provider := addrs.MustParseProviderSourceString("example.com/weaksauce/no-versions")
   106  	_, warnings, err := source.AvailableVersions(context.Background(), provider)
   107  	if err != nil {
   108  		t.Fatalf("unexpected error: %s", err.Error())
   109  	}
   110  
   111  	if len(warnings) != 1 {
   112  		t.Fatalf("wrong number of warnings. Expected 1, got %d", len(warnings))
   113  	}
   114  
   115  }
   116  
   117  func TestSourcePackageMeta(t *testing.T) {
   118  	source, baseURL, close := testRegistrySource(t)
   119  	defer close()
   120  
   121  	validMeta := PackageMeta{
   122  		Provider: addrs.NewProvider(
   123  			svchost.Hostname("example.com"), "awesomesauce", "happycloud",
   124  		),
   125  		Version:          versions.MustParseVersion("1.2.0"),
   126  		ProtocolVersions: VersionList{versions.MustParseVersion("5.0.0")},
   127  		TargetPlatform:   Platform{"linux", "amd64"},
   128  		Filename:         "happycloud_1.2.0.zip",
   129  		Location:         PackageHTTPURL(baseURL + "/pkg/awesomesauce/happycloud_1.2.0.zip"),
   130  	}
   131  	validMeta.Authentication = PackageAuthenticationAll(
   132  		NewMatchingChecksumAuthentication(
   133  			[]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"),
   134  			"happycloud_1.2.0.zip",
   135  			[32]byte{30: 0xf0, 31: 0x0d},
   136  		),
   137  		NewArchiveChecksumAuthentication(Platform{"linux", "amd64"}, [32]byte{30: 0xf0, 31: 0x0d}),
   138  		NewSignatureAuthentication(
   139  			validMeta,
   140  			[]byte("000000000000000000000000000000000000000000000000000000000000f00d happycloud_1.2.0.zip\n000000000000000000000000000000000000000000000000000000000000face happycloud_1.2.0_face.zip\n"),
   141  			[]byte("GPG signature"),
   142  			[]SigningKey{
   143  				{ASCIIArmor: TestingPublicKey},
   144  			},
   145  			&tfaddr.Provider{Hostname: "example.com", Namespace: "awesomesauce", Type: "happycloud"},
   146  		),
   147  	)
   148  
   149  	tests := []struct {
   150  		provider   string
   151  		version    string
   152  		os, arch   string
   153  		want       PackageMeta
   154  		wantHashes []Hash
   155  		wantErr    string
   156  	}{
   157  		// These test cases are relying on behaviors of the fake provider
   158  		// registry server implemented in registry_client_test.go.
   159  		{
   160  			"example.com/awesomesauce/happycloud",
   161  			"1.2.0",
   162  			"linux", "amd64",
   163  			validMeta,
   164  			[]Hash{
   165  				"zh:000000000000000000000000000000000000000000000000000000000000f00d",
   166  				"zh:000000000000000000000000000000000000000000000000000000000000face",
   167  			},
   168  			``,
   169  		},
   170  		{
   171  			"example.com/awesomesauce/happycloud",
   172  			"1.2.0",
   173  			"nonexist", "amd64",
   174  			PackageMeta{},
   175  			nil,
   176  			`provider example.com/awesomesauce/happycloud 1.2.0 is not available for nonexist_amd64`,
   177  		},
   178  		{
   179  			"not.example.com/awesomesauce/happycloud",
   180  			"1.2.0",
   181  			"linux", "amd64",
   182  			PackageMeta{},
   183  			nil,
   184  			`host not.example.com does not offer a OpenTofu provider registry`,
   185  		},
   186  		{
   187  			"too-new.example.com/awesomesauce/happycloud",
   188  			"1.2.0",
   189  			"linux", "amd64",
   190  			PackageMeta{},
   191  			nil,
   192  			`host too-new.example.com does not support the provider registry protocol required by this OpenTofu version, but may be compatible with a different OpenTofu version`,
   193  		},
   194  		{
   195  			"fails.example.com/awesomesauce/happycloud",
   196  			"1.2.0",
   197  			"linux", "amd64",
   198  			PackageMeta{},
   199  			nil,
   200  			`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`,
   201  		},
   202  	}
   203  
   204  	// Sometimes error messages contain specific HTTP endpoint URLs, but
   205  	// since our test server is on a random port we'd not be able to
   206  	// consistently match those. Instead, we'll normalize the URLs.
   207  	urlPattern := regexp.MustCompile(`http://[^/]+/`)
   208  
   209  	cmpOpts := cmp.Comparer(Version.Same)
   210  
   211  	for _, test := range tests {
   212  		t.Run(fmt.Sprintf("%s for %s_%s", test.provider, test.os, test.arch), func(t *testing.T) {
   213  			// TEMP: We don't yet have a function for parsing provider
   214  			// source addresses so we'll just fake it in here for now.
   215  			parts := strings.Split(test.provider, "/")
   216  			providerAddr := addrs.Provider{
   217  				Hostname:  svchost.Hostname(parts[0]),
   218  				Namespace: parts[1],
   219  				Type:      parts[2],
   220  			}
   221  
   222  			version := versions.MustParseVersion(test.version)
   223  
   224  			got, err := source.PackageMeta(context.Background(), providerAddr, version, Platform{test.os, test.arch})
   225  
   226  			if err != nil {
   227  				if test.wantErr == "" {
   228  					t.Fatalf("wrong error\ngot:  %s\nwant: <nil>", err.Error())
   229  				}
   230  				gotErr := urlPattern.ReplaceAllLiteralString(err.Error(), "http://placeholder-origin/")
   231  				if got, want := gotErr, test.wantErr; got != want {
   232  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
   233  				}
   234  				return
   235  			}
   236  
   237  			if test.wantErr != "" {
   238  				t.Fatalf("wrong error\ngot:  <nil>\nwant: %s", test.wantErr)
   239  			}
   240  
   241  			if diff := cmp.Diff(got, test.want, cmpOpts); diff != "" {
   242  				t.Errorf("wrong result\n%s", diff)
   243  			}
   244  			if diff := cmp.Diff(test.wantHashes, got.AcceptableHashes()); diff != "" {
   245  				t.Errorf("wrong AcceptableHashes result\n%s", diff)
   246  			}
   247  		})
   248  	}
   249  
   250  }