1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
package tftest
import (
"bytes"
"encoding/json"
"fmt"
getter "github.com/hashicorp/go-getter"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
const releaseHost = "https://releases.hashicorp.com"
// FindTerraform attempts to find a Terraform CLI executable for plugin testing.
//
// As a first preference it will look for the environment variable
// TF_ACC_TERRAFORM_PATH and return its value. If that variable is not set, it will
// look in PATH for a program named "terraform" and, if one is found, return
// its absolute path.
//
// If no Terraform executable can be found, the result is the empty string. In
// that case, the test program will usually fail outright.
func FindTerraform() string {
if p := os.Getenv("TF_ACC_TERRAFORM_PATH"); p != "" {
return p
}
p, err := exec.LookPath("terraform")
if err != nil {
return ""
}
return p
}
func tfURL(version, osName, archName string) string {
return fmt.Sprintf(
"%s/terraform/%s/terraform_%s_%s_%s.zip",
releaseHost, version, version, osName, archName,
)
}
// InstallTerraform downloads and decompresses a Terraform CLI executable with
// the specified version, downloaded from the HashiCorp releases page over HTTP.
//
// The version string must match an existing Terraform release semver version,
// e.g. 0.12.5.
//
// The terraform executable is installed to a temporary folder.
//
// FIXME: Temporary folder should be cleaned up after tests have finished.
func InstallTerraform(tfVersion string) (string, error) {
osName := runtime.GOOS
archName := runtime.GOARCH
var tfDir string
var err error
tempDir := os.Getenv("TF_ACC_TEMP_DIR")
tfDir, err = ioutil.TempDir(tempDir, "tftest-terraform")
if err != nil {
return "", fmt.Errorf("failed to create temp dir: %s", err)
}
url := tfURL(tfVersion, osName, archName)
client := getter.Client{
Src: url,
Dst: tfDir,
Mode: getter.ClientModeDir,
}
err = client.Get()
if err != nil {
return "", fmt.Errorf("failed to download terraform from %s: %s", url, err)
}
return filepath.Join(tfDir, "terraform"), nil
}
// getTerraformEnv returns the appropriate Env for the Terraform command.
func getTerraformEnv() []string {
var env []string
for _, e := range os.Environ() {
env = append(env, e)
}
env = append(env, "TF_DISABLE_PLUGIN_TLS=1")
env = append(env, "TF_SKIP_PROVIDER_VERIFY=1")
// FIXME: Ideally in testing.Verbose mode we'd turn on Terraform DEBUG
// logging, perhaps redirected to a separate fd other than stderr to avoid
// polluting it, and then propagate the log lines out into t.Log so that
// they are visible to the person running the test. Currently though,
// Terraform CLI is able to send logs only to either an on-disk file or
// to stderr.
env = append(env, "TF_LOG=") // so logging can't pollute our stderr output
env = append(env, "TF_INPUT=0")
if p := os.Getenv("TF_ACC_LOG_PATH"); p != "" {
env = append(env, "TF_LOG=TRACE")
env = append(env, "TF_LOG_PATH="+p)
}
return env
}
// RunTerraform runs the configured Terraform CLI executable with the given
// arguments, returning an error if it produces a non-successful exit status.
func (wd *WorkingDir) runTerraform(args ...string) error {
allArgs := []string{"terraform"}
allArgs = append(allArgs, args...)
env := getTerraformEnv()
var errBuf strings.Builder
cmd := &exec.Cmd{
Path: wd.h.TerraformExecPath(),
Args: allArgs,
Dir: wd.baseDir,
Stderr: &errBuf,
Env: env,
}
err := cmd.Run()
if tErr, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("terraform failed: %s\n\nstderr:\n%s", tErr.ProcessState.String(), errBuf.String())
}
return err
}
// runTerraformJSON runs the configured Terraform CLI executable with the given
// arguments and tries to decode its stdout into the given target value (which
// must be a non-nil pointer) as JSON.
func (wd *WorkingDir) runTerraformJSON(target interface{}, args ...string) error {
allArgs := []string{"terraform"}
allArgs = append(allArgs, args...)
env := getTerraformEnv()
var outBuf bytes.Buffer
var errBuf strings.Builder
cmd := &exec.Cmd{
Path: wd.h.TerraformExecPath(),
Args: allArgs,
Dir: wd.baseDir,
Stderr: &errBuf,
Stdout: &outBuf,
Env: env,
}
err := cmd.Run()
if err != nil {
if tErr, ok := err.(*exec.ExitError); ok {
err = fmt.Errorf("terraform failed: %s\n\nstderr:\n%s", tErr.ProcessState.String(), errBuf.String())
}
return err
}
return json.Unmarshal(outBuf.Bytes(), target)
}
|