mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-31 02:34:07 +09:00
policy/v2: keep failing tests apart per entry
A refusal can then name each failing test's src.
This commit is contained in:
1 parent
00f597aa6d
commit
a336e1e7e6
2 files changed
+65
-8
No files matched your search
@@ -92,6 +92,21 @@ func (r SSHPolicyTestResults) Errors() string {
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// failures is the breakdown apart per failing entry, so an API answer can
|
||||
// name each entry's src.
|
||||
func (r SSHPolicyTestResults) failures() []TestFailure {
|
||||
var out []TestFailure
|
||||
|
||||
for _, res := range r.Results {
|
||||
if !res.Passed {
|
||||
one := SSHPolicyTestResults{Results: []SSHPolicyTestResult{res}}
|
||||
out = append(out, TestFailure{Src: res.Src, Errors: strings.Split(one.Errors(), "\n")})
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func sortedUsers(m map[string][]string) []string {
|
||||
return slices.Sorted(maps.Keys(m))
|
||||
}
|
||||
@@ -128,7 +143,7 @@ func (pm *PolicyManager) RunSSHTests() error {
|
||||
cache := make(map[types.NodeID]*tailcfg.SSHPolicy)
|
||||
results := runSSHPolicyTests(pm.pol, pm.users, pm.nodes, cache)
|
||||
|
||||
return wrapTestResult(errSSHPolicyTestsFailed, results.AllPassed, results.Errors)
|
||||
return wrapTestResult(errSSHPolicyTestsFailed, results.AllPassed, results)
|
||||
}
|
||||
|
||||
// evaluateSSHTests runs the block against pol without mutating live state.
|
||||
@@ -144,7 +159,7 @@ func evaluateSSHTests(
|
||||
cache := make(map[types.NodeID]*tailcfg.SSHPolicy)
|
||||
results := runSSHPolicyTests(pol, users, nodes, cache)
|
||||
|
||||
return wrapTestResult(errSSHPolicyTestsFailed, results.AllPassed, results.Errors)
|
||||
return wrapTestResult(errSSHPolicyTestsFailed, results.AllPassed, results)
|
||||
}
|
||||
|
||||
// runSSHPolicyTests evaluates every sshTests entry. The cache is keyed
|
||||
|
||||
@@ -196,15 +196,57 @@ func (r PolicyTestResults) Errors() string {
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// TestFailure is one failing tests or sshTests entry: its src as written and
|
||||
// its lines of the failure breakdown.
|
||||
type TestFailure struct {
|
||||
Src string
|
||||
Errors []string
|
||||
}
|
||||
|
||||
// TestsFailedError is a failed tests or sshTests run. It reads as the whole
|
||||
// breakdown; Failures holds it per entry.
|
||||
type TestsFailedError struct {
|
||||
err error
|
||||
Failures []TestFailure
|
||||
}
|
||||
|
||||
func (e *TestsFailedError) Error() string { return e.err.Error() }
|
||||
|
||||
func (e *TestsFailedError) Unwrap() error { return e.err }
|
||||
|
||||
// failures is the breakdown apart per failing entry, so an API answer can
|
||||
// name each entry's src.
|
||||
func (r PolicyTestResults) failures() []TestFailure {
|
||||
var out []TestFailure
|
||||
|
||||
for _, res := range r.Results {
|
||||
if !res.Passed {
|
||||
one := PolicyTestResults{Results: []PolicyTestResult{res}}
|
||||
out = append(out, TestFailure{Src: res.Src, Errors: strings.Split(one.Errors(), "\n")})
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// testReport is the outcome of a tests or sshTests run.
|
||||
type testReport interface {
|
||||
Errors() string
|
||||
failures() []TestFailure
|
||||
}
|
||||
|
||||
// wrapTestResult returns nil when every test passed, otherwise wraps the
|
||||
// rendered failure breakdown in sentinel. errs is passed uncalled so it is
|
||||
// only evaluated on the failure path.
|
||||
func wrapTestResult(sentinel error, allPassed bool, errs func() string) error {
|
||||
// rendered failure breakdown in sentinel. The report is rendered only on the
|
||||
// failure path.
|
||||
func wrapTestResult(sentinel error, allPassed bool, r testReport) error {
|
||||
if allPassed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w:\n%s", sentinel, errs())
|
||||
return &TestsFailedError{
|
||||
err: fmt.Errorf("%w:\n%s", sentinel, r.Errors()),
|
||||
Failures: r.failures(),
|
||||
}
|
||||
}
|
||||
|
||||
// RunTests evaluates the policy's own `tests` block against the live compiled
|
||||
@@ -220,7 +262,7 @@ func (pm *PolicyManager) RunTests() error {
|
||||
|
||||
results := runPolicyTests(pm.pol, pm.filter, pm.users, pm.nodes)
|
||||
|
||||
return wrapTestResult(errPolicyTestsFailed, results.AllPassed, results.Errors)
|
||||
return wrapTestResult(errPolicyTestsFailed, results.AllPassed, results)
|
||||
}
|
||||
|
||||
// evaluateTests runs the `tests` block against a fresh compilation of pol.
|
||||
@@ -242,7 +284,7 @@ func evaluateTests(pol *Policy, users []types.User, nodes views.Slice[types.Node
|
||||
|
||||
results := runPolicyTests(pol, filter, users, nodes)
|
||||
|
||||
return wrapTestResult(errPolicyTestsFailed, results.AllPassed, results.Errors)
|
||||
return wrapTestResult(errPolicyTestsFailed, results.AllPassed, results)
|
||||
}
|
||||
|
||||
// runPolicyTests is the pure evaluation function: given a policy, the
|
||||
|
||||
Reference in new issue
Block a user