forked from manetu/policyengine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathselector.go
More file actions
117 lines (103 loc) · 2.98 KB
/
selector.go
File metadata and controls
117 lines (103 loc) · 2.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//
// Copyright © Manetu Inc. All rights reserved.
//
package lint
import (
"fmt"
"regexp"
"strings"
"gopkg.in/yaml.v3"
)
// lintSelectors validates selector regex patterns on operations, mappers, and
// resources within a raw PolicyDomain YAML document.
//
// It walks the YAML node tree to find selector sequences and attempts to compile
// each pattern, emitting a structured Diagnostic for any invalid regex. This
// phase runs on the raw bytes before LoadFromBytes so that entity-aware
// diagnostics (with entity type, ID, and YAML line number) are produced even
// when the full parse would fail due to the bad pattern.
func lintSelectors(data []byte, key string) []Diagnostic {
var root yaml.Node
if err := yaml.Unmarshal(data, &root); err != nil {
// YAML syntax errors are reported by lintYAML; nothing to do here.
return nil
}
if root.Kind != yaml.DocumentNode || len(root.Content) == 0 {
return nil
}
doc := root.Content[0]
domainName := ""
if meta := findMappingValue(doc, "metadata"); meta != nil {
domainName = findScalarValue(meta, "name")
}
spec := findMappingValue(doc, "spec")
if spec == nil || spec.Kind != yaml.MappingNode {
return nil
}
var diagnostics []Diagnostic
for _, section := range []struct {
key string
entityType string
}{
{"operations", "operation"},
{"mappers", "mapper"},
{"resources", "resource"},
} {
sectionNode := findMappingValue(spec, section.key)
if sectionNode == nil || sectionNode.Kind != yaml.SequenceNode {
continue
}
for i, item := range sectionNode.Content {
if item.Kind != yaml.MappingNode {
continue
}
entityID := findScalarValue(item, "name")
if entityID == "" {
entityID = findScalarValue(item, "mrn")
}
if entityID == "" {
entityID = fmt.Sprintf("%s[%d]", section.entityType, i)
}
selectorNode := findMappingValue(item, "selector")
if selectorNode == nil || selectorNode.Kind != yaml.SequenceNode {
continue
}
for _, sel := range selectorNode.Content {
if sel.Kind != yaml.ScalarNode {
continue
}
pattern := selectorAnchorPattern(sel.Value)
if _, err := regexp.Compile(pattern); err != nil {
diagnostics = append(diagnostics, Diagnostic{
Source: SourceSelector,
Severity: SeverityError,
Location: Location{
File: key,
Start: Position{Line: sel.Line, Column: sel.Column},
},
Entity: Entity{
Domain: domainName,
Type: section.entityType,
ID: entityID,
Field: "selector",
},
Message: fmt.Sprintf("invalid selector regex %q: %s", sel.Value, err.Error()),
})
}
}
}
}
return diagnostics
}
// selectorAnchorPattern ensures the pattern is anchored with ^ and $,
// matching the behaviour of the v1alpha3/v1alpha4/v1beta1 parsers.
func selectorAnchorPattern(pattern string) string {
result := pattern
if !strings.HasPrefix(result, "^") {
result = "^" + result
}
if !strings.HasSuffix(result, "$") {
result = result + "$"
}
return result
}