package tools import ( "context" "encoding/json" "fmt" "io" "os/exec" "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/product" "github.com/hashicorp/hc-install/releases" "github.com/hashicorp/terraform-exec/tfexec" tfjson "github.com/hashicorp/terraform-json" ) func Terraform(workingDir string) *terraform { return &terraform{ workingDir: workingDir, version: "1.14.5", logger: tfLogParser{}, vars: []string{}, } } type ( terraform struct { tf *tfexec.Terraform state *tfjson.State workingDir string version string logger io.Writer vars []string } tfLogParser struct{} ) func (_ tfLogParser) Write(p []byte) (n int, err error) { var logEntry tfjson.LogMessage err = json.Unmarshal(p, &logEntry) if err != nil { return 0, err } msg := []byte(fmt.Sprintf("[%s] %s\n", logEntry.Level(), logEntry.Message())) log.Write(msg) return len(msg), nil } func (t *terraform) VarsFile(path string) *terraform { t.vars = append(t.vars, path) return t } func (t *terraform) Version(version string) *terraform { t.version = version return t } func (t *terraform) Init() error { var err error var execPath = "" var install = true execPath, err = exec.LookPath("terraform") if err == nil { t.tf, err = tfexec.NewTerraform(t.workingDir, execPath) if err == nil { v, _, err := t.tf.Version(context.Background(), true) if err == nil { if v.String() == t.version { install = false } } } } if install { installer := &releases.ExactVersion{ Product: product.Terraform, Version: version.Must(version.NewVersion(t.version)), } execPath, err = installer.Install(context.Background()) if err != nil { return err } } t.tf, err = tfexec.NewTerraform(t.workingDir, execPath) if err != nil { return err } err = t.tf.InitJSON(context.Background(), t.logger, tfexec.Upgrade(true), ) if err != nil { return err } t.state, err = t.tf.Show(context.Background()) if err != nil { return err } return nil } func (t *terraform) Plan() (*tfjson.Plan, error) { if t.tf == nil { err := t.Init() if err != nil { return nil, err } } opts := []tfexec.PlanOption{ tfexec.Out("terraform.plan"), } for _, f := range t.vars { tfexec.VarFile(f) } _, err := t.tf.PlanJSON(context.Background(), t.logger, opts..., ) if err != nil { return nil, err } plan, err := t.tf.ShowPlanFile(context.Background(), t.workingDir+"/terraform.plan") if err != nil { return nil, err } return plan, nil } func (t *terraform) Apply() error { if t.tf == nil { err := t.Init() if err != nil { return err } } err := t.tf.ApplyJSON(context.Background(), t.logger) if err != nil { return err } t.state, err = t.tf.Show(context.Background()) if err != nil { return err } return nil } func (t *terraform) Show() error { if t.tf == nil { err := t.Init() if err != nil { return err } } var err error t.state, err = t.tf.Show(context.Background()) if err != nil { return err } return nil } func (t *terraform) Outputs() map[string]any { outputs := map[string]any{} if t.state != nil { if t.state.Values != nil { if t.state.Values.Outputs != nil { for k, v := range t.state.Values.Outputs { outputs[k] = v.Value } } } } return outputs } func (t *terraform) State() *tfjson.StateModule { if t.state != nil { if t.state.Values != nil { return t.state.Values.RootModule } } return nil } func (t *terraform) Destroy() error { if t.tf == nil { err := t.Init() if err != nil { return err } } err := t.tf.DestroyJSON(context.Background(), t.logger) if err != nil { return err } t.state, err = t.tf.Show(context.Background()) if err != nil { return err } return nil }