diff --git a/backend/remote/backend_apply.go b/backend/remote/backend_apply.go index c17d413f9e..657641a5f2 100644 --- a/backend/remote/backend_apply.go +++ b/backend/remote/backend_apply.go @@ -7,6 +7,7 @@ import ( "fmt" "log" "strings" + "time" tfe "github.com/hashicorp/go-tfe" "github.com/hashicorp/terraform/backend" @@ -146,21 +147,30 @@ func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operati return r, nil } -func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error { +func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Operation, r *tfe.Run) (err error) { if b.CLI != nil { b.CLI.Output("\n------------------------------------------------------------------------\n") } for _, pc := range r.PolicyChecks { - logs, err := b.client.PolicyChecks.Logs(stopCtx, pc.ID) - if err != nil { - return generalError("error retrieving policy check logs", err) - } - scanner := bufio.NewScanner(logs) + // Loop until the context is canceled or the policy check is finished. + for { + pc, err = b.client.PolicyChecks.Read(stopCtx, pc.ID) + if err != nil { + return generalError("error retrieving policy check", err) + } - // Retrieve the policy check to get its current status. - pc, err := b.client.PolicyChecks.Read(stopCtx, pc.ID) - if err != nil { - return generalError("error retrieving policy check", err) + switch pc.Status { + case tfe.PolicyPending, tfe.PolicyQueued: + select { + case <-stopCtx.Done(): + return generalError("error retrieving policy check", stopCtx.Err()) + case <-time.After(500 * time.Millisecond): + continue + } + } + + // Break if the policy check is finished. + break } var msgPrefix string @@ -173,10 +183,25 @@ func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Ope msgPrefix = fmt.Sprintf("Unknown policy check (%s)", pc.Scope) } + // Don't show the full policy output if the policy passed. + if pc.Status == tfe.PolicyPasses { + if b.CLI != nil { + b.CLI.Output(b.Colorize().Color(msgPrefix + ": passed\n")) + b.CLI.Output("------------------------------------------------------------------------") + } + continue + } + if b.CLI != nil { b.CLI.Output(b.Colorize().Color(msgPrefix + ":\n")) } + logs, err := b.client.PolicyChecks.Logs(stopCtx, pc.ID) + if err != nil { + return generalError("error retrieving policy check logs", err) + } + scanner := bufio.NewScanner(logs) + for scanner.Scan() { if b.CLI != nil { b.CLI.Output(b.Colorize().Color(scanner.Text())) @@ -187,11 +212,6 @@ func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Ope } switch pc.Status { - case tfe.PolicyPasses: - if b.CLI != nil { - b.CLI.Output("\n------------------------------------------------------------------------") - } - continue case tfe.PolicyErrored: return fmt.Errorf(msgPrefix + " errored.") case tfe.PolicyHardFailed: @@ -215,13 +235,13 @@ func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Ope return err } - if b.CLI != nil { - b.CLI.Output("------------------------------------------------------------------------") - } - if _, err = b.client.PolicyChecks.Override(stopCtx, pc.ID); err != nil { return generalError("error overriding policy check", err) } + + if b.CLI != nil { + b.CLI.Output("------------------------------------------------------------------------") + } } return nil diff --git a/backend/remote/backend_apply_test.go b/backend/remote/backend_apply_test.go index 27b8b901ed..460b2124db 100644 --- a/backend/remote/backend_apply_test.go +++ b/backend/remote/backend_apply_test.go @@ -442,8 +442,8 @@ func TestRemote_applyPolicyPass(t *testing.T) { if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { t.Fatalf("missing plan summery in output: %s", output) } - if !strings.Contains(output, "Sentinel Result: true") { - t.Fatalf("missing Sentinel result in output: %s", output) + if !strings.Contains(output, "policy check: passed") { + t.Fatalf("missing polic check result in output: %s", output) } if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") { t.Fatalf("missing apply summery in output: %s", output) @@ -487,7 +487,7 @@ func TestRemote_applyPolicyHardFail(t *testing.T) { t.Fatalf("missing plan summery in output: %s", output) } if !strings.Contains(output, "Sentinel Result: false") { - t.Fatalf("missing Sentinel result in output: %s", output) + t.Fatalf("missing policy check result in output: %s", output) } if strings.Contains(output, "1 added, 0 changed, 0 destroyed") { t.Fatalf("unexpected apply summery in output: %s", output) @@ -530,7 +530,7 @@ func TestRemote_applyPolicySoftFail(t *testing.T) { t.Fatalf("missing plan summery in output: %s", output) } if !strings.Contains(output, "Sentinel Result: false") { - t.Fatalf("missing Sentinel result in output: %s", output) + t.Fatalf("missing policy check result in output: %s", output) } if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") { t.Fatalf("missing apply summery in output: %s", output) @@ -573,7 +573,7 @@ func TestRemote_applyPolicySoftFailAutoApprove(t *testing.T) { t.Fatalf("missing plan summery in output: %s", output) } if !strings.Contains(output, "Sentinel Result: false") { - t.Fatalf("missing Sentinel result in output: %s", output) + t.Fatalf("missing policy check result in output: %s", output) } if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") { t.Fatalf("missing apply summery in output: %s", output) diff --git a/backend/remote/backend_mock.go b/backend/remote/backend_mock.go index cda8ffa9ab..d30e7eed16 100644 --- a/backend/remote/backend_mock.go +++ b/backend/remote/backend_mock.go @@ -471,23 +471,6 @@ func (m *mockPolicyChecks) Read(ctx context.Context, policyCheckID string) (*tfe if !ok { return nil, tfe.ErrResourceNotFound } - return pc, nil -} - -func (m *mockPolicyChecks) Override(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { - pc, ok := m.checks[policyCheckID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - pc.Status = tfe.PolicyOverridden - return pc, nil -} - -func (m *mockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) { - pc, ok := m.checks[policyCheckID] - if !ok { - return nil, tfe.ErrResourceNotFound - } logfile, ok := m.logs[pc.ID] if !ok { @@ -495,7 +478,7 @@ func (m *mockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.R } if _, err := os.Stat(logfile); os.IsNotExist(err) { - return bytes.NewBufferString("logfile does not exist"), nil + return nil, fmt.Errorf("logfile does not exist") } logs, err := ioutil.ReadFile(logfile) @@ -520,6 +503,38 @@ func (m *mockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.R pc.Status = tfe.PolicyErrored } + return pc, nil +} + +func (m *mockPolicyChecks) Override(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { + pc, ok := m.checks[policyCheckID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + pc.Status = tfe.PolicyOverridden + return pc, nil +} + +func (m *mockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) { + pc, ok := m.checks[policyCheckID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + + logfile, ok := m.logs[pc.ID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + + if _, err := os.Stat(logfile); os.IsNotExist(err) { + return nil, fmt.Errorf("logfile does not exist") + } + + logs, err := ioutil.ReadFile(logfile) + if err != nil { + return nil, err + } + return bytes.NewBuffer(logs), nil } diff --git a/backend/remote/test-fixtures/apply-policy-passed/policy.log b/backend/remote/test-fixtures/apply-policy-passed/policy.log index b0cb1e5985..eb4527a79a 100644 --- a/backend/remote/test-fixtures/apply-policy-passed/policy.log +++ b/backend/remote/test-fixtures/apply-policy-passed/policy.log @@ -1,12 +1,2 @@ +# This line is here only for the mock! Sentinel Result: true - -This result means that Sentinel policies returned true and the protected -behavior is allowed by Sentinel policies. - -1 policies evaluated. - -## Policy 1: Passthrough.sentinel (soft-mandatory) - -Result: true - -TRUE - Passthrough.sentinel:1:1 - Rule "main"