From 59ae6cb7ffa3f66c28d81a6c374e89d2db33d824 Mon Sep 17 00:00:00 2001 From: Paul Tyng Date: Fri, 14 Aug 2020 13:23:54 -0400 Subject: [PATCH] tee stdout and stderr for consumers --- tfexec/apply.go | 13 +------------ tfexec/cmd.go | 23 +++++++++++++++++++++++ tfexec/destroy.go | 13 +------------ tfexec/import.go | 15 ++------------- tfexec/init.go | 15 ++------------- tfexec/internal/e2etest/util_test.go | 6 ++++++ tfexec/output.go | 8 ++------ tfexec/plan.go | 13 +------------ tfexec/providers_schema.go | 13 ++++--------- tfexec/show.go | 13 ++++--------- tfexec/terraform.go | 22 ++++++++++++++++++++++ tfexec/version.go | 7 +++---- 12 files changed, 71 insertions(+), 90 deletions(-) diff --git a/tfexec/apply.go b/tfexec/apply.go index dbfc8953..1250413a 100644 --- a/tfexec/apply.go +++ b/tfexec/apply.go @@ -5,7 +5,6 @@ import ( "fmt" "os/exec" "strconv" - "strings" ) type applyConfig struct { @@ -81,17 +80,7 @@ func (opt *DirOrPlanOption) configureApply(conf *applyConfig) { } func (tf *Terraform) Apply(ctx context.Context, opts ...ApplyOption) error { - applyCmd := tf.applyCmd(ctx, opts...) - - var errBuf strings.Builder - applyCmd.Stderr = &errBuf - - err := applyCmd.Run() - if err != nil { - return parseError(err, errBuf.String()) - } - - return nil + return tf.runTerraformCmd(tf.applyCmd(ctx, opts...)) } func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) *exec.Cmd { diff --git a/tfexec/cmd.go b/tfexec/cmd.go index 3c17c27e..5a3e38e2 100644 --- a/tfexec/cmd.go +++ b/tfexec/cmd.go @@ -2,6 +2,7 @@ package tfexec import ( "context" + "io" "os" "os/exec" "strings" @@ -100,3 +101,25 @@ func (tf *Terraform) buildTerraformCmd(ctx context.Context, args ...string) *exe return cmd } + +func (tf *Terraform) runTerraformCmd(cmd *exec.Cmd) error { + var errBuf strings.Builder + + stdout := tf.stdout + if cmd.Stdout != nil { + stdout = io.MultiWriter(cmd.Stdout, stdout) + } + cmd.Stdout = stdout + + stderr := io.MultiWriter(&errBuf, tf.stderr) + if cmd.Stderr != nil { + stderr = io.MultiWriter(cmd.Stderr, stderr) + } + cmd.Stderr = stderr + + err := cmd.Run() + if err != nil { + return parseError(err, errBuf.String()) + } + return nil +} diff --git a/tfexec/destroy.go b/tfexec/destroy.go index 3e1d44a8..3b9214b5 100644 --- a/tfexec/destroy.go +++ b/tfexec/destroy.go @@ -5,7 +5,6 @@ import ( "fmt" "os/exec" "strconv" - "strings" ) type destroyConfig struct { @@ -77,17 +76,7 @@ func (opt *VarOption) configureDestroy(conf *destroyConfig) { } func (tf *Terraform) Destroy(ctx context.Context, opts ...DestroyOption) error { - destroyCmd := tf.destroyCmd(ctx, opts...) - - var errBuf strings.Builder - destroyCmd.Stderr = &errBuf - - err := destroyCmd.Run() - if err != nil { - return parseError(err, errBuf.String()) - } - - return nil + return tf.runTerraformCmd(tf.destroyCmd(ctx, opts...)) } func (tf *Terraform) destroyCmd(ctx context.Context, opts ...DestroyOption) *exec.Cmd { diff --git a/tfexec/import.go b/tfexec/import.go index cdada1dc..ab0cb3c8 100644 --- a/tfexec/import.go +++ b/tfexec/import.go @@ -4,7 +4,6 @@ import ( "context" "os/exec" "strconv" - "strings" ) type importConfig struct { @@ -67,18 +66,8 @@ func (opt *VarFileOption) configureImport(conf *importConfig) { conf.varFile = opt.path } -func (t *Terraform) Import(ctx context.Context, address, id string, opts ...ImportOption) error { - importCmd := t.importCmd(ctx, address, id, opts...) - - var errBuf strings.Builder - importCmd.Stderr = &errBuf - - err := importCmd.Run() - if err != nil { - return parseError(err, errBuf.String()) - } - - return nil +func (tf *Terraform) Import(ctx context.Context, address, id string, opts ...ImportOption) error { + return tf.runTerraformCmd(tf.importCmd(ctx, address, id, opts...)) } func (tf *Terraform) importCmd(ctx context.Context, address, id string, opts ...ImportOption) *exec.Cmd { diff --git a/tfexec/init.go b/tfexec/init.go index 440da16d..69bc6410 100644 --- a/tfexec/init.go +++ b/tfexec/init.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os/exec" - "strings" ) type initConfig struct { @@ -82,18 +81,8 @@ func (opt *VerifyPluginsOption) configureInit(conf *initConfig) { conf.verifyPlugins = opt.verifyPlugins } -func (t *Terraform) Init(ctx context.Context, opts ...InitOption) error { - initCmd := t.initCmd(ctx, opts...) - - var errBuf strings.Builder - initCmd.Stderr = &errBuf - - err := initCmd.Run() - if err != nil { - return parseError(err, errBuf.String()) - } - - return nil +func (tf *Terraform) Init(ctx context.Context, opts ...InitOption) error { + return tf.runTerraformCmd(tf.initCmd(ctx, opts...)) } func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) *exec.Cmd { diff --git a/tfexec/internal/e2etest/util_test.go b/tfexec/internal/e2etest/util_test.go index b4e09ebf..faf50454 100644 --- a/tfexec/internal/e2etest/util_test.go +++ b/tfexec/internal/e2etest/util_test.go @@ -62,8 +62,14 @@ func runTestVersions(t *testing.T, versions []string, fixtureName string, cb fun } } + var stdouterr bytes.Buffer + tf.SetStdout(&stdouterr) + tf.SetStderr(&stdouterr) + // TODO: capture panics here? cb(t, version.Must(version.NewVersion(tfv)), tf) + + t.Logf("CLI Output:\n%s", stdouterr.String()) }) } } diff --git a/tfexec/output.go b/tfexec/output.go index 3c1a62f8..25ab025b 100644 --- a/tfexec/output.go +++ b/tfexec/output.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "os/exec" - "strings" ) type outputConfig struct { @@ -36,17 +35,14 @@ type OutputMeta struct { func (tf *Terraform) Output(ctx context.Context, opts ...OutputOption) (map[string]OutputMeta, error) { outputCmd := tf.outputCmd(ctx, opts...) - var errBuf strings.Builder var outBuf bytes.Buffer - - outputCmd.Stderr = &errBuf outputCmd.Stdout = &outBuf outputs := map[string]OutputMeta{} - err := outputCmd.Run() + err := tf.runTerraformCmd(outputCmd) if err != nil { - return nil, parseError(err, errBuf.String()) + return nil, err } err = json.Unmarshal(outBuf.Bytes(), &outputs) diff --git a/tfexec/plan.go b/tfexec/plan.go index 6affccfc..29b04ee3 100644 --- a/tfexec/plan.go +++ b/tfexec/plan.go @@ -5,7 +5,6 @@ import ( "fmt" "os/exec" "strconv" - "strings" ) type planConfig struct { @@ -74,17 +73,7 @@ func (opt *DestroyFlagOption) configurePlan(conf *planConfig) { } func (tf *Terraform) Plan(ctx context.Context, opts ...PlanOption) error { - planCmd := tf.planCmd(ctx, opts...) - - var errBuf strings.Builder - planCmd.Stderr = &errBuf - - err := planCmd.Run() - if err != nil { - return parseError(err, errBuf.String()) - } - - return nil + return tf.runTerraformCmd(tf.planCmd(ctx, opts...)) } func (tf *Terraform) planCmd(ctx context.Context, opts ...PlanOption) *exec.Cmd { diff --git a/tfexec/providers_schema.go b/tfexec/providers_schema.go index a1223af3..e0d98f67 100644 --- a/tfexec/providers_schema.go +++ b/tfexec/providers_schema.go @@ -5,25 +5,20 @@ import ( "context" "encoding/json" "os/exec" - "strings" tfjson "github.com/hashicorp/terraform-json" ) func (tf *Terraform) ProvidersSchema(ctx context.Context) (*tfjson.ProviderSchemas, error) { - var ret tfjson.ProviderSchemas - - var errBuf strings.Builder - var outBuf bytes.Buffer - schemaCmd := tf.providersSchemaCmd(ctx) - schemaCmd.Stderr = &errBuf + var ret tfjson.ProviderSchemas + var outBuf bytes.Buffer schemaCmd.Stdout = &outBuf - err := schemaCmd.Run() + err := tf.runTerraformCmd(schemaCmd) if err != nil { - return nil, parseError(err, errBuf.String()) + return nil, err } err = json.Unmarshal(outBuf.Bytes(), &ret) diff --git a/tfexec/show.go b/tfexec/show.go index ce8ffd0d..937e8ccb 100644 --- a/tfexec/show.go +++ b/tfexec/show.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "os/exec" - "strings" tfjson "github.com/hashicorp/terraform-json" ) @@ -17,19 +16,15 @@ func (tf *Terraform) Show(ctx context.Context) (*tfjson.State, error) { return nil, fmt.Errorf("terraform show -json was added in 0.12.0: %w", err) } - var ret tfjson.State - - var errBuf strings.Builder - var outBuf bytes.Buffer - showCmd := tf.showCmd(ctx) - showCmd.Stderr = &errBuf + var ret tfjson.State + var outBuf bytes.Buffer showCmd.Stdout = &outBuf - err = showCmd.Run() + err = tf.runTerraformCmd(showCmd) if err != nil { - return nil, parseError(err, errBuf.String()) + return nil, err } err = json.Unmarshal(outBuf.Bytes(), &ret) diff --git a/tfexec/terraform.go b/tfexec/terraform.go index c888ae93..c74e4e29 100644 --- a/tfexec/terraform.go +++ b/tfexec/terraform.go @@ -2,6 +2,7 @@ package tfexec import ( "fmt" + "io" "io/ioutil" "log" "os" @@ -16,6 +17,8 @@ type Terraform struct { workingDir string env map[string]string + stdout io.Writer + stderr io.Writer logger *log.Logger logPath string @@ -46,6 +49,8 @@ func NewTerraform(workingDir string, execPath string) (*Terraform, error) { workingDir: workingDir, env: nil, // explicit nil means copy os.Environ logger: log.New(ioutil.Discard, "", 0), + stdout: ioutil.Discard, + stderr: ioutil.Discard, } return &tf, nil @@ -71,10 +76,27 @@ func (tf *Terraform) SetEnv(env map[string]string) error { return nil } +// SetLogger specifies a logger for tfexec to use. func (tf *Terraform) SetLogger(logger *log.Logger) { 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 { diff --git a/tfexec/version.go b/tfexec/version.go index 79f11f59..c468c8c1 100644 --- a/tfexec/version.go +++ b/tfexec/version.go @@ -42,14 +42,13 @@ func (tf *Terraform) version(ctx context.Context) (*version.Version, map[string] // for that here and fallback to string parsing versionCmd := tf.buildTerraformCmd(ctx, "version") - var errBuf strings.Builder + var outBuf bytes.Buffer - versionCmd.Stderr = &errBuf versionCmd.Stdout = &outBuf - err := versionCmd.Run() + err := tf.runTerraformCmd(versionCmd) if err != nil { - return nil, nil, parseError(err, errBuf.String()) + return nil, nil, err } tfVersion, providerVersions, err := parseVersionOutput(outBuf.String())