mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-18 12:42:58 -06:00
4c254cc2be
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
264 lines
7.2 KiB
Go
264 lines
7.2 KiB
Go
package test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
|
|
svchost "github.com/hashicorp/terraform-svchost"
|
|
"github.com/hashicorp/terraform-svchost/auth"
|
|
"github.com/hashicorp/terraform-svchost/disco"
|
|
"github.com/hashicorp/terraform/internal/httpclient"
|
|
"github.com/hashicorp/terraform/internal/registry/regsrc"
|
|
"github.com/hashicorp/terraform/internal/registry/response"
|
|
tfversion "github.com/hashicorp/terraform/version"
|
|
)
|
|
|
|
// Disco return a *disco.Disco mapping registry.terraform.io, localhost,
|
|
// localhost.localdomain, and example.com to the test server.
|
|
func Disco(s *httptest.Server) *disco.Disco {
|
|
services := map[string]interface{}{
|
|
// Note that both with and without trailing slashes are supported behaviours
|
|
// TODO: add specific tests to enumerate both possibilities.
|
|
"modules.v1": fmt.Sprintf("%s/v1/modules", s.URL),
|
|
"providers.v1": fmt.Sprintf("%s/v1/providers", s.URL),
|
|
}
|
|
d := disco.NewWithCredentialsSource(credsSrc)
|
|
d.SetUserAgent(httpclient.TerraformUserAgent(tfversion.String()))
|
|
|
|
d.ForceHostServices(svchost.Hostname("registry.terraform.io"), services)
|
|
d.ForceHostServices(svchost.Hostname("localhost"), services)
|
|
d.ForceHostServices(svchost.Hostname("localhost.localdomain"), services)
|
|
d.ForceHostServices(svchost.Hostname("example.com"), services)
|
|
return d
|
|
}
|
|
|
|
// Map of module names and location of test modules.
|
|
// Only one version for now, as we only lookup latest from the registry.
|
|
type testMod struct {
|
|
location string
|
|
version string
|
|
}
|
|
|
|
// Map of provider names and location of test providers.
|
|
// Only one version for now, as we only lookup latest from the registry.
|
|
type testProvider struct {
|
|
version string
|
|
url string
|
|
}
|
|
|
|
const (
|
|
testCred = "test-auth-token"
|
|
)
|
|
|
|
var (
|
|
regHost = svchost.Hostname(regsrc.PublicRegistryHost.Normalized())
|
|
credsSrc = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
|
|
regHost: {"token": testCred},
|
|
})
|
|
)
|
|
|
|
// All the locationes from the mockRegistry start with a file:// scheme. If
|
|
// the the location string here doesn't have a scheme, the mockRegistry will
|
|
// find the absolute path and return a complete URL.
|
|
var testMods = map[string][]testMod{
|
|
"registry/foo/bar": {{
|
|
location: "file:///download/registry/foo/bar/0.2.3//*?archive=tar.gz",
|
|
version: "0.2.3",
|
|
}},
|
|
"registry/foo/baz": {{
|
|
location: "file:///download/registry/foo/baz/1.10.0//*?archive=tar.gz",
|
|
version: "1.10.0",
|
|
}},
|
|
"registry/local/sub": {{
|
|
location: "testdata/registry-tar-subdir/foo.tgz//*?archive=tar.gz",
|
|
version: "0.1.2",
|
|
}},
|
|
"exists-in-registry/identifier/provider": {{
|
|
location: "file:///registry/exists",
|
|
version: "0.2.0",
|
|
}},
|
|
"relative/foo/bar": {{ // There is an exception for the "relative/" prefix in the test registry server
|
|
location: "/relative-path",
|
|
version: "0.2.0",
|
|
}},
|
|
"test-versions/name/provider": {
|
|
{version: "2.2.0"},
|
|
{version: "2.1.1"},
|
|
{version: "1.2.2"},
|
|
{version: "1.2.1"},
|
|
},
|
|
"private/name/provider": {
|
|
{version: "1.0.0"},
|
|
},
|
|
}
|
|
|
|
var testProviders = map[string][]testProvider{
|
|
"-/foo": {
|
|
{
|
|
version: "0.2.3",
|
|
url: "https://releases.hashicorp.com/terraform-provider-foo/0.2.3/terraform-provider-foo.zip",
|
|
},
|
|
{version: "0.3.0"},
|
|
},
|
|
"-/bar": {
|
|
{
|
|
version: "0.1.1",
|
|
url: "https://releases.hashicorp.com/terraform-provider-bar/0.1.1/terraform-provider-bar.zip",
|
|
},
|
|
{version: "0.1.2"},
|
|
},
|
|
}
|
|
|
|
func providerAlias(provider string) string {
|
|
re := regexp.MustCompile("^-/")
|
|
if re.MatchString(provider) {
|
|
return re.ReplaceAllString(provider, "terraform-providers/")
|
|
}
|
|
return provider
|
|
}
|
|
|
|
func init() {
|
|
// Add provider aliases
|
|
for provider, info := range testProviders {
|
|
alias := providerAlias(provider)
|
|
testProviders[alias] = info
|
|
}
|
|
}
|
|
|
|
func mockRegHandler() http.Handler {
|
|
mux := http.NewServeMux()
|
|
|
|
moduleDownload := func(w http.ResponseWriter, r *http.Request) {
|
|
p := strings.TrimLeft(r.URL.Path, "/")
|
|
// handle download request
|
|
re := regexp.MustCompile(`^([-a-z]+/\w+/\w+).*/download$`)
|
|
// download lookup
|
|
matches := re.FindStringSubmatch(p)
|
|
if len(matches) != 2 {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// check for auth
|
|
if strings.Contains(matches[0], "private/") {
|
|
if !strings.Contains(r.Header.Get("Authorization"), testCred) {
|
|
http.Error(w, "", http.StatusForbidden)
|
|
return
|
|
}
|
|
}
|
|
|
|
versions, ok := testMods[matches[1]]
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
mod := versions[0]
|
|
|
|
location := mod.location
|
|
if !strings.HasPrefix(matches[0], "relative/") && !strings.HasPrefix(location, "file:///") {
|
|
// we can't use filepath.Abs because it will clean `//`
|
|
wd, _ := os.Getwd()
|
|
location = fmt.Sprintf("file://%s/%s", wd, location)
|
|
}
|
|
|
|
w.Header().Set("X-Terraform-Get", location)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
// no body
|
|
}
|
|
|
|
moduleVersions := func(w http.ResponseWriter, r *http.Request) {
|
|
p := strings.TrimLeft(r.URL.Path, "/")
|
|
re := regexp.MustCompile(`^([-a-z]+/\w+/\w+)/versions$`)
|
|
matches := re.FindStringSubmatch(p)
|
|
if len(matches) != 2 {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// check for auth
|
|
if strings.Contains(matches[1], "private/") {
|
|
if !strings.Contains(r.Header.Get("Authorization"), testCred) {
|
|
http.Error(w, "", http.StatusForbidden)
|
|
}
|
|
}
|
|
|
|
name := matches[1]
|
|
versions, ok := testMods[name]
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
// only adding the single requested module for now
|
|
// this is the minimal that any regisry is epected to support
|
|
mpvs := &response.ModuleProviderVersions{
|
|
Source: name,
|
|
}
|
|
|
|
for _, v := range versions {
|
|
mv := &response.ModuleVersion{
|
|
Version: v.version,
|
|
}
|
|
mpvs.Versions = append(mpvs.Versions, mv)
|
|
}
|
|
|
|
resp := response.ModuleVersions{
|
|
Modules: []*response.ModuleProviderVersions{mpvs},
|
|
}
|
|
|
|
js, err := json.Marshal(resp)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write(js)
|
|
}
|
|
|
|
mux.Handle("/v1/modules/",
|
|
http.StripPrefix("/v1/modules/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasSuffix(r.URL.Path, "/download") {
|
|
moduleDownload(w, r)
|
|
return
|
|
}
|
|
|
|
if strings.HasSuffix(r.URL.Path, "/versions") {
|
|
moduleVersions(w, r)
|
|
return
|
|
}
|
|
|
|
http.NotFound(w, r)
|
|
})),
|
|
)
|
|
|
|
mux.HandleFunc("/.well-known/terraform.json", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
io.WriteString(w, `{"modules.v1":"http://localhost/v1/modules/", "providers.v1":"http://localhost/v1/providers/"}`)
|
|
})
|
|
return mux
|
|
}
|
|
|
|
// Registry returns an httptest server that mocks out some registry functionality.
|
|
func Registry() *httptest.Server {
|
|
return httptest.NewServer(mockRegHandler())
|
|
}
|
|
|
|
// RegistryRetryableErrorsServer returns an httptest server that mocks out the
|
|
// registry API to return 502 errors.
|
|
func RegistryRetryableErrorsServer() *httptest.Server {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/v1/modules/", func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "mocked server error", http.StatusBadGateway)
|
|
})
|
|
mux.HandleFunc("/v1/providers/", func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "mocked server error", http.StatusBadGateway)
|
|
})
|
|
return httptest.NewServer(mux)
|
|
}
|