package tfexec import ( "context" "fmt" "io" "io/ioutil" "log" "os" "sync" "github.com/hashicorp/go-version" ) type printfer interface { Printf(format string, v ...interface{}) } // Terraform represents the Terraform CLI executable and working directory. // // Typically this is constructed against the root module of a Terraform configuration // but you can override paths used in some commands depending on the available // options. // // All functions that execute CLI commands take a context.Context. It should be noted that // exec.Cmd.Run will not return context.DeadlineExceeded or context.Canceled by default, we // have augmented our wrapped errors to respond true to errors.Is for context.DeadlineExceeded // and context.Canceled if those are present on the context when the error is parsed. See // https://github.com/golang/go/issues/21880 for more about the Go limitations. // // By default, the instance inherits the environment from the calling code (using os.Environ) // but it ignores certain environment variables that are managed within the code and prohibits // setting them through SetEnv: // // - TF_APPEND_USER_AGENT // - TF_IN_AUTOMATION // - TF_INPUT // - TF_LOG // - TF_LOG_PATH // - TF_REATTACH_PROVIDERS // - TF_DISABLE_PLUGIN_TLS // - TF_SKIP_PROVIDER_VERIFY type Terraform struct { execPath string workingDir string appendUserAgent string disablePluginTLS bool skipProviderVerify bool env map[string]string stdout io.Writer stderr io.Writer logger printfer logPath string versionLock sync.Mutex execVersion *version.Version provVersions map[string]*version.Version } // NewTerraform returns a Terraform struct with default values for all fields. // If a blank execPath is supplied, NewTerraform will error. // Use hc-install or output from os.LookPath to get a desirable execPath. func NewTerraform(workingDir string, execPath string) (*Terraform, error) { if workingDir == "" { return nil, fmt.Errorf("Terraform cannot be initialised with empty workdir") } if _, err := os.Stat(workingDir); err != nil { return nil, fmt.Errorf("error initialising Terraform with workdir %s: %s", workingDir, err) } if execPath == "" { err := fmt.Errorf("NewTerraform: please supply the path to a Terraform executable using execPath, e.g. using the github.com/hashicorp/hc-install module.") return nil, &ErrNoSuitableBinary{ err: err, } } tf := Terraform{ execPath: execPath, workingDir: workingDir, env: nil, // explicit nil means copy os.Environ logger: log.New(ioutil.Discard, "", 0), } return &tf, nil } // SetEnv allows you to override environment variables, this should not be used for any well known // Terraform environment variables that are already covered in options. Pass nil to copy the values // from os.Environ. Attempting to set environment variables that should be managed manually will // result in ErrManualEnvVar being returned. func (tf *Terraform) SetEnv(env map[string]string) error { prohibited := ProhibitedEnv(env) if len(prohibited) > 0 { // just error on the first instance return &ErrManualEnvVar{prohibited[0]} } tf.env = env return nil } // SetLogger specifies a logger for tfexec to use. func (tf *Terraform) SetLogger(logger printfer) { tf.logger = logger } // SetStdout specifies a writer to stream stdout to for every command. // // This should be used for information or logging purposes only, not control // flow. Any parsing necessary should be added as functionality to this package. func (tf *Terraform) SetStdout(w io.Writer) { tf.stdout = w } // SetStderr specifies a writer to stream stderr to for every command. // // This should be used for information or logging purposes only, not control // flow. Any parsing necessary should be added as functionality to this package. func (tf *Terraform) SetStderr(w io.Writer) { tf.stderr = w } // SetLogPath sets the TF_LOG_PATH environment variable for Terraform CLI // execution. func (tf *Terraform) SetLogPath(path string) error { tf.logPath = path return nil } // SetAppendUserAgent sets the TF_APPEND_USER_AGENT environment variable for // Terraform CLI execution. func (tf *Terraform) SetAppendUserAgent(ua string) error { tf.appendUserAgent = ua return nil } // SetDisablePluginTLS sets the TF_DISABLE_PLUGIN_TLS environment variable for // Terraform CLI execution. func (tf *Terraform) SetDisablePluginTLS(disabled bool) error { tf.disablePluginTLS = disabled return nil } // SetSkipProviderVerify sets the TF_SKIP_PROVIDER_VERIFY environment variable // for Terraform CLI execution. This is no longer used in 0.13.0 and greater. func (tf *Terraform) SetSkipProviderVerify(skip bool) error { err := tf.compatible(context.Background(), nil, tf0_13_0) if err != nil { return err } tf.skipProviderVerify = skip return nil } // WorkingDir returns the working directory for Terraform. func (tf *Terraform) WorkingDir() string { return tf.workingDir } // ExecPath returns the path to the Terraform executable. func (tf *Terraform) ExecPath() string { return tf.execPath }