| 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 |
| } |