You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
terraform/internal/command/cloud_mock.go

199 lines
5.6 KiB

// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package command
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/backend/backendrun"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/states/statemgr"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
backendLocal "github.com/hashicorp/terraform/internal/backend/local"
)
func cloudTestServerWithVars(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(filepath.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"
}`, filepath.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
}
}
}`)
})
// Respond to our "test" workspace
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.15.0",
"execution-mode": "remote"
}
}
}`)
})
// Respond to the "default" workspace request, will always be requested by Terraform
mux.HandleFunc("/api/v2/organizations/hashicorp/workspaces/default", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.api+json")
io.WriteString(w, `{
"data": {
"id": "ws-GExadygjSbKP8hsX",
"type": "workspaces",
"attributes": {
"name": "default",
"terraform-version": "1.15.0",
"execution-mode": "remote"
}
}
}`)
})
// Respond to the variables for the "test" workspace:
// module_name -> example
mux.HandleFunc("/api/v2/workspaces/ws-GExadygjSbKP8hsY/all-vars", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.api+json")
io.WriteString(w, `{
"data": [
{
"id": "var-1234",
"type": "vars",
"attributes": {
"key": "module_name",
"value": "example",
"category": "terraform",
"hcl": false,
"sensitive": false
}
}
]
}`)
})
return httptest.NewServer(mux)
}
// TestVariableBackend is a backend used in test that are fetching variable
// values for constant values.
type TestVariableBackend struct {
*backendLocal.Local
vars map[string]string
}
func TestNewVariableBackend(result map[string]string) backend.Backend {
return &TestVariableBackend{
Local: backendLocal.New(),
vars: result,
}
}
var _ backendrun.ConstVariableSupplier = (*TestVariableBackend)(nil)
var _ backendrun.Local = (*TestVariableBackend)(nil)
func (b *TestVariableBackend) FetchVariables(ctx context.Context, workspace string) (map[string]arguments.UnparsedVariableValue, tfdiags.Diagnostics) {
result := make(map[string]arguments.UnparsedVariableValue)
for name, value := range b.vars {
result[name] = testUnparsedVariableValueString{
str: value,
name: name,
}
}
return result, nil
}
func (b *TestVariableBackend) LocalRun(op *backendrun.Operation) (*backendrun.LocalRun, statemgr.Full, tfdiags.Diagnostics) {
// Sometimes a command (like graph) requires a local backend. The cloud
// backend implements LocalRun and will fetch variables from the backend.
// But our mock TestVariableBackend will fail in these tests, because it
// embends the backendrun.Local backend and never calls FetchVariables.
//
// We now set the variables manually here and defer to the regular backend
// run implementation, this helps us test the desired behavior.
if op.Variables == nil {
op.Variables = make(map[string]arguments.UnparsedVariableValue)
}
fetchedVars, _ := b.FetchVariables(context.Background(), op.Workspace)
for k, v := range fetchedVars {
if _, ok := op.Variables[k]; !ok {
op.Variables[k] = v
}
}
return b.Local.LocalRun(op)
}
type testUnparsedVariableValueString struct {
str string
name string
}
func (v testUnparsedVariableValueString) ParseVariableValue(mode configs.VariableParsingMode) (*terraform.InputValue, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
val, hclDiags := mode.Parse(v.name, v.str)
diags = diags.Append(hclDiags)
return &terraform.InputValue{
Value: val,
SourceType: terraform.ValueFromInput,
}, diags
}