terraform-provider-gitea/vendor/github.com/hashicorp/terraform-exec/tfexec/cmd.go
Tobias Trabelsi e1266ebf64
Some checks reported errors
continuous-integration/drone/pr Build encountered an error
continuous-integration/drone/push Build encountered an error
updated GHA
Update to v2 SDK
updated dependencies
2022-08-06 16:21:18 +02:00

275 lines
6.0 KiB
Go

package tfexec
import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
"github.com/hashicorp/terraform-exec/internal/version"
)
const (
checkpointDisableEnvVar = "CHECKPOINT_DISABLE"
cliArgsEnvVar = "TF_CLI_ARGS"
inputEnvVar = "TF_INPUT"
automationEnvVar = "TF_IN_AUTOMATION"
logEnvVar = "TF_LOG"
logCoreEnvVar = "TF_LOG_CORE"
logPathEnvVar = "TF_LOG_PATH"
logProviderEnvVar = "TF_LOG_PROVIDER"
reattachEnvVar = "TF_REATTACH_PROVIDERS"
appendUserAgentEnvVar = "TF_APPEND_USER_AGENT"
workspaceEnvVar = "TF_WORKSPACE"
disablePluginTLSEnvVar = "TF_DISABLE_PLUGIN_TLS"
skipProviderVerifyEnvVar = "TF_SKIP_PROVIDER_VERIFY"
varEnvVarPrefix = "TF_VAR_"
cliArgEnvVarPrefix = "TF_CLI_ARGS_"
)
var prohibitedEnvVars = []string{
cliArgsEnvVar,
inputEnvVar,
automationEnvVar,
logEnvVar,
logCoreEnvVar,
logPathEnvVar,
logProviderEnvVar,
reattachEnvVar,
appendUserAgentEnvVar,
workspaceEnvVar,
disablePluginTLSEnvVar,
skipProviderVerifyEnvVar,
}
var prohibitedEnvVarPrefixes = []string{
varEnvVarPrefix,
cliArgEnvVarPrefix,
}
func manualEnvVars(env map[string]string, cb func(k string)) {
for k := range env {
for _, p := range prohibitedEnvVars {
if p == k {
cb(k)
goto NextEnvVar
}
}
for _, prefix := range prohibitedEnvVarPrefixes {
if strings.HasPrefix(k, prefix) {
cb(k)
goto NextEnvVar
}
}
NextEnvVar:
}
}
// ProhibitedEnv returns a slice of environment variable keys that are not allowed
// to be set manually from the passed environment.
func ProhibitedEnv(env map[string]string) []string {
var p []string
manualEnvVars(env, func(k string) {
p = append(p, k)
})
return p
}
// CleanEnv removes any prohibited environment variables from an environment map.
func CleanEnv(dirty map[string]string) map[string]string {
clean := dirty
manualEnvVars(clean, func(k string) {
delete(clean, k)
})
return clean
}
func envMap(environ []string) map[string]string {
env := map[string]string{}
for _, ev := range environ {
parts := strings.SplitN(ev, "=", 2)
if len(parts) == 0 {
continue
}
k := parts[0]
v := ""
if len(parts) == 2 {
v = parts[1]
}
env[k] = v
}
return env
}
func envSlice(environ map[string]string) []string {
env := []string{}
for k, v := range environ {
env = append(env, k+"="+v)
}
return env
}
func (tf *Terraform) buildEnv(mergeEnv map[string]string) []string {
// set Terraform level env, if env is nil, fall back to os.Environ
var env map[string]string
if tf.env == nil {
env = envMap(os.Environ())
} else {
env = make(map[string]string, len(tf.env))
for k, v := range tf.env {
env[k] = v
}
}
// override env with any command specific environment
for k, v := range mergeEnv {
env[k] = v
}
// always propagate CHECKPOINT_DISABLE env var unless it is
// explicitly overridden with tf.SetEnv or command env
if _, ok := env[checkpointDisableEnvVar]; !ok {
env[checkpointDisableEnvVar] = os.Getenv(checkpointDisableEnvVar)
}
// always override user agent
ua := mergeUserAgent(
os.Getenv(appendUserAgentEnvVar),
tf.appendUserAgent,
fmt.Sprintf("HashiCorp-terraform-exec/%s", version.ModuleVersion()),
)
env[appendUserAgentEnvVar] = ua
// always override logging
if tf.logPath == "" {
// so logging can't pollute our stderr output
env[logEnvVar] = ""
env[logCoreEnvVar] = ""
env[logPathEnvVar] = ""
env[logProviderEnvVar] = ""
} else {
env[logEnvVar] = tf.log
env[logCoreEnvVar] = tf.logCore
env[logPathEnvVar] = tf.logPath
env[logProviderEnvVar] = tf.logProvider
}
// constant automation override env vars
env[automationEnvVar] = "1"
// force usage of workspace methods for switching
env[workspaceEnvVar] = ""
if tf.disablePluginTLS {
env[disablePluginTLSEnvVar] = "1"
}
if tf.skipProviderVerify {
env[skipProviderVerifyEnvVar] = "1"
}
return envSlice(env)
}
func (tf *Terraform) buildTerraformCmd(ctx context.Context, mergeEnv map[string]string, args ...string) *exec.Cmd {
cmd := exec.CommandContext(ctx, tf.execPath, args...)
cmd.Env = tf.buildEnv(mergeEnv)
cmd.Dir = tf.workingDir
tf.logger.Printf("[INFO] running Terraform command: %s", cmd.String())
return cmd
}
func (tf *Terraform) runTerraformCmdJSON(ctx context.Context, cmd *exec.Cmd, v interface{}) error {
var outbuf = bytes.Buffer{}
cmd.Stdout = mergeWriters(cmd.Stdout, &outbuf)
err := tf.runTerraformCmd(ctx, cmd)
if err != nil {
return err
}
dec := json.NewDecoder(&outbuf)
dec.UseNumber()
return dec.Decode(v)
}
// mergeUserAgent does some minor deduplication to ensure we aren't
// just using the same append string over and over.
func mergeUserAgent(uas ...string) string {
included := map[string]bool{}
merged := []string{}
for _, ua := range uas {
ua = strings.TrimSpace(ua)
if ua == "" {
continue
}
if included[ua] {
continue
}
included[ua] = true
merged = append(merged, ua)
}
return strings.Join(merged, " ")
}
func mergeWriters(writers ...io.Writer) io.Writer {
compact := []io.Writer{}
for _, w := range writers {
if w != nil {
compact = append(compact, w)
}
}
if len(compact) == 0 {
return ioutil.Discard
}
if len(compact) == 1 {
return compact[0]
}
return io.MultiWriter(compact...)
}
func writeOutput(ctx context.Context, r io.ReadCloser, w io.Writer) error {
// ReadBytes will block until bytes are read, which can cause a delay in
// returning even if the command's context has been canceled. Use a separate
// goroutine to prompt ReadBytes to return on cancel
closeCtx, closeCancel := context.WithCancel(ctx)
defer closeCancel()
go func() {
select {
case <-ctx.Done():
r.Close()
case <-closeCtx.Done():
return
}
}()
buf := bufio.NewReader(r)
for {
line, err := buf.ReadBytes('\n')
if len(line) > 0 {
if _, err := w.Write(line); err != nil {
return err
}
}
if err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return err
}
}
}