Skip to content

Commit 6767e13

Browse files
authored
Feat/add alphabetical order check (#1042)
* feat(sorted-keys): add new template to check for alphabetically sorted YAML keys Signed-off-by: Ayush Kumar <[email protected]> * feat(sorted-keys): implement sorted keys check with test cases for YAML validation * feat(sorted-keys): update YAML key sorting description and add new test cases for various Kubernetes resources * feat(params): refactor parameter handling and validation logic for recursive checks * docs: add generated documentation for sorted-keys check * feat(sorted-keys): add test case for sorted keys validation in YAML files * feat(sorted-keys): refactor and expand test cases for sorted keys validation in YAML files * feat(sorted-keys): add various test cases for sorted keys validation in YAML files * feat(sorted-keys): update YAML parsing to use goccy/go-yaml and improve key extraction logic * fix: add missing newline at end of file in getKeyString function * feat: add additional ignore path for linting in flag-ignore-paths test --------- Signed-off-by: Ayush Kumar <[email protected]>
1 parent 28b9ac8 commit 6767e13

30 files changed

+1436
-5
lines changed

docs/generated/checks.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,21 @@ dirs:
613613
- ^/sys$
614614
- ^/usr$
615615
```
616+
## sorted-keys
617+
618+
**Enabled by default**: No
619+
620+
**Description**: Check that YAML keys are sorted in alphabetical order wherever possible.
621+
622+
**Remediation**: Ensure that keys in your YAML manifest are sorted in alphabetical order to improve consistency and readability.
623+
624+
**Template**: [sorted-keys](templates.md#sorted-keys)
625+
626+
**Parameters**:
627+
628+
```yaml
629+
recursive: true
630+
```
616631
## ssh-port
617632
618633
**Enabled by default**: Yes

docs/generated/templates.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,25 @@ KubeLinter supports the following templates:
820820
type: string
821821
```
822822

823+
## Sorted Keys
824+
825+
**Key**: `sorted-keys`
826+
827+
**Description**: Flag YAML keys that are not sorted in alphabetical order
828+
829+
**Supported Objects**: Any
830+
831+
832+
**Parameters**:
833+
834+
```yaml
835+
- description: Recursive determines whether to check keys recursively at all nesting
836+
levels. Default is true.
837+
name: recursive
838+
required: false
839+
type: boolean
840+
```
841+
823842
## Startup Port Exposed
824843

825844
**Key**: `startup-port`

e2etests/bats-tests.sh

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,25 @@ get_value_from() {
926926
[[ "${count}" == "2" ]]
927927
}
928928

929+
@test "sorted-keys" {
930+
tmp="tests/checks/sorted-keys.yaml"
931+
cmd="${KUBE_LINTER_BIN} lint --include sorted-keys --do-not-auto-add-defaults --format json ${tmp}"
932+
run ${cmd}
933+
934+
print_info "${status}" "${output}" "${cmd}" "${tmp}"
935+
[ "$status" -eq 1 ]
936+
937+
message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message')
938+
message2=$(get_value_from "${lines[0]}" '.Reports[1].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[1].Diagnostic.Message')
939+
message3=$(get_value_from "${lines[0]}" '.Reports[2].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[2].Diagnostic.Message')
940+
count=$(get_value_from "${lines[0]}" '.Reports | length')
941+
942+
[[ "${message1}" == "Deployment: Keys are not sorted at spec.template.spec.containers[0]. Expected order: [image, name, ports], got: [name, image, ports]" ]]
943+
[[ "${message2}" == "Deployment: Keys are not sorted at root. Expected order: [apiVersion, kind, metadata, spec], got: [apiVersion, metadata, spec, kind]" ]]
944+
[[ "${message3}" == "Deployment: Keys are not sorted at spec.template. Expected order: [metadata, spec], got: [spec, metadata]" ]]
945+
[[ "${count}" == "27" ]]
946+
}
947+
929948
@test "ssh-port" {
930949
tmp="tests/checks/ssh-port.yml"
931950
cmd="${KUBE_LINTER_BIN} lint --include ssh-port --do-not-auto-add-defaults --format json ${tmp}"
@@ -1100,7 +1119,7 @@ get_value_from() {
11001119

11011120
@test "flag-ignore-paths" {
11021121
tmp="."
1103-
cmd="${KUBE_LINTER_BIN} lint --ignore-paths \"tests/**\" --ignore-paths \"e2etests/**\" ${tmp}"
1122+
cmd="${KUBE_LINTER_BIN} lint --ignore-paths \"tests/**\" --ignore-paths \"e2etests/**\" --ignore-paths \"pkg/**/testdata/**\" ${tmp}"
11041123
run ${cmd}
11051124
print_info "${status}" "${output}" "${cmd}" "${tmp}"
11061125
[ "$status" -eq 0 ]

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/cert-manager/cert-manager v1.19.1
99
github.com/fatih/color v1.18.0
1010
github.com/go-viper/mapstructure/v2 v2.4.0
11+
github.com/goccy/go-yaml v1.18.0
1112
github.com/google/cel-go v0.26.1
1213
github.com/mitchellh/go-homedir v1.1.0
1314
github.com/mitchellh/mapstructure v1.5.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
111111
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
112112
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
113113
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
114+
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
115+
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
114116
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
115117
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
116118
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: "sorted-keys"
2+
description: "Check that YAML keys are sorted in alphabetical order wherever possible."
3+
remediation: "Ensure that keys in your YAML manifest are sorted in alphabetical order to improve consistency and readability."
4+
scope:
5+
objectKinds:
6+
- Any
7+
template: "sorted-keys"
8+
params:
9+
recursive: true

pkg/lintcontext/mocks/context.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@ import (
77

88
// MockLintContext is mock implementation of the LintContext used in unit tests
99
type MockLintContext struct {
10-
objects map[string]k8sutil.Object
10+
objects map[string]k8sutil.Object
11+
rawObjects map[string][]byte
1112
}
1213

1314
// Objects returns all the objects under this MockLintContext
1415
func (l *MockLintContext) Objects() []lintcontext.Object {
1516
result := make([]lintcontext.Object, 0, len(l.objects))
16-
for _, p := range l.objects {
17-
result = append(result, lintcontext.Object{Metadata: lintcontext.ObjectMetadata{}, K8sObject: p})
17+
for key, p := range l.objects {
18+
metadata := lintcontext.ObjectMetadata{}
19+
if raw, ok := l.rawObjects[key]; ok {
20+
metadata.Raw = raw
21+
}
22+
result = append(result, lintcontext.Object{Metadata: metadata, K8sObject: p})
1823
}
1924
return result
2025
}
@@ -26,10 +31,19 @@ func (l *MockLintContext) InvalidObjects() []lintcontext.InvalidObject {
2631

2732
// NewMockContext returns an empty mockLintContext
2833
func NewMockContext() *MockLintContext {
29-
return &MockLintContext{objects: make(map[string]k8sutil.Object)}
34+
return &MockLintContext{
35+
objects: make(map[string]k8sutil.Object),
36+
rawObjects: make(map[string][]byte),
37+
}
3038
}
3139

3240
// AddObject adds an object to the MockLintContext
3341
func (l *MockLintContext) AddObject(key string, obj k8sutil.Object) {
3442
l.objects[key] = obj
3543
}
44+
45+
// AddObjectWithRaw adds an object to the MockLintContext with raw YAML data
46+
func (l *MockLintContext) AddObjectWithRaw(key string, obj k8sutil.Object, raw []byte) {
47+
l.objects[key] = obj
48+
l.rawObjects[key] = raw
49+
}

pkg/templates/all/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import (
5656
_ "golang.stackrox.io/kube-linter/pkg/templates/sccdenypriv"
5757
_ "golang.stackrox.io/kube-linter/pkg/templates/serviceaccount"
5858
_ "golang.stackrox.io/kube-linter/pkg/templates/servicetype"
59+
_ "golang.stackrox.io/kube-linter/pkg/templates/sortedkeys"
5960
_ "golang.stackrox.io/kube-linter/pkg/templates/startupport"
6061
_ "golang.stackrox.io/kube-linter/pkg/templates/sysctl"
6162
_ "golang.stackrox.io/kube-linter/pkg/templates/targetport"

pkg/templates/sortedkeys/internal/params/gen-params.go

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package params
2+
3+
// Params represents the params accepted by this template.
4+
type Params struct {
5+
// Recursive determines whether to check keys recursively at all nesting levels.
6+
// Default is true.
7+
Recursive bool
8+
}

0 commit comments

Comments
 (0)