From edf056babc3932cbfeebec29765b8531b0a529fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Banaszewski?= Date: Thu, 13 Nov 2025 20:14:53 +0100 Subject: [PATCH] test: add mocked terraform installation files (#20757) Adds mocked terraform installation files and uses them in provisioner/terraform.TestInstall Fixes: https://github.com/coder/internal/issues/72 --- provisioner/terraform/install.go | 12 +- provisioner/terraform/install_test.go | 186 +++++++++++++++++++++++++- provisioner/terraform/serve.go | 2 +- 3 files changed, 190 insertions(+), 10 deletions(-) diff --git a/provisioner/terraform/install.go b/provisioner/terraform/install.go index 174fcf257d..19bf7c6a8d 100644 --- a/provisioner/terraform/install.go +++ b/provisioner/terraform/install.go @@ -34,7 +34,7 @@ var ( // operation. // //nolint:revive // verbose is a control flag that controls the verbosity of the log output. -func Install(ctx context.Context, log slog.Logger, verbose bool, dir string, wantVersion *version.Version) (string, error) { +func Install(ctx context.Context, log slog.Logger, verbose bool, dir string, wantVersion *version.Version, baseUrl string, verifyChecksums bool) (string, error) { err := os.MkdirAll(dir, 0o750) if err != nil { return "", err @@ -63,11 +63,15 @@ func Install(ctx context.Context, log slog.Logger, verbose bool, dir string, wan } installer := &releases.ExactVersion{ - InstallDir: dir, - Product: product.Terraform, - Version: TerraformVersion, + InstallDir: dir, + Product: product.Terraform, + Version: TerraformVersion, + SkipChecksumVerification: !verifyChecksums, } installer.SetLogger(slog.Stdlib(ctx, log, slog.LevelDebug)) + if baseUrl != "" { + installer.ApiBaseURL = baseUrl + } logInstall := log.Debug if verbose { diff --git a/provisioner/terraform/install_test.go b/provisioner/terraform/install_test.go index 6a1be707dd..edbc758043 100644 --- a/provisioner/terraform/install_test.go +++ b/provisioner/terraform/install_test.go @@ -6,8 +6,15 @@ package terraform_test import ( + "archive/zip" "context" + "encoding/json" + "fmt" + "net" + "net/http" "os" + "path/filepath" + "strings" "sync" "testing" "time" @@ -20,6 +27,175 @@ import ( "github.com/coder/coder/v2/testutil" ) +const ( + // simple script that mocks `./terraform version -json` + terraformExecutableTemplate = `#!/bin/bash +cat < zip contains 'terraform' binary and sometimes 'LICENSE.txt' +func createFakeTerraformInstallationFiles(t *testing.T) string { + tmpDir := t.TempDir() + + mij := mustMarshal(t, mainJSON(version1, version2)) + jv1 := mustMarshal(t, versionedJSON(version1)) + jv2 := mustMarshal(t, versionedJSON(version2)) + + // `index.json` + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "index.json"), mij, 0o400)) + + // `${version1}/index.json` + require.NoError(t, os.Mkdir(filepath.Join(tmpDir, version1.String()), 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, version1.String(), "index.json"), jv1, 0o400)) + + // `${version2}/index.json` + require.NoError(t, os.Mkdir(filepath.Join(tmpDir, version2.String()), 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, version2.String(), "index.json"), jv2, 0o400)) + + // `${version1}/linux_amd64.zip` + zip1, err := os.Create(filepath.Join(tmpDir, version1.String(), zipFilename(version1))) + require.NoError(t, err) + zip1Writer := zip.NewWriter(zip1) + + // `${version1}/linux_amd64.zip/terraform` + exe1, err := zip1Writer.Create("terraform") + require.NoError(t, err) + n, err := exe1.Write(exeContent(version1)) + require.NoError(t, err) + require.NotZero(t, n) + + // `${version1}/linux_amd64.zip/LICENSE.txt` + lic1, err := zip1Writer.Create("LICENSE.txt") + require.NoError(t, err) + n, err = lic1.Write([]byte("some license")) + require.NoError(t, err) + require.NotZero(t, n) + require.NoError(t, zip1Writer.Close()) + + // `${version2}/linux_amd64.zip` + zip2, err := os.Create(filepath.Join(tmpDir, version2.String(), zipFilename(version2))) + require.NoError(t, err) + zip2Writer := zip.NewWriter(zip2) + + // `${version1}/linux_amd64.zip/terraform` + exe2, err := zip2Writer.Create("terraform") + require.NoError(t, err) + n, err = exe2.Write(exeContent(version2)) + require.NoError(t, err) + require.NotZero(t, n) + require.NoError(t, zip2Writer.Close()) + + return tmpDir +} + +// starts http server serving fake terraform installation files +func startFakeTerraformServer(t *testing.T, tmpDir string) string { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to create listener") + } + + mux := http.NewServeMux() + fs := http.FileServer(http.Dir(tmpDir)) + mux.Handle("/terraform/", http.StripPrefix("/terraform", fs)) + + srv := http.Server{ + ReadHeaderTimeout: time.Second, + Handler: mux, + } + go srv.Serve(listener) + t.Cleanup(func() { + if err := srv.Close(); err != nil { + t.Errorf("failed to close server: %v", err) + } + }) + return "http://" + listener.Addr().String() +} + func TestInstall(t *testing.T) { t.Parallel() if testing.Short() { @@ -29,6 +205,9 @@ func TestInstall(t *testing.T) { dir := t.TempDir() log := testutil.Logger(t) + tmpDir := createFakeTerraformInstallationFiles(t) + addr := startFakeTerraformServer(t, tmpDir) + // Install spins off 8 installs with Version and waits for them all // to complete. The locking mechanism within Install should // prevent multiple binaries from being installed, so the function @@ -40,7 +219,7 @@ func TestInstall(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - p, err := terraform.Install(ctx, log, false, dir, version) + p, err := terraform.Install(ctx, log, false, dir, version, addr, false) assert.NoError(t, err) paths <- p }() @@ -60,7 +239,6 @@ func TestInstall(t *testing.T) { return firstPath } - version1 := terraform.TerraformVersion binPath := install(version1) checkBinModTime := func() time.Time { @@ -73,13 +251,11 @@ func TestInstall(t *testing.T) { modTime1 := checkBinModTime() // Since we're using the same version the install should be idempotent. - install(terraform.TerraformVersion) + install(version1) modTime2 := checkBinModTime() require.Equal(t, modTime1, modTime2) // Ensure a new install happens when version changes - version2 := version.Must(version.NewVersion("1.2.0")) - // Sanity-check require.NotEqual(t, version2.String(), version1.String()) diff --git a/provisioner/terraform/serve.go b/provisioner/terraform/serve.go index 6b14282f9f..a927f288fa 100644 --- a/provisioner/terraform/serve.go +++ b/provisioner/terraform/serve.go @@ -103,7 +103,7 @@ func Serve(ctx context.Context, options *ServeOptions) error { slog.F("min_version", minTerraformVersion.String())) } - binPath, err := Install(ctx, options.Logger, options.ExternalProvisioner, options.CachePath, TerraformVersion) + binPath, err := Install(ctx, options.Logger, options.ExternalProvisioner, options.CachePath, TerraformVersion, "", true) if err != nil { return xerrors.Errorf("install terraform: %w", err) }