Files
2026-07-20 04:55:20 +00:00

87 lines
3.0 KiB
Go

package workflowpolicy
import (
"strings"
"gopkg.in/yaml.v3"
)
func (c *checker) checkRequiredAggregator(jobs *yaml.Node) {
aggregatorName := map[Repository]string{
RepositoryAgent: "agent-quality-required", RepositoryNezha: "nezha-quality-required",
}[c.repository]
if aggregatorName == "" {
return
}
_, hasTestsJob := mappingValue(jobs, "tests")
_, hasQualityJob := mappingValue(jobs, "linux-race-quality")
if !hasTestsJob || !hasQualityJob {
return
}
requiredJobs := []string{"tests", "linux-race-quality", "agentcompat-stress"}
aggregator, hasAggregator := mappingValue(jobs, aggregatorName)
if !hasAggregator || aggregator.Kind != yaml.MappingNode {
c.reject(RuleWorkflowStructure, at("$.jobs."+aggregatorName, jobs), "required quality aggregator is missing")
return
}
needs, hasNeeds := mappingValue(aggregator, "needs")
if !hasNeeds || !hasExactRequiredNeeds(needs, requiredJobs) {
c.reject(RuleWorkflowStructure, at("$.jobs."+aggregatorName+".needs", aggregator), "required quality aggregator needs must include every blocking job exactly once")
}
condition, hasCondition := mappingValue(aggregator, "if")
if !hasCondition || !isAlwaysCondition(condition) {
c.reject(RuleWorkflowStructure, at("$.jobs."+aggregatorName+".if", aggregator), "required quality aggregator must use if: always()")
}
steps, hasSteps := mappingValue(aggregator, "steps")
if !hasSteps || !hasRequiredSuccessChecks(steps, requiredJobs) {
c.reject(RuleWorkflowStructure, at("$.jobs."+aggregatorName+".steps", aggregator), "required quality aggregator must test every blocking job result for success")
}
}
func hasExactRequiredNeeds(node *yaml.Node, requiredJobs []string) bool {
if node == nil || node.Kind != yaml.SequenceNode || len(node.Content) != len(requiredJobs) {
return false
}
required := make(map[string]struct{}, len(requiredJobs))
for _, jobName := range requiredJobs {
required[jobName] = struct{}{}
}
for _, valueNode := range node.Content {
value, literal := scalarString(valueNode)
if !literal {
return false
}
delete(required, value)
}
return len(required) == 0
}
func isAlwaysCondition(node *yaml.Node) bool {
condition, literal := scalarString(node)
condition = strings.TrimSpace(condition)
return literal && (condition == "always()" || condition == "${{ always() }}")
}
func hasRequiredSuccessChecks(steps *yaml.Node, requiredJobs []string) bool {
if steps == nil || steps.Kind != yaml.SequenceNode || len(steps.Content) != 1 {
return false
}
run, hasRun := mappingValue(steps.Content[0], "run")
command, literal := scalarString(run)
if !hasRun || !literal {
return false
}
lines := strings.Split(strings.TrimSpace(command), "\n")
if len(lines) != len(requiredJobs) {
return false
}
requiredChecks := make(map[string]struct{}, len(requiredJobs))
for _, jobName := range requiredJobs {
requiredChecks[`test "${{ needs.`+jobName+`.result }}" = success`] = struct{}{}
}
for _, line := range lines {
delete(requiredChecks, strings.TrimSpace(line))
}
return len(requiredChecks) == 0
}