Skip to content

Commit 13ca337

Browse files
author
Jonathan Henrique Medeiros
committed
feature(lint): Add linter to priorityClassName
1 parent c468360 commit 13ca337

File tree

11 files changed

+440
-0
lines changed

11 files changed

+440
-0
lines changed

.github/workflows/build.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ jobs:
6565
token: ${{ secrets.CODECOV_TOKEN }}
6666
slug: stackrox/kube-linter
6767
flags: unit
68+
gcov_ignore: "**/gen-params.go"
6869

6970
- name: Run E2E tests
7071
run: make e2e-test

docs/generated/checks.md

+17
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,23 @@ strategyTypeRegex: ^(RollingUpdate|Rolling)$
455455
**Remediation**: Set unhealthyPodEvictionPolicy to AlwaysAllow. Refer to https://kubernetes.io/docs/tasks/run-application/configure-pdb/#unhealthy-pod-eviction-policy for more information.
456456
457457
**Template**: [pdb-unhealthy-pod-eviction-policy](templates.md#.spec.unhealthypodevictionpolicy-in-pdb-is-set-to-default)
458+
## priority-class-name
459+
460+
**Enabled by default**: No
461+
462+
**Description**: Indicates when a deployment-like object does not use a valid priority class name
463+
464+
**Remediation**: Set up the priority class name for your object to any accepted values.
465+
466+
**Template**: [priority-class-name](templates.md#priority-class-name)
467+
468+
**Parameters**:
469+
470+
```yaml
471+
acceptedPriorityClassNames:
472+
- system-cluster-critical
473+
- system-node-critical
474+
```
458475
## privilege-escalation-container
459476
460477
**Enabled by default**: Yes

docs/generated/templates.md

+21
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,27 @@ KubeLinter supports the following templates:
597597
type: string
598598
```
599599

600+
## Priority class name
601+
602+
**Key**: `priority-class-name`
603+
604+
**Description**: Flag applications running with invalid priority class name.
605+
606+
**Supported Objects**: DeploymentLike
607+
608+
609+
**Parameters**:
610+
611+
```yaml
612+
- arrayElemType: string
613+
description: Array of all priority class names that are accepted.
614+
name: acceptedPriorityClassNames
615+
negationAllowed: false
616+
regexAllowed: false
617+
required: false
618+
type: array
619+
```
620+
600621
## Privilege Escalation on Containers
601622

602623
**Key**: `privilege-escalation-container`

e2etests/bats-tests.sh

+24
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,30 @@ get_value_from() {
662662

663663
}
664664

665+
@test "priority-class-name" {
666+
tmp="tests/checks/priority-class-name.yaml"
667+
cmd="${KUBE_LINTER_BIN} lint --include priority-class-name --do-not-auto-add-defaults --format json ${tmp}"
668+
run ${cmd}
669+
670+
message1=$(get_value_from "${lines[0]}" '.Reports[0] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
671+
message2=$(get_value_from "${lines[0]}" '.Reports[1] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
672+
message3=$(get_value_from "${lines[0]}" '.Reports[2] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
673+
message4=$(get_value_from "${lines[0]}" '.Reports[3] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
674+
message5=$(get_value_from "${lines[0]}" '.Reports[4] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
675+
message6=$(get_value_from "${lines[0]}" '.Reports[5] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
676+
message7=$(get_value_from "${lines[0]}" '.Reports[6] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
677+
count=$(get_value_from "${lines[0]}" '.Reports | length')
678+
679+
[[ "${message1}" == "Deployment fire-deployment: object has a priority class name defined with 'fire' but the only accepted priority class names are '[system-cluster-critical system-node-critical]'" ]]
680+
[[ "${message2}" == "Pod fire-pod: object has a priority class name defined with 'fire' but the only accepted priority class names are '[system-cluster-critical system-node-critical]'" ]]
681+
[[ "${message3}" == "DaemonSet fire-daemonset: object has a priority class name defined with 'fire' but the only accepted priority class names are '[system-cluster-critical system-node-critical]'" ]]
682+
[[ "${message4}" == "ReplicaSet fire-replicaset: object has a priority class name defined with 'fire' but the only accepted priority class names are '[system-cluster-critical system-node-critical]'" ]]
683+
[[ "${message5}" == "ReplicationController fire-replicationcontroller: object has a priority class name defined with 'fire' but the only accepted priority class names are '[system-cluster-critical system-node-critical]'" ]]
684+
[[ "${message6}" == "Job fire-job: object has a priority class name defined with 'fire' but the only accepted priority class names are '[system-cluster-critical system-node-critical]'" ]]
685+
[[ "${message7}" == "CronJob fire-cronjob: object has a priority class name defined with 'fire' but the only accepted priority class names are '[system-cluster-critical system-node-critical]'" ]]
686+
[[ "${count}" == "7" ]]
687+
}
688+
665689
@test "privilege-escalation-container" {
666690
tmp="tests/checks/privilege-escalation-container.yml"
667691
cmd="${KUBE_LINTER_BIN} lint --include privilege-escalation-container --do-not-auto-add-defaults --format json ${tmp}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: "priority-class-name"
2+
description: "Indicates when a deployment-like object does not use a valid priority class name"
3+
remediation: >-
4+
Set up the priority class name for your object to any accepted values.
5+
scope:
6+
objectKinds:
7+
- DeploymentLike
8+
template: "priority-class-name"
9+
params:
10+
acceptedPriorityClassNames: ["system-cluster-critical", "system-node-critical"]

pkg/templates/all/all.go

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
_ "golang.stackrox.io/kube-linter/pkg/templates/pdbminavailable"
3939
_ "golang.stackrox.io/kube-linter/pkg/templates/pdbunhealthypodevictionpolicy"
4040
_ "golang.stackrox.io/kube-linter/pkg/templates/ports"
41+
_ "golang.stackrox.io/kube-linter/pkg/templates/priorityclassname"
4142
_ "golang.stackrox.io/kube-linter/pkg/templates/privileged"
4243
_ "golang.stackrox.io/kube-linter/pkg/templates/privilegedports"
4344
_ "golang.stackrox.io/kube-linter/pkg/templates/privilegeescalation"

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

+69
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package params
2+
3+
// Params represents the params accepted by this template.
4+
type Params struct {
5+
// Array of all priority class names that are accepted.
6+
// +noregex
7+
// +notnegatable
8+
AcceptedPriorityClassNames []string `json:"acceptedPriorityClassNames"`
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package priorityclassname
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"slices"
8+
9+
"golang.stackrox.io/kube-linter/pkg/check"
10+
"golang.stackrox.io/kube-linter/pkg/config"
11+
"golang.stackrox.io/kube-linter/pkg/diagnostic"
12+
"golang.stackrox.io/kube-linter/pkg/extract"
13+
"golang.stackrox.io/kube-linter/pkg/lintcontext"
14+
"golang.stackrox.io/kube-linter/pkg/objectkinds"
15+
"golang.stackrox.io/kube-linter/pkg/templates"
16+
"golang.stackrox.io/kube-linter/pkg/templates/priorityclassname/internal/params"
17+
)
18+
19+
const (
20+
templateKey = "priority-class-name"
21+
)
22+
23+
func init() {
24+
templates.Register(check.Template{
25+
HumanName: "Priority class name",
26+
Key: templateKey,
27+
Description: "Flag applications running with invalid priority class name.",
28+
SupportedObjectKinds: config.ObjectKindsDesc{
29+
ObjectKinds: []string{objectkinds.DeploymentLike},
30+
},
31+
Parameters: params.ParamDescs,
32+
ParseAndValidateParams: params.ParseAndValidate,
33+
Instantiate: params.WrapInstantiateFunc(func(p params.Params) (check.Func, error) {
34+
return func(_ lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic {
35+
spec, found := extract.PodSpec(object.K8sObject)
36+
isEmpty := strings.TrimSpace(spec.PriorityClassName) == ""
37+
isAccepted := slices.Contains(p.AcceptedPriorityClassNames, spec.PriorityClassName)
38+
if !found || isEmpty || isAccepted {
39+
return nil
40+
}
41+
return []diagnostic.Diagnostic{
42+
{Message: fmt.Sprintf("object has a priority class name defined with '%s' but the only accepted priority class names are '%s'", spec.PriorityClassName, p.AcceptedPriorityClassNames)},
43+
}
44+
}, nil
45+
}),
46+
})
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package priorityclassname
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/suite"
7+
"golang.stackrox.io/kube-linter/pkg/diagnostic"
8+
"golang.stackrox.io/kube-linter/pkg/lintcontext/mocks"
9+
"golang.stackrox.io/kube-linter/pkg/templates"
10+
"golang.stackrox.io/kube-linter/pkg/templates/priorityclassname/internal/params"
11+
appsV1 "k8s.io/api/apps/v1"
12+
)
13+
14+
func TestPriorityClassName(t *testing.T) {
15+
suite.Run(t, new(PriorityClassNameTestSuite))
16+
}
17+
18+
type PriorityClassNameTestSuite struct {
19+
templates.TemplateTestSuite
20+
21+
ctx *mocks.MockLintContext
22+
}
23+
24+
func (s *PriorityClassNameTestSuite) SetupTest() {
25+
s.Init(templateKey)
26+
s.ctx = mocks.NewMockContext()
27+
}
28+
29+
func (s *PriorityClassNameTestSuite) addDeploymentWithPriorityClassName(name, priorityClassName string) {
30+
s.ctx.AddMockDeployment(s.T(), name)
31+
s.ctx.ModifyDeployment(s.T(), name, func(deployment *appsV1.Deployment) {
32+
deployment.Spec.Template.Spec.PriorityClassName = priorityClassName
33+
})
34+
}
35+
36+
func (s *PriorityClassNameTestSuite) addDeploymentWithEmptyPriorityClassName(name string) {
37+
s.ctx.AddMockDeployment(s.T(), name)
38+
s.ctx.ModifyDeployment(s.T(), name, func(deployment *appsV1.Deployment) {
39+
deployment.Spec.Template.Spec.PriorityClassName = ""
40+
})
41+
}
42+
43+
func (s *PriorityClassNameTestSuite) addDeploymentWithoutPriorityClassName(name string) {
44+
s.ctx.AddMockDeployment(s.T(), name)
45+
}
46+
47+
func (s *PriorityClassNameTestSuite) addObjectWithoutPodSpec(name string) {
48+
s.ctx.AddMockService(s.T(), name)
49+
}
50+
51+
func (s *PriorityClassNameTestSuite) TestInvalidPriorityClassName() {
52+
const (
53+
invalidPriorityClassName = "invalid-priority-class-name"
54+
)
55+
56+
s.addDeploymentWithPriorityClassName(invalidPriorityClassName, "system-node-critical")
57+
58+
s.Validate(s.ctx, []templates.TestCase{
59+
{
60+
Param: params.Params{
61+
AcceptedPriorityClassNames: []string{"system-cluster-critical", "custom-priority-class-name"},
62+
},
63+
Diagnostics: map[string][]diagnostic.Diagnostic{
64+
invalidPriorityClassName: {
65+
{Message: "object has a priority class name defined with 'system-node-critical' but the only accepted priority class names are '[system-cluster-critical custom-priority-class-name]'"},
66+
},
67+
},
68+
ExpectInstantiationError: false,
69+
},
70+
})
71+
}
72+
73+
func (s *PriorityClassNameTestSuite) TestAcceptablePriorityClassName() {
74+
const (
75+
validPriorityClassName = "valid-priority-class-name"
76+
emptyPriorityClassName = "empty-priotity-class-name"
77+
withoutPriorityClassName = "without-piority-class-name"
78+
)
79+
80+
s.addDeploymentWithPriorityClassName(validPriorityClassName, "system-cluster-critical")
81+
s.addDeploymentWithEmptyPriorityClassName(emptyPriorityClassName)
82+
s.addDeploymentWithoutPriorityClassName(withoutPriorityClassName)
83+
84+
s.Validate(s.ctx, []templates.TestCase{
85+
{
86+
Param: params.Params{
87+
AcceptedPriorityClassNames: []string{"system-cluster-critical", "custom-priority-class-name"},
88+
},
89+
Diagnostics: map[string][]diagnostic.Diagnostic{
90+
validPriorityClassName: nil,
91+
emptyPriorityClassName: nil,
92+
withoutPriorityClassName: nil,
93+
},
94+
ExpectInstantiationError: false,
95+
},
96+
})
97+
}
98+
99+
func (s *PriorityClassNameTestSuite) TestObjectWithoutPodSpec() {
100+
const (
101+
objectWithoutPodSpec = "object-without-pod-spec"
102+
)
103+
104+
s.addObjectWithoutPodSpec(objectWithoutPodSpec)
105+
106+
s.Validate(s.ctx, []templates.TestCase{
107+
{
108+
Param: params.Params{},
109+
Diagnostics: map[string][]diagnostic.Diagnostic{
110+
objectWithoutPodSpec: nil,
111+
},
112+
ExpectInstantiationError: false,
113+
},
114+
})
115+
}

0 commit comments

Comments
 (0)