Skip to content

Commit

Permalink
policy: parse explanations in match blocks (#948)
Browse files Browse the repository at this point in the history
This adds a secondary output to every block, an "explanation" of why a value was
returned. This can be structured or non-structured text.
For now the parser is simply storing the value, but the compiler cannot process
it yet.
  • Loading branch information
seirl authored May 30, 2024
1 parent 8289a0c commit fbf5c77
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 3 deletions.
33 changes: 30 additions & 3 deletions policy/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,10 @@ func NewMatch() *Match {
// Match declares a condition (defaults to true) as well as an output or a rule.
// Either the output or the rule field may be set, but not both.
type Match struct {
condition ValueString
output *ValueString
rule *Rule
condition ValueString
output *ValueString
explanation *ValueString
rule *Rule
}

// Condition returns the condition CEL expression.
Expand All @@ -227,6 +228,19 @@ func (m *Match) Output() ValueString {
return ValueString{}
}

// HasExplanation indicates whether the explanation field is set of the match.
func (m *Match) HasExplanation() bool {
return m.explanation != nil
}

// Explanation returns the explanation expression, or empty expression if output is not set.
func (m *Match) Explanation() ValueString {
if m.HasExplanation() {
return *m.explanation
}
return ValueString{}
}

// HasRule indicates whether the rule field is set on a match.
func (m *Match) HasRule() bool {
return m.rule != nil
Expand All @@ -247,6 +261,11 @@ func (m *Match) SetOutput(o ValueString) {
m.output = &o
}

// SetExplanation sets the explanation expression for the match.
func (m *Match) SetExplanation(e ValueString) {
m.explanation = &e
}

// SetRule sets the rule for the match.
func (m *Match) SetRule(r *Rule) {
m.rule = r
Expand Down Expand Up @@ -633,10 +652,18 @@ func (p *parserImpl) ParseMatch(ctx ParserContext, policy *Policy, node *yaml.No
p.ReportErrorAtID(keyID, "only the rule or the output may be set")
}
m.SetOutput(ctx.NewString(val))
case "explanation":
if m.HasRule() {
p.ReportErrorAtID(keyID, "explanation can only be set on output match cases, not nested rules")
}
m.SetExplanation(ctx.NewString(val))
case "rule":
if m.HasOutput() {
p.ReportErrorAtID(keyID, "only the rule or the output may be set")
}
if m.HasExplanation() {
p.ReportErrorAtID(keyID, "explanation can only be set on output match cases, not nested rules")
}
m.SetRule(p.ParseRule(ctx, policy, val))
default:
p.visitor.MatchTag(ctx, keyID, fieldName, val, policy, m)
Expand Down
26 changes: 26 additions & 0 deletions policy/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,32 @@ rule:
output: "world"`,
err: `ERROR: <input>:8:7: only the rule or the output may be set
| output: "world"
| ......^`,
},
{
txt: `
rule:
match:
- condition: "true"
explanation: "hi"
rule:
match:
- output: "hello"`,
err: `ERROR: <input>:6:7: explanation can only be set on output match cases, not nested rules
| rule:
| ......^`,
},
{
txt: `
rule:
match:
- condition: "true"
rule:
match:
- output: "hello"
explanation: "hi"`,
err: `ERROR: <input>:8:7: explanation can only be set on output match cases, not nested rules
| explanation: "hi"
| ......^`,
},
}
Expand Down
1 change: 1 addition & 0 deletions policy/testdata/nested_rule/policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ rule:
resource.origin in variables.banned_regions &&
!(resource.origin in variables.permitted_regions)
output: "{'banned': true}"
explanation: "'resource is in the banned region ' + resource.origin"
- condition: resource.origin in variables.permitted_regions
output: "{'banned': false}"
- output: "{'banned': true}"

0 comments on commit fbf5c77

Please sign in to comment.