2022-04-03 04:07:16 +00:00
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:
//
2023-03-20 20:25:53 +00:00
// - 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
2022-04-03 04:07:16 +00:00
type Terraform struct {
execPath string
workingDir string
appendUserAgent string
disablePluginTLS bool
skipProviderVerify bool
env map [ string ] string
2022-08-06 14:21:18 +00:00
stdout io . Writer
stderr io . Writer
logger printfer
// TF_LOG environment variable, defaults to TRACE if logPath is set.
log string
// TF_LOG_CORE environment variable
logCore string
// TF_LOG_PATH environment variable
2022-04-03 04:07:16 +00:00
logPath string
2022-08-06 14:21:18 +00:00
// TF_LOG_PROVIDER environment variable
logProvider string
2022-04-03 04:07:16 +00:00
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
}
2022-08-06 14:21:18 +00:00
// SetLog sets the TF_LOG environment variable for Terraform CLI execution.
// This must be combined with a call to SetLogPath to take effect.
//
// This is only compatible with Terraform CLI 0.15.0 or later as setting the
// log level was unreliable in earlier versions. It will default to TRACE when
// SetLogPath is called on versions 0.14.11 and earlier, or if SetLogCore and
// SetLogProvider have not been called before SetLogPath on versions 0.15.0 and
// later.
func ( tf * Terraform ) SetLog ( log string ) error {
err := tf . compatible ( context . Background ( ) , tf0_15_0 , nil )
if err != nil {
return err
}
tf . log = log
return nil
}
// SetLogCore sets the TF_LOG_CORE environment variable for Terraform CLI
// execution. This must be combined with a call to SetLogPath to take effect.
//
// This is only compatible with Terraform CLI 0.15.0 or later.
func ( tf * Terraform ) SetLogCore ( logCore string ) error {
err := tf . compatible ( context . Background ( ) , tf0_15_0 , nil )
if err != nil {
return err
}
tf . logCore = logCore
return nil
}
2022-04-03 04:07:16 +00:00
// SetLogPath sets the TF_LOG_PATH environment variable for Terraform CLI
// execution.
func ( tf * Terraform ) SetLogPath ( path string ) error {
tf . logPath = path
2022-08-06 14:21:18 +00:00
// Prevent setting the log path without enabling logging
if tf . log == "" && tf . logCore == "" && tf . logProvider == "" {
tf . log = "TRACE"
}
return nil
}
// SetLogProvider sets the TF_LOG_PROVIDER environment variable for Terraform
// CLI execution. This must be combined with a call to SetLogPath to take
// effect.
//
// This is only compatible with Terraform CLI 0.15.0 or later.
func ( tf * Terraform ) SetLogProvider ( logProvider string ) error {
err := tf . compatible ( context . Background ( ) , tf0_15_0 , nil )
if err != nil {
return err
}
tf . logProvider = logProvider
2022-04-03 04:07:16 +00:00
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
}