From 531dc1c630d9095ae6ff301e00a044a8129eeaaa Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Wed, 18 May 2022 01:47:41 -0500 Subject: [PATCH] tfexec: Add (Terraform).SetLog() method (#291) * tfexec: Add (Terraform).SetLog() method Reference: https://github.com/hashicorp/terraform-exec/issues/290 * tfexec: Add testing for SetLog * tfexec: Add SetLogPath testing * tfexec: Ensure SetLog version compatibility is 0.15 minimum, testing updates for darwin/arm64 --- tfexec/cmd.go | 3 +- tfexec/terraform.go | 30 +++++- tfexec/terraform_test.go | 211 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 5 deletions(-) diff --git a/tfexec/cmd.go b/tfexec/cmd.go index 83abd22d..21243624 100644 --- a/tfexec/cmd.go +++ b/tfexec/cmd.go @@ -149,8 +149,7 @@ func (tf *Terraform) buildEnv(mergeEnv map[string]string) []string { env[logPathEnvVar] = "" } else { env[logPathEnvVar] = tf.logPath - // Log levels other than TRACE are currently unreliable, the CLI recommends using TRACE only. - env[logEnvVar] = "TRACE" + env[logEnvVar] = tf.log } // constant automation override env vars diff --git a/tfexec/terraform.go b/tfexec/terraform.go index 2ad143a4..835e7e06 100644 --- a/tfexec/terraform.go +++ b/tfexec/terraform.go @@ -48,9 +48,14 @@ type Terraform struct { skipProviderVerify bool env map[string]string - stdout io.Writer - stderr io.Writer - logger printfer + stdout io.Writer + stderr io.Writer + logger printfer + + // TF_LOG environment variable, defaults to TRACE if logPath is set. + log string + + // TF_LOG_PATH environment variable logPath string versionLock sync.Mutex @@ -122,10 +127,29 @@ func (tf *Terraform) SetStderr(w io.Writer) { tf.stderr = w } +// 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 for +// those earlier versions when SetLogPath is called. +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 +} + // SetLogPath sets the TF_LOG_PATH environment variable for Terraform CLI // execution. func (tf *Terraform) SetLogPath(path string) error { tf.logPath = path + // Prevent setting the log path without enabling logging + if tf.log == "" { + tf.log = "TRACE" + } return nil } diff --git a/tfexec/terraform_test.go b/tfexec/terraform_test.go index d8f4ab4c..d6521b4c 100644 --- a/tfexec/terraform_test.go +++ b/tfexec/terraform_test.go @@ -5,6 +5,8 @@ import ( "errors" "io/ioutil" "os" + "path/filepath" + "runtime" "testing" "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" @@ -61,6 +63,215 @@ func TestSetEnv(t *testing.T) { } } +func TestSetLog(t *testing.T) { + td := t.TempDir() + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1)) + + if err != nil { + t.Fatalf("unexpected NewTerraform error: %s", err) + } + + // Required so all testing environment variables are not copied. + err = tf.SetEnv(map[string]string{ + "CLEARENV": "1", + }) + + if err != nil { + t.Fatalf("unexpected SetEnv error: %s", err) + } + + t.Run("case 1: SetLog <= 0.15 error", func(t *testing.T) { + if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { + t.Skip("Terraform for darwin/arm64 is not available until v1") + } + + td012 := t.TempDir() + + tf012, err := NewTerraform(td012, tfVersion(t, testutil.Latest012)) + + if err != nil { + t.Fatalf("unexpected NewTerraform error: %s", err) + } + + err = tf012.SetLog("TRACE") + + if err == nil { + t.Fatal("expected SetLog error, got none") + } + }) + + t.Run("case 2: SetLog TRACE no SetLogPath", func(t *testing.T) { + err := tf.SetLog("TRACE") + + if err != nil { + t.Fatalf("unexpected SetLog error: %s", err) + } + + initCmd, err := tf.initCmd(context.Background()) + + if err != nil { + t.Fatalf("unexpected command error: %s", err) + } + + assertCmd(t, []string{ + "init", + "-no-color", + "-force-copy", + "-input=false", + "-backend=true", + "-get=true", + "-upgrade=false", + }, map[string]string{ + "CLEARENV": "1", + "TF_LOG": "", + }, initCmd) + }) + + t.Run("case 3: SetLog TRACE and SetLogPath", func(t *testing.T) { + tfLogPath := filepath.Join(td, "test.log") + + err := tf.SetLog("TRACE") + + if err != nil { + t.Fatalf("unexpected SetLog error: %s", err) + } + + err = tf.SetLogPath(tfLogPath) + + if err != nil { + t.Fatalf("unexpected SetLogPath error: %s", err) + } + + initCmd, err := tf.initCmd(context.Background()) + + if err != nil { + t.Fatalf("unexpected command error: %s", err) + } + + assertCmd(t, []string{ + "init", + "-no-color", + "-force-copy", + "-input=false", + "-backend=true", + "-get=true", + "-upgrade=false", + }, map[string]string{ + "CLEARENV": "1", + "TF_LOG": "TRACE", + "TF_LOG_PATH": tfLogPath, + }, initCmd) + }) + + t.Run("case 4: SetLog DEBUG and SetLogPath", func(t *testing.T) { + tfLogPath := filepath.Join(td, "test.log") + + err := tf.SetLog("DEBUG") + + if err != nil { + t.Fatalf("unexpected SetLog error: %s", err) + } + + err = tf.SetLogPath(tfLogPath) + + if err != nil { + t.Fatalf("unexpected SetLogPath error: %s", err) + } + + initCmd, err := tf.initCmd(context.Background()) + + if err != nil { + t.Fatalf("unexpected command error: %s", err) + } + + assertCmd(t, []string{ + "init", + "-no-color", + "-force-copy", + "-input=false", + "-backend=true", + "-get=true", + "-upgrade=false", + }, map[string]string{ + "CLEARENV": "1", + "TF_LOG": "DEBUG", + "TF_LOG_PATH": tfLogPath, + }, initCmd) + }) +} + +func TestSetLogPath(t *testing.T) { + td := t.TempDir() + + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest_v1)) + + if err != nil { + t.Fatalf("unexpected NewTerraform error: %s", err) + } + + // Required so all testing environment variables are not copied. + err = tf.SetEnv(map[string]string{ + "CLEARENV": "1", + }) + + if err != nil { + t.Fatalf("unexpected SetEnv error: %s", err) + } + + t.Run("case 1: No SetLogPath", func(t *testing.T) { + initCmd, err := tf.initCmd(context.Background()) + + if err != nil { + t.Fatalf("unexpected command error: %s", err) + } + + assertCmd(t, []string{ + "init", + "-no-color", + "-force-copy", + "-input=false", + "-backend=true", + "-get=true", + "-upgrade=false", + }, map[string]string{ + "CLEARENV": "1", + "TF_LOG": "", + "TF_LOG_PATH": "", + }, initCmd) + }) + + t.Run("case 2: SetLogPath sets TF_LOG and TF_LOG_PATH", func(t *testing.T) { + tfLogPath := filepath.Join(td, "test.log") + + err = tf.SetLogPath(tfLogPath) + + if err != nil { + t.Fatalf("unexpected SetLogPath error: %s", err) + } + + initCmd, err := tf.initCmd(context.Background()) + + if err != nil { + t.Fatalf("unexpected command error: %s", err) + } + + assertCmd(t, []string{ + "init", + "-no-color", + "-force-copy", + "-input=false", + "-backend=true", + "-get=true", + "-upgrade=false", + }, map[string]string{ + "CLEARENV": "1", + "TF_LOG": "TRACE", + "TF_LOG_PATH": tfLogPath, + }, initCmd) + }) +} + func TestCheckpointDisablePropagation(t *testing.T) { td := t.TempDir()