From 87cde18e91b1b0f9a406345c49ceb250cf51483b Mon Sep 17 00:00:00 2001 From: aksel-skaar-leirvaag Date: Tue, 4 Jun 2024 07:09:49 +0200 Subject: [PATCH 1/2] Print plan before apply --- docs/use-tf-controller/with-tf-runner-logging.md | 5 +++++ runner/server.go | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/use-tf-controller/with-tf-runner-logging.md b/docs/use-tf-controller/with-tf-runner-logging.md index 2670a5d8f..840331279 100644 --- a/docs/use-tf-controller/with-tf-runner-logging.md +++ b/docs/use-tf-controller/with-tf-runner-logging.md @@ -19,3 +19,8 @@ the `DISABLE_TF_LOGS` variable must also be set to "1". For more information on configuring the Terraform Runner and its environment variables, please consult the documentation on [customizing runners](provision-resources-with-customized-runner-pods.md) within the Weave TF-controller. + +## Logging human-readable plan + +The plan can be logged in a human-readable format just before the applying it in the `tf-runner`. +To enable this, set the environment variable `LOG_HUMAN_READABLE_PLAN` to "1" on the runner. diff --git a/runner/server.go b/runner/server.go index a43e9fe10..20a801664 100644 --- a/runner/server.go +++ b/runner/server.go @@ -382,6 +382,21 @@ func (r *TerraformRunnerServer) Apply(ctx context.Context, req *ApplyRequest) (* applyOpt = append(applyOpt, tfexec.Parallelism(int(req.Parallelism))) } + if os.Getenv("LOG_HUMAN_READABLE_PLAN") == "1" { + planName := TFPlanName + if req.DirOrPlan != "" { + planName = req.DirOrPlan + } + + rawOutput, err := r.tfShowPlanFileRaw(ctx, planName) + if err != nil { + log.Error(err, "unable to get the plan output for human") + return nil, err + } + + fmt.Println(rawOutput) + } + if err := r.tf.Apply(ctx, applyOpt...); err != nil { st := status.New(codes.Internal, err.Error()) var stateErr *tfexec.ErrStateLocked From bde16ccbc09f0e19d5bddbe8c04e627c2b563ff9 Mon Sep 17 00:00:00 2001 From: aksel-skaar-leirvaag Date: Thu, 6 Jun 2024 13:11:44 +0200 Subject: [PATCH 2/2] Added unit test --- runner/server.go | 33 ++++++++++++++++----------- runner/server_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 runner/server_test.go diff --git a/runner/server.go b/runner/server.go index 20a801664..b56272541 100644 --- a/runner/server.go +++ b/runner/server.go @@ -382,19 +382,9 @@ func (r *TerraformRunnerServer) Apply(ctx context.Context, req *ApplyRequest) (* applyOpt = append(applyOpt, tfexec.Parallelism(int(req.Parallelism))) } - if os.Getenv("LOG_HUMAN_READABLE_PLAN") == "1" { - planName := TFPlanName - if req.DirOrPlan != "" { - planName = req.DirOrPlan - } - - rawOutput, err := r.tfShowPlanFileRaw(ctx, planName) - if err != nil { - log.Error(err, "unable to get the plan output for human") - return nil, err - } - - fmt.Println(rawOutput) + if err := printHumanReadablePlanIfEnabled(ctx, req.DirOrPlan, r.tfShowPlanFileRaw); err != nil { + log.Error(err, "unable to print plan") + return nil, err } if err := r.tf.Apply(ctx, applyOpt...); err != nil { @@ -416,6 +406,23 @@ func (r *TerraformRunnerServer) Apply(ctx context.Context, req *ApplyRequest) (* return &ApplyReply{Message: "ok"}, nil } +func printHumanReadablePlanIfEnabled(ctx context.Context, planName string, tfShowPlanFileRaw func(ctx context.Context, planPath string, opts ...tfexec.ShowOption) (string, error)) error { + if os.Getenv("LOG_HUMAN_READABLE_PLAN") == "1" { + if planName == "" { + planName = TFPlanName + } + + rawOutput, err := tfShowPlanFileRaw(ctx, planName) + if err != nil { + return err + } + + fmt.Println(rawOutput) + } + + return nil +} + func getInventoryFromTerraformModule(m *tfjson.StateModule) []*Inventory { var result []*Inventory for _, r := range m.Resources { diff --git a/runner/server_test.go b/runner/server_test.go new file mode 100644 index 000000000..8d762955f --- /dev/null +++ b/runner/server_test.go @@ -0,0 +1,52 @@ +package runner + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-exec/tfexec" + . "github.com/onsi/gomega" +) + +func Test_printHumanReadablePlanIfEnabled(t *testing.T) { + g := NewGomegaWithT(t) + ctx := context.Background() + + defer func() { + g.Expect(os.Unsetenv("LOG_HUMAN_READABLE_PLAN")).Should(Succeed()) + }() + + var tfShowPlanFileRawCalled int + expectedPlan := TFPlanName + tfShowPlanFileRaw := func(ctx context.Context, planPath string, opts ...tfexec.ShowOption) (string, error) { + g.Expect(planPath).To(Equal(expectedPlan)) + tfShowPlanFileRawCalled++ + return "", nil + } + + // When plan is enabled, then it should be called once + g.Expect(os.Setenv("LOG_HUMAN_READABLE_PLAN", "1")).Should(Succeed()) + g.Expect(printHumanReadablePlanIfEnabled(ctx, "", tfShowPlanFileRaw)).Should(Succeed()) + g.Expect(tfShowPlanFileRawCalled).To(Equal(1)) + + // When the planName is non-empty, then it should use the planName + expectedPlan = "foo" + g.Expect(printHumanReadablePlanIfEnabled(ctx, expectedPlan, tfShowPlanFileRaw)).Should(Succeed()) + g.Expect(tfShowPlanFileRawCalled).To(Equal(2)) + + // When it is disabled, then it should not be called + expectedPlan = TFPlanName + g.Expect(os.Setenv("LOG_HUMAN_READABLE_PLAN", "0")).Should(Succeed()) + g.Expect(printHumanReadablePlanIfEnabled(ctx, "", tfShowPlanFileRaw)).Should(Succeed()) + g.Expect(tfShowPlanFileRawCalled).To(Equal(2)) + + // When tfShowPlanFileRaw fails, then it should return an error + g.Expect(os.Setenv("LOG_HUMAN_READABLE_PLAN", "1")).Should(Succeed()) + tfShowPlanFileRaw = func(ctx context.Context, planPath string, opts ...tfexec.ShowOption) (string, error) { + return "", fmt.Errorf("error") + } + + g.Expect(printHumanReadablePlanIfEnabled(ctx, "", tfShowPlanFileRaw)).ShouldNot(Succeed()) +}