Update the Terraform CLI output to show logical separation between OPA and Sentinel policy evaluations (#34963)

* add sentinel policy evaluation title in the CLI results

* Add Sentinel policy evaluations to the CLI

* add changelog

* minor design fixes
TF-13959
Mrinali Rao 2 years ago committed by GitHub
parent 3fdfbd6944
commit b1a40a1a65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -5,6 +5,7 @@ ENHANCEMENTS:
* `terraform console`: Now has basic support for multi-line input in interactive mode. ([#34822](https://github.com/hashicorp/terraform/pull/34822))
If an entered line contains opening paretheses/etc that are not closed, Terraform will await another line of input to complete the expression. This initial implementation is primarily intended to support pasting in multi-line expressions from elsewhere, rather than for manual multi-line editing, so the interactive editing support is currently limited.
* `cli`: Updates the Terraform CLI output to show logical separation between OPA and Sentinel policy evaluations
BUG FIXES:

@ -45,7 +45,8 @@ func newPolicyEvaluationSummarizer(b *Cloud, ts *tfe.TaskStage) taskStageSummari
func (pes *policyEvaluationSummarizer) Summarize(context *IntegrationContext, output IntegrationOutputWriter, ts *tfe.TaskStage) (bool, *string, error) {
if pes.counter == 0 {
output.Output("[bold]OPA Policy Evaluation\n")
output.Output("------------------------------------------------------------------------\n")
output.Output("[bold]Policy Evaluations\n")
pes.counter++
}
@ -103,20 +104,27 @@ func summarizePolicyEvaluationResults(policyEvaluations []*tfe.PolicyEvaluation)
}
func (pes *policyEvaluationSummarizer) taskStageWithPolicyEvaluation(context *IntegrationContext, output IntegrationOutputWriter, policyEvaluation []*tfe.PolicyEvaluation) error {
var result, message string
var result, message, kind string
// Currently only one policy evaluation supported : OPA
for _, polEvaluation := range policyEvaluation {
if polEvaluation.PolicyKind == "opa" {
kind = "OPA"
} else {
kind = "Sentinel"
}
if polEvaluation.Status == tfe.PolicyEvaluationPassed {
message = "[dim] This result means that all OPA policies passed and the protected behavior is allowed"
message = fmt.Sprintf("[dim] This result means that all %s policies passed and the protected behavior is allowed", kind)
result = fmt.Sprintf("[green]%s", strings.ToUpper(string(tfe.PolicyEvaluationPassed)))
if polEvaluation.ResultCount.AdvisoryFailed > 0 {
result += " (with advisory)"
}
} else {
message = "[dim] This result means that one or more OPA policies failed. More than likely, this was due to the discovery of violations by the main rule and other sub rules"
message = fmt.Sprintf("[dim] This result means that one or more %s policies failed. More than likely, this was due to the discovery of violations by the main rule and other sub rules", kind)
result = fmt.Sprintf("[red]%s", strings.ToUpper(string(tfe.PolicyEvaluationFailed)))
}
output.Output("--------------------------------\n")
output.Output(fmt.Sprintf("[bold]%s Policy Evaluation\n", kind))
output.Output(fmt.Sprintf("[bold]%c%c Overall Result: %s", Arrow, Arrow, result))
output.Output(message)

@ -10,7 +10,7 @@ import (
"github.com/hashicorp/go-tfe"
)
func TestCloud_runTaskStageWithPolicyEvaluation(t *testing.T) {
func TestCloud_runTaskStageWithOPAPolicyEvaluation(t *testing.T) {
b, bCleanup := testBackendWithName(t)
defer bCleanup()
@ -27,7 +27,7 @@ func TestCloud_runTaskStageWithPolicyEvaluation(t *testing.T) {
taskStage: func() *tfe.TaskStage {
ts := &tfe.TaskStage{}
ts.PolicyEvaluations = []*tfe.PolicyEvaluation{
{ID: "pol-pass", ResultCount: &tfe.PolicyResultCount{Passed: 1}, Status: "passed"},
{ID: "pol-pass", ResultCount: &tfe.PolicyResultCount{Passed: 1}, Status: "passed", PolicyKind: "opa"},
}
return ts
},
@ -40,7 +40,7 @@ func TestCloud_runTaskStageWithPolicyEvaluation(t *testing.T) {
taskStage: func() *tfe.TaskStage {
ts := &tfe.TaskStage{}
ts.PolicyEvaluations = []*tfe.PolicyEvaluation{
{ID: "pol-fail", ResultCount: &tfe.PolicyResultCount{MandatoryFailed: 1}, Status: "failed"},
{ID: "pol-fail", ResultCount: &tfe.PolicyResultCount{MandatoryFailed: 1}, Status: "failed", PolicyKind: "opa"},
}
return ts
},
@ -53,7 +53,7 @@ func TestCloud_runTaskStageWithPolicyEvaluation(t *testing.T) {
taskStage: func() *tfe.TaskStage {
ts := &tfe.TaskStage{}
ts.PolicyEvaluations = []*tfe.PolicyEvaluation{
{ID: "adv-fail", ResultCount: &tfe.PolicyResultCount{AdvisoryFailed: 1}, Status: "failed"},
{ID: "adv-fail", ResultCount: &tfe.PolicyResultCount{AdvisoryFailed: 1}, Status: "failed", PolicyKind: "opa"},
}
return ts
},
@ -66,7 +66,96 @@ func TestCloud_runTaskStageWithPolicyEvaluation(t *testing.T) {
taskStage: func() *tfe.TaskStage {
ts := &tfe.TaskStage{}
ts.PolicyEvaluations = []*tfe.PolicyEvaluation{
{ID: "adv-fail", ResultCount: &tfe.PolicyResultCount{Errored: 1}, Status: "unreachable"},
{ID: "adv-fail", ResultCount: &tfe.PolicyResultCount{Errored: 1}, Status: "unreachable", PolicyKind: "opa"},
}
return ts
},
writer: writer,
context: integrationContext,
expectedOutputs: []string{"Skipping policy evaluation."},
isError: false,
},
}
for _, c := range cases {
c.writer.output.Reset()
trs := policyEvaluationSummarizer{
cloud: b,
}
c.context.Poll(0, 0, func(i int) (bool, error) {
cont, _, _ := trs.Summarize(c.context, c.writer, c.taskStage())
if cont {
return true, nil
}
output := c.writer.output.String()
for _, expected := range c.expectedOutputs {
if !strings.Contains(output, expected) {
t.Fatalf("Expected output to contain '%s' but it was:\n\n%s", expected, output)
}
}
return false, nil
})
}
}
func TestCloud_runTaskStageWithSentinelPolicyEvaluation(t *testing.T) {
b, bCleanup := testBackendWithName(t)
defer bCleanup()
integrationContext, writer := newMockIntegrationContext(b, t)
cases := map[string]struct {
taskStage func() *tfe.TaskStage
context *IntegrationContext
writer *testIntegrationOutput
expectedOutputs []string
isError bool
}{
"all-succeeded": {
taskStage: func() *tfe.TaskStage {
ts := &tfe.TaskStage{}
ts.PolicyEvaluations = []*tfe.PolicyEvaluation{
{ID: "pol-pass", ResultCount: &tfe.PolicyResultCount{Passed: 1}, Status: "passed", PolicyKind: "sentinel"},
}
return ts
},
writer: writer,
context: integrationContext,
expectedOutputs: []string{"│ [bold]Sentinel Policy Evaluation\n\n│ [bold]→→ Overall Result: [green]PASSED\n│ [dim] This result means that all Sentinel policies passed and the protected behavior is allowed\n│ 1 policies evaluated\n\n│ → Policy set 1: [bold]policy-set-that-passes (1)\n│ ↳ Policy name: [bold]policy-pass\n│ | [green][bold]✓ Passed\n│ | [dim]This policy will pass\n"},
isError: false,
},
"mandatory-failed": {
taskStage: func() *tfe.TaskStage {
ts := &tfe.TaskStage{}
ts.PolicyEvaluations = []*tfe.PolicyEvaluation{
{ID: "pol-fail", ResultCount: &tfe.PolicyResultCount{MandatoryFailed: 1}, Status: "failed", PolicyKind: "sentinel"},
}
return ts
},
writer: writer,
context: integrationContext,
expectedOutputs: []string{"│ [bold]→→ Overall Result: [red]FAILED\n│ [dim] This result means that one or more Sentinel policies failed. More than likely, this was due to the discovery of violations by the main rule and other sub rules\n│ 1 policies evaluated\n\n│ → Policy set 1: [bold]policy-set-that-fails (1)\n│ ↳ Policy name: [bold]policy-fail\n│ | [red][bold]× Failed\n│ | [dim]This policy will fail"},
isError: true,
},
"advisory-failed": {
taskStage: func() *tfe.TaskStage {
ts := &tfe.TaskStage{}
ts.PolicyEvaluations = []*tfe.PolicyEvaluation{
{ID: "adv-fail", ResultCount: &tfe.PolicyResultCount{AdvisoryFailed: 1}, Status: "failed", PolicyKind: "sentinel"},
}
return ts
},
writer: writer,
context: integrationContext,
expectedOutputs: []string{"│ [bold]Sentinel Policy Evaluation\n\n│ [bold]→→ Overall Result: [red]FAILED\n│ [dim] This result means that one or more Sentinel policies failed. More than likely, this was due to the discovery of violations by the main rule and other sub rules\n│ 1 policies evaluated\n\n│ → Policy set 1: [bold]policy-set-that-fails (1)\n│ ↳ Policy name: [bold]policy-fail\n│ | [blue][bold]Ⓘ Advisory\n│ | [dim]This policy will fail"},
isError: false,
},
"unreachable": {
taskStage: func() *tfe.TaskStage {
ts := &tfe.TaskStage{}
ts.PolicyEvaluations = []*tfe.PolicyEvaluation{
{ID: "adv-fail", ResultCount: &tfe.PolicyResultCount{Errored: 1}, Status: "unreachable", PolicyKind: "sentinel"},
}
return ts
},

@ -114,6 +114,9 @@ func (b *Cloud) runTaskStage(ctx *IntegrationContext, output IntegrationOutputWr
errs = e
}
if ok {
if b.CLI != nil {
b.CLI.Output("------------------------------------------------------------------------")
}
return true, nil
}
case tfe.TaskStageCanceled, tfe.TaskStageErrored, tfe.TaskStageFailed:
@ -122,6 +125,9 @@ func (b *Cloud) runTaskStage(ctx *IntegrationContext, output IntegrationOutputWr
errs = e
}
if ok {
if b.CLI != nil {
b.CLI.Output("------------------------------------------------------------------------")
}
return true, nil
}
return false, fmt.Errorf("Task Stage %s.", stage.Status)
@ -137,6 +143,9 @@ func (b *Cloud) runTaskStage(ctx *IntegrationContext, output IntegrationOutputWr
if err != nil {
errs = errors.Join(errs, err)
} else {
if b.CLI != nil {
b.CLI.Output("------------------------------------------------------------------------")
}
return cont, nil
}
case tfe.TaskStageUnreachable:
@ -170,9 +179,13 @@ func processSummarizers(ctx *IntegrationContext, output IntegrationOutputWriter,
}
func (b *Cloud) processStageOverrides(context *IntegrationContext, output IntegrationOutputWriter, taskStageID string) (bool, error) {
if b.CLI != nil {
b.CLI.Output("--------------------------------\n")
b.CLI.Output(b.Colorize().Color(fmt.Sprintf("%c%c [bold]Override", Arrow, Arrow)))
}
opts := &terraform.InputOpts{
Id: fmt.Sprintf("%c%c [bold]Override", Arrow, Arrow),
Query: "\nDo you want to override the failed policy check?",
Query: "\nDo you want to override the failed policies?",
Description: "Only 'override' will be accepted to override.",
}
runURL := fmt.Sprintf(taskStageHeader, b.Hostname, b.Organization, context.Op.Workspace, context.Run.ID)

Loading…
Cancel
Save