// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package command import ( "fmt" "io" "log" "net/http" "net/http/httptest" "net/url" "os" "path" "reflect" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/cli" svchost "github.com/hashicorp/terraform-svchost" "github.com/hashicorp/terraform-svchost/auth" "github.com/hashicorp/terraform-svchost/disco" "github.com/hashicorp/terraform/internal/backend" backendInit "github.com/hashicorp/terraform/internal/backend/init" backendCloud "github.com/hashicorp/terraform/internal/cloud" "github.com/hashicorp/terraform/internal/httpclient" "github.com/hashicorp/terraform/version" "google.golang.org/grpc/metadata" ) func newCloudPluginManifestHTTPTestServer(t *testing.T) *httptest.Server { t.Helper() mux := http.NewServeMux() initialPath, _ := os.Getwd() mux.HandleFunc("/api/cloudplugin/v1/manifest", func(w http.ResponseWriter, r *http.Request) { fileToSend, _ := os.Open(path.Join(initialPath, "testdata/cloud-archives/manifest.json")) defer fileToSend.Close() io.Copy(w, fileToSend) }) // Respond to service version constraints calls. mux.HandleFunc("/v1/versions/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") io.WriteString(w, fmt.Sprintf(`{ "service": "%s", "product": "terraform", "minimum": "0.1.0", "maximum": "10.0.0" }`, path.Base(r.URL.Path))) }) // Respond to pings to get the API version header. mux.HandleFunc("/api/v2/ping", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("TFP-API-Version", "2.5") }) // Respond to the initial query to read the hashicorp org entitlements. mux.HandleFunc("/api/v2/organizations/hashicorp/entitlement-set", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/vnd.api+json") io.WriteString(w, `{ "data": { "id": "org-GExadygjSbKP8hsY", "type": "entitlement-sets", "attributes": { "operations": true, "private-module-registry": true, "sentinel": true, "state-storage": true, "teams": true, "vcs-integrations": true } } }`) }) mux.HandleFunc("/api/v2/organizations/hashicorp/workspaces/test", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/vnd.api+json") io.WriteString(w, `{ "data": { "id": "ws-GExadygjSbKP8hsY", "type": "workspaces", "attributes": { "name": "test", "terraform-version": "1.5.4" } } }`) }) return httptest.NewServer(mux) } // testDisco returns a *disco.Disco mapping app.terraform.io and // localhost to a local test server. func testDisco(s *httptest.Server) *disco.Disco { host, _ := url.Parse(s.URL) defaultHostname := "app.terraform.io" tfeHost := svchost.Hostname(defaultHostname) services := map[string]interface{}{ "cloudplugin.v1": fmt.Sprintf("%s/api/cloudplugin/v1/", s.URL), "tfe.v2": fmt.Sprintf("%s/api/v2/", s.URL), } credsSrc := auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{ tfeHost: {"token": "test-auth-token"}, }) d := disco.NewWithCredentialsSource(credsSrc) d.SetUserAgent(httpclient.TerraformUserAgent(version.String())) d.ForceHostServices(tfeHost, services) d.ForceHostServices(svchost.Hostname(host.Host), services) return d } func TestCloud_withBackendConfig(t *testing.T) { t.Skip("To be converted to an e2e test") server := newCloudPluginManifestHTTPTestServer(t) disco := testDisco(server) wd := tempWorkingDirFixture(t, "cloud-config") t.Chdir(wd.RootModuleDir()) // Overwrite the cloud backend with the test disco previousBackend := backendInit.Backend("cloud") backendInit.Set("cloud", func() backend.Backend { return backendCloud.New(disco) }) defer backendInit.Set("cloud", previousBackend) ui := cli.NewMockUi() view, _ := testView(t) // Initialize the backend ic := &InitCommand{ Meta: Meta{ Ui: ui, View: view, testingOverrides: metaOverridesForProvider(testProvider()), Services: disco, }, } log.Print("[TRACE] TestCloud_withBackendConfig running: terraform init") if code := ic.Run([]string{}); code != 0 { t.Fatalf("init failed\n%s", ui.ErrorWriter) } // Run the cloud command ui = cli.NewMockUi() c := &CloudCommand{ Meta: Meta{ Ui: ui, testingOverrides: metaOverridesForProvider(testProvider()), Services: disco, WorkingDir: wd, }, } args := []string{"version"} if code := c.Run(args); code != 0 { t.Fatalf("expected exit 0, got %d: \n%s", code, ui.ErrorWriter.String()) } output := ui.OutputWriter.String() expected := "HCP Terraform Plugin v0.1.0\n\n" if output != expected { t.Fatalf("the output did not equal the expected string:\n%s", cmp.Diff(expected, output)) } } func TestCloud_withENVConfig(t *testing.T) { t.Skip("To be converted to an e2e test") server := newCloudPluginManifestHTTPTestServer(t) disco := testDisco(server) wd := tempWorkingDir(t) t.Chdir(wd.RootModuleDir()) serverURL, _ := url.Parse(server.URL) os.Setenv("TF_CLOUD_HOSTNAME", serverURL.Host) defer os.Unsetenv("TF_CLOUD_HOSTNAME") // Run the cloud command ui := cli.NewMockUi() c := &CloudCommand{ Meta: Meta{ Ui: ui, testingOverrides: metaOverridesForProvider(testProvider()), Services: disco, WorkingDir: wd, }, } args := []string{"version"} if code := c.Run(args); code != 0 { t.Fatalf("expected exit 0, got %d: \n%s", code, ui.ErrorWriter.String()) } output := ui.OutputWriter.String() expected := "HCP Terraform Plugin v0.1.0\n\n" if output != expected { t.Fatalf("the output did not equal the expected string:\n%s", cmp.Diff(expected, output)) } } func TestCloudPluginConfig_ToMetadata(t *testing.T) { expected := metadata.Pairs( "tfc-address", "https://app.staging.terraform.io", "tfc-base-path", "/api/v2/", "tfc-display-hostname", "app.staging.terraform.io", "tfc-token", "not-a-legit-token", "tfc-organization", "example-corp", "tfc-current-workspace", "example-space", "tfc-workspace-name", "example-space", // Actually combining -name and -tags is an invalid scenario from // Terraform's point of view, but here we're just testing that every // field makes the trip safely if sent. "tfc-workspace-tags", "networking", // Duplicate is on purpose. "tfc-workspace-tags", "platform-team", "tfc-default-project-name", "production-services", ) inputStruct := CloudPluginConfig{ Address: "https://app.staging.terraform.io", BasePath: "/api/v2/", DisplayHostname: "app.staging.terraform.io", Token: "not-a-legit-token", Organization: "example-corp", CurrentWorkspace: "example-space", WorkspaceName: "example-space", WorkspaceTags: []string{"networking", "platform-team"}, DefaultProjectName: "production-services", } result := inputStruct.ToMetadata() if !reflect.DeepEqual(expected, result) { t.Fatalf("Expected: %#v\nGot: %#v\n", expected, result) } }