Skip to content

Commit 99380d7

Browse files
jonathanmdrJonathan Henrique Medeiros
andauthored
feature(lint): Adds a new template linter to check the restart policy for all deployments like objects (#915)
Co-authored-by: Jonathan Henrique Medeiros <[email protected]>
1 parent 37f508e commit 99380d7

File tree

10 files changed

+465
-0
lines changed

10 files changed

+465
-0
lines changed

docs/generated/checks.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,15 @@ value: '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+'
531531
```yaml
532532
key: owner
533533
```
534+
## restart-policy
535+
536+
**Enabled by default**: No
537+
538+
**Description**: Indicates when a deployment-like object does not use a restart policy
539+
540+
**Remediation**: Set up the restart policy for your object to 'Always' or 'OnFailure' to increase the fault tolerance.
541+
542+
**Template**: [restart-policy](templates.md#restart-policy)
534543
## run-as-non-root
535544
536545
**Enabled by default**: Yes

docs/generated/templates.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,15 @@ KubeLinter supports the following templates:
712712
type: string
713713
```
714714

715+
## Restart policy
716+
717+
**Key**: `restart-policy`
718+
719+
**Description**: Flag applications running without the restart policy.
720+
721+
**Supported Objects**: DeploymentLike
722+
723+
715724
## Run as non-root user
716725

717726
**Key**: `run-as-non-root`

e2etests/bats-tests.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,30 @@ get_value_from() {
785785
[[ "${count}" == "2" ]]
786786
}
787787

788+
@test "restart-policy" {
789+
tmp="tests/checks/restart-policy.yaml"
790+
cmd="${KUBE_LINTER_BIN} lint --include restart-policy --do-not-auto-add-defaults --format json ${tmp}"
791+
run ${cmd}
792+
793+
message1=$(get_value_from "${lines[0]}" '.Reports[0] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
794+
message2=$(get_value_from "${lines[0]}" '.Reports[1] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
795+
message3=$(get_value_from "${lines[0]}" '.Reports[2] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
796+
message4=$(get_value_from "${lines[0]}" '.Reports[3] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
797+
message5=$(get_value_from "${lines[0]}" '.Reports[4] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
798+
message6=$(get_value_from "${lines[0]}" '.Reports[5] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
799+
message7=$(get_value_from "${lines[0]}" '.Reports[6] | .Object.K8sObject.GroupVersionKind.Kind + " " + .Object.K8sObject.Name + ": " + .Diagnostic.Message')
800+
count=$(get_value_from "${lines[0]}" '.Reports | length')
801+
802+
[[ "${message1}" == "Deployment fire-deployment-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
803+
[[ "${message2}" == "Pod fire-pod-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
804+
[[ "${message3}" == "DaemonSet fire-daemonset-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
805+
[[ "${message4}" == "ReplicaSet fire-replicaset-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
806+
[[ "${message5}" == "ReplicationController fire-replicationcontroller-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
807+
[[ "${message6}" == "Job fire-job-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
808+
[[ "${message7}" == "CronJob fire-cronjob-never: object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'" ]]
809+
[[ "${count}" == "7" ]]
810+
}
811+
788812
@test "run-as-non-root" {
789813
tmp="tests/checks/run-as-non-root.yml"
790814
cmd="${KUBE_LINTER_BIN} lint --include run-as-non-root --do-not-auto-add-defaults --format json ${tmp}"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name: "restart-policy"
2+
description: "Indicates when a deployment-like object does not use a restart policy"
3+
remediation: >-
4+
Set up the restart policy for your object to 'Always' or 'OnFailure' to increase the fault tolerance.
5+
scope:
6+
objectKinds:
7+
- DeploymentLike
8+
template: "restart-policy"

pkg/templates/all/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import (
4848
_ "golang.stackrox.io/kube-linter/pkg/templates/replicas"
4949
_ "golang.stackrox.io/kube-linter/pkg/templates/requiredannotation"
5050
_ "golang.stackrox.io/kube-linter/pkg/templates/requiredlabel"
51+
_ "golang.stackrox.io/kube-linter/pkg/templates/restartpolicy"
5152
_ "golang.stackrox.io/kube-linter/pkg/templates/runasnonroot"
5253
_ "golang.stackrox.io/kube-linter/pkg/templates/sccdenypriv"
5354
_ "golang.stackrox.io/kube-linter/pkg/templates/serviceaccount"

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

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package params
2+
3+
// Params represents the params accepted by this template.
4+
type Params struct {
5+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package restartpolicy
2+
3+
import (
4+
"fmt"
5+
6+
"golang.stackrox.io/kube-linter/pkg/check"
7+
"golang.stackrox.io/kube-linter/pkg/config"
8+
"golang.stackrox.io/kube-linter/pkg/diagnostic"
9+
"golang.stackrox.io/kube-linter/pkg/extract"
10+
"golang.stackrox.io/kube-linter/pkg/lintcontext"
11+
"golang.stackrox.io/kube-linter/pkg/objectkinds"
12+
"golang.stackrox.io/kube-linter/pkg/templates"
13+
"golang.stackrox.io/kube-linter/pkg/templates/restartpolicy/internal/params"
14+
coreV1 "k8s.io/api/core/v1"
15+
)
16+
17+
const (
18+
templateKey = "restart-policy"
19+
)
20+
21+
var acceptedRestartPolicies = []coreV1.RestartPolicy{coreV1.RestartPolicyAlways, coreV1.RestartPolicyOnFailure}
22+
23+
func init() {
24+
templates.Register(check.Template{
25+
HumanName: "Restart policy",
26+
Key: templateKey,
27+
Description: "Flag applications running without the restart policy.",
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+
if !found {
37+
return nil
38+
}
39+
for _, policy := range acceptedRestartPolicies {
40+
if spec.RestartPolicy == policy {
41+
return nil
42+
}
43+
}
44+
return []diagnostic.Diagnostic{
45+
{Message: fmt.Sprintf("object has a restart policy defined with '%s' but the only accepted restart policies are '%s'", spec.RestartPolicy, acceptedRestartPolicies)},
46+
}
47+
}, nil
48+
}),
49+
})
50+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package restartpolicy
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/restartpolicy/internal/params"
11+
appsV1 "k8s.io/api/apps/v1"
12+
coreV1 "k8s.io/api/core/v1"
13+
)
14+
15+
func TestRestartPolicy(t *testing.T) {
16+
suite.Run(t, new(RestartPolicyTestSuite))
17+
}
18+
19+
type RestartPolicyTestSuite struct {
20+
templates.TemplateTestSuite
21+
22+
ctx *mocks.MockLintContext
23+
}
24+
25+
func (s *RestartPolicyTestSuite) SetupTest() {
26+
s.Init(templateKey)
27+
s.ctx = mocks.NewMockContext()
28+
}
29+
30+
func (s *RestartPolicyTestSuite) addDeploymentWithRestartPolicy(name string, policy coreV1.RestartPolicy) {
31+
s.ctx.AddMockDeployment(s.T(), name)
32+
s.ctx.ModifyDeployment(s.T(), name, func(deployment *appsV1.Deployment) {
33+
deployment.Spec.Template.Spec.RestartPolicy = policy
34+
})
35+
}
36+
37+
func (s *RestartPolicyTestSuite) addDeploymentWithEmptyRestartPolicy(name string) {
38+
s.ctx.AddMockDeployment(s.T(), name)
39+
s.ctx.ModifyDeployment(s.T(), name, func(deployment *appsV1.Deployment) {
40+
deployment.Spec.Template.Spec.RestartPolicy = ""
41+
})
42+
}
43+
44+
func (s *RestartPolicyTestSuite) addDeploymentWithoutRestartPolicy(name string) {
45+
s.ctx.AddMockDeployment(s.T(), name)
46+
}
47+
48+
func (s *RestartPolicyTestSuite) addObjectWithoutPodSpec(name string) {
49+
s.ctx.AddMockService(s.T(), name)
50+
}
51+
52+
func (s *RestartPolicyTestSuite) TestInvalidRestartPolicies() {
53+
const (
54+
withoutRestartPolicy = "without-restart-policy"
55+
emptyRestartPolicy = "empty-restart-policy"
56+
restartPolicyNever = "restart-policy-never"
57+
)
58+
59+
s.addDeploymentWithoutRestartPolicy(withoutRestartPolicy)
60+
s.addDeploymentWithEmptyRestartPolicy(emptyRestartPolicy)
61+
s.addDeploymentWithRestartPolicy(restartPolicyNever, coreV1.RestartPolicyNever)
62+
63+
s.Validate(s.ctx, []templates.TestCase{
64+
{
65+
Param: params.Params{},
66+
Diagnostics: map[string][]diagnostic.Diagnostic{
67+
withoutRestartPolicy: {
68+
{Message: "object has a restart policy defined with '' but the only accepted restart policies are '[Always OnFailure]'"},
69+
},
70+
emptyRestartPolicy: {
71+
{Message: "object has a restart policy defined with '' but the only accepted restart policies are '[Always OnFailure]'"},
72+
},
73+
restartPolicyNever: {
74+
{Message: "object has a restart policy defined with 'Never' but the only accepted restart policies are '[Always OnFailure]'"},
75+
},
76+
},
77+
ExpectInstantiationError: false,
78+
},
79+
})
80+
}
81+
82+
func (s *RestartPolicyTestSuite) TestAcceptableRestartPolicy() {
83+
const (
84+
alwaysRestartPolicy = "restart-policy-always"
85+
onFailureRestartPolicy = "restart-policy-on-failure"
86+
)
87+
s.addDeploymentWithRestartPolicy(alwaysRestartPolicy, coreV1.RestartPolicyAlways)
88+
s.addDeploymentWithRestartPolicy(onFailureRestartPolicy, coreV1.RestartPolicyOnFailure)
89+
90+
s.Validate(s.ctx, []templates.TestCase{
91+
{
92+
Param: params.Params{},
93+
Diagnostics: map[string][]diagnostic.Diagnostic{
94+
alwaysRestartPolicy: nil,
95+
onFailureRestartPolicy: nil,
96+
},
97+
ExpectInstantiationError: false,
98+
},
99+
})
100+
}
101+
102+
func (s *RestartPolicyTestSuite) TestObjectWithoutPodSpec() {
103+
const (
104+
objectWithoutPodSpec = "object-without-pod-spec"
105+
)
106+
107+
s.addObjectWithoutPodSpec(objectWithoutPodSpec)
108+
109+
s.Validate(s.ctx, []templates.TestCase{
110+
{
111+
Param: params.Params{},
112+
Diagnostics: map[string][]diagnostic.Diagnostic{
113+
objectWithoutPodSpec: nil,
114+
},
115+
ExpectInstantiationError: false,
116+
},
117+
})
118+
}

0 commit comments

Comments
 (0)