mirror of https://github.com/coder/coder.git
feat: Download default terraform version when minor version mismatches (#1775)
This commit is contained in:
parent
6a2a145545
commit
c6b1daabc5
|
@ -197,7 +197,7 @@ jobs:
|
|||
|
||||
- uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: 1.1.2
|
||||
terraform_version: 1.1.9
|
||||
terraform_wrapper: false
|
||||
|
||||
- name: Test with Mock Database
|
||||
|
@ -264,7 +264,7 @@ jobs:
|
|||
|
||||
- uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: 1.1.2
|
||||
terraform_version: 1.1.9
|
||||
terraform_wrapper: false
|
||||
|
||||
- name: Start PostgreSQL Database
|
||||
|
@ -494,7 +494,7 @@ jobs:
|
|||
|
||||
- uses: hashicorp/setup-terraform@v2
|
||||
with:
|
||||
terraform_version: 1.1.2
|
||||
terraform_version: 1.1.9
|
||||
terraform_wrapper: false
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
|
|
|
@ -376,7 +376,6 @@ func server() *cobra.Command {
|
|||
shutdownConnsCtx, shutdownConns := context.WithCancel(cmd.Context())
|
||||
defer shutdownConns()
|
||||
go func() {
|
||||
defer close(errCh)
|
||||
server := http.Server{
|
||||
// These errors are typically noise like "TLS: EOF". Vault does similar:
|
||||
// https://github.com/hashicorp/vault/blob/e2490059d0711635e529a4efcbaa1b26998d6e1c/command/server.go#L2714
|
||||
|
@ -590,7 +589,7 @@ func newProvisionerDaemon(ctx context.Context, coderAPI *coderd.API,
|
|||
CachePath: cacheDir,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
if err != nil && !xerrors.Is(err, context.Canceled) {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
|
|
@ -104,11 +104,22 @@ func (e executor) checkMinVersion(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (e executor) version(ctx context.Context) (*version.Version, error) {
|
||||
return versionFromBinaryPath(ctx, e.binaryPath)
|
||||
}
|
||||
|
||||
func versionFromBinaryPath(ctx context.Context, binaryPath string) (*version.Version, error) {
|
||||
// #nosec
|
||||
cmd := exec.CommandContext(ctx, e.binaryPath, "version", "-json")
|
||||
cmd := exec.CommandContext(ctx, binaryPath, "version", "-json")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
select {
|
||||
// `exec` library throws a `signal: killed`` error instead of the canceled context.
|
||||
// Since we know the cause for the killed signal, we are throwing the relevant error here.
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
vj := tfjson.VersionOutput{}
|
||||
err = json.Unmarshal(out, &vj)
|
||||
|
|
|
@ -16,7 +16,9 @@ import (
|
|||
|
||||
// This is the exact version of Terraform used internally
|
||||
// when Terraform is missing on the system.
|
||||
const terraformVersion = "1.1.9"
|
||||
var terraformVersion = version.Must(version.NewVersion("1.1.9"))
|
||||
var minTerraformVersion = version.Must(version.NewVersion("1.1.0"))
|
||||
var maxTerraformVersion = version.Must(version.NewVersion("1.2.0"))
|
||||
|
||||
var (
|
||||
// The minimum version of Terraform supported by the provisioner.
|
||||
|
@ -31,6 +33,8 @@ var (
|
|||
}()
|
||||
)
|
||||
|
||||
var terraformMinorVersionMismatch = xerrors.New("Terraform binary minor version mismatch.")
|
||||
|
||||
type ServeOptions struct {
|
||||
*provisionersdk.ServeOptions
|
||||
|
||||
|
@ -41,15 +45,51 @@ type ServeOptions struct {
|
|||
Logger slog.Logger
|
||||
}
|
||||
|
||||
func absoluteBinaryPath(ctx context.Context) (string, error) {
|
||||
binaryPath, err := safeexec.LookPath("terraform")
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("Terraform binary not found: %w", err)
|
||||
}
|
||||
|
||||
// If the "coder" binary is in the same directory as
|
||||
// the "terraform" binary, "terraform" is returned.
|
||||
//
|
||||
// We must resolve the absolute path for other processes
|
||||
// to execute this properly!
|
||||
absoluteBinary, err := filepath.Abs(binaryPath)
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("Terraform binary absolute path not found: %w", err)
|
||||
}
|
||||
|
||||
// Checking the installed version of Terraform.
|
||||
version, err := versionFromBinaryPath(ctx, absoluteBinary)
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("Terraform binary get version failed: %w", err)
|
||||
}
|
||||
|
||||
if version.LessThan(minTerraformVersion) || version.GreaterThanOrEqual(maxTerraformVersion) {
|
||||
return "", terraformMinorVersionMismatch
|
||||
}
|
||||
|
||||
return absoluteBinary, nil
|
||||
}
|
||||
|
||||
// Serve starts a dRPC server on the provided transport speaking Terraform provisioner.
|
||||
func Serve(ctx context.Context, options *ServeOptions) error {
|
||||
if options.BinaryPath == "" {
|
||||
binaryPath, err := safeexec.LookPath("terraform")
|
||||
absoluteBinary, err := absoluteBinaryPath(ctx)
|
||||
if err != nil {
|
||||
// This is an early exit to prevent extra execution in case the context is canceled.
|
||||
// It generally happens in unit tests since this method is asynchronous and
|
||||
// the unit test kills the app before this is complete.
|
||||
if xerrors.Is(err, context.Canceled) {
|
||||
return xerrors.Errorf("absolute binary context canceled: %w", err)
|
||||
}
|
||||
|
||||
installer := &releases.ExactVersion{
|
||||
InstallDir: options.CachePath,
|
||||
Product: product.Terraform,
|
||||
Version: version.Must(version.NewVersion(terraformVersion)),
|
||||
Version: terraformVersion,
|
||||
}
|
||||
|
||||
execPath, err := installer.Install(ctx)
|
||||
|
@ -58,15 +98,6 @@ func Serve(ctx context.Context, options *ServeOptions) error {
|
|||
}
|
||||
options.BinaryPath = execPath
|
||||
} else {
|
||||
// If the "coder" binary is in the same directory as
|
||||
// the "terraform" binary, "terraform" is returned.
|
||||
//
|
||||
// We must resolve the absolute path for other processes
|
||||
// to execute this properly!
|
||||
absoluteBinary, err := filepath.Abs(binaryPath)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("absolute: %w", err)
|
||||
}
|
||||
options.BinaryPath = absoluteBinary
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// nolint:paralleltest
|
||||
func Test_absoluteBinaryPath(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
terraformVersion string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "TestCorrectVersion",
|
||||
args: args{ctx: context.Background()},
|
||||
terraformVersion: "1.1.9",
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "TestOldVersion",
|
||||
args: args{ctx: context.Background()},
|
||||
terraformVersion: "1.0.9",
|
||||
expectedErr: terraformMinorVersionMismatch,
|
||||
},
|
||||
{
|
||||
name: "TestNewVersion",
|
||||
args: args{ctx: context.Background()},
|
||||
terraformVersion: "1.2.9",
|
||||
expectedErr: terraformMinorVersionMismatch,
|
||||
},
|
||||
{
|
||||
name: "TestMalformedVersion",
|
||||
args: args{ctx: context.Background()},
|
||||
terraformVersion: "version",
|
||||
expectedErr: xerrors.Errorf("Terraform binary get version failed: Malformed version: version"),
|
||||
},
|
||||
}
|
||||
// nolint:paralleltest
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Dummy terraform executable on Windows requires sh which isn't very practical.")
|
||||
}
|
||||
|
||||
// Create a temp dir with the binary
|
||||
tempDir := t.TempDir()
|
||||
terraformBinaryOutput := fmt.Sprintf(`#!/bin/sh
|
||||
cat <<-EOF
|
||||
{
|
||||
"terraform_version": "%s",
|
||||
"platform": "linux_amd64",
|
||||
"provider_selections": {},
|
||||
"terraform_outdated": false
|
||||
}
|
||||
EOF`, tt.terraformVersion)
|
||||
|
||||
// #nosec
|
||||
err := os.WriteFile(
|
||||
filepath.Join(tempDir, "terraform"),
|
||||
[]byte(terraformBinaryOutput),
|
||||
0770,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add the binary to PATH
|
||||
pathVariable := os.Getenv("PATH")
|
||||
t.Setenv("PATH", strings.Join([]string{tempDir, pathVariable}, ":"))
|
||||
|
||||
var expectedAbsoluteBinary string
|
||||
if tt.expectedErr == nil {
|
||||
expectedAbsoluteBinary = filepath.Join(tempDir, "terraform")
|
||||
}
|
||||
|
||||
actualAbsoluteBinary, actualErr := absoluteBinaryPath(tt.args.ctx)
|
||||
|
||||
require.Equal(t, expectedAbsoluteBinary, actualAbsoluteBinary)
|
||||
if tt.expectedErr == nil {
|
||||
require.NoError(t, actualErr)
|
||||
} else {
|
||||
require.EqualError(t, actualErr, tt.expectedErr.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue