Skip to content

Commit 21eedca

Browse files
Add a New Check For Annotations on PersistentVolumeClaims (#922)
1 parent a92a715 commit 21eedca

File tree

15 files changed

+643
-0
lines changed

15 files changed

+643
-0
lines changed

docs/generated/templates.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,26 @@ KubeLinter supports the following templates:
829829
**Supported Objects**: DeploymentLike
830830

831831

832+
## StatefulSet VolumeClaimTemplate Annotation
833+
834+
**Key**: `statefulset-volumeclaimtemplate-annotation`
835+
836+
**Description**: Check if StatefulSet's VolumeClaimTemplate contains a specific annotation
837+
838+
**Supported Objects**: DeploymentLike
839+
840+
841+
**Parameters**:
842+
843+
```yaml
844+
- description: Annotation specifies the required annotation to match.
845+
name: annotation
846+
negationAllowed: true
847+
regexAllowed: true
848+
required: true
849+
type: string
850+
```
851+
832852
## Target Port
833853

834854
**Key**: `target-port`

e2etests/bats-tests.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,3 +1109,21 @@ get_value_from() {
11091109
@test "flag-read-from-stdin" {
11101110
echo "---" | ${KUBE_LINTER_BIN} lint -
11111111
}
1112+
1113+
@test "statefulset-volumeclaimtemplate-annotation" {
1114+
tmp="tests/checks/statefulset-volumeclaimtemplate-annotation.yml"
1115+
cmd="${KUBE_LINTER_BIN} lint --config e2etests/testdata/statefulset-volumeclaimtemplate-annotation-config.yaml --do-not-auto-add-defaults --format json ${tmp}"
1116+
run ${cmd}
1117+
1118+
print_info "${status}" "${output}" "${cmd}" "${tmp}"
1119+
[ "$status" -eq 1 ]
1120+
1121+
message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message')
1122+
failing_resource=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.Name')
1123+
count=$(get_value_from "${lines[0]}" '.Reports | length')
1124+
1125+
[[ "${message1}" == "StatefulSet: StatefulSet's VolumeClaimTemplate is missing required annotation: required-annotation" ]]
1126+
[[ "${failing_resource}" == "bad-sts" ]]
1127+
[[ "${count}" == "1" ]]
1128+
}
1129+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
checks:
2+
addAllBuiltIn: false
3+
customChecks:
4+
- name: "statefulset-volumeclaimtemplate-annotation"
5+
template: "statefulset-volumeclaimtemplate-annotation"
6+
params:
7+
annotation: required-annotation

pkg/extract/sts_spec.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package extract
2+
3+
import (
4+
"reflect"
5+
6+
"golang.stackrox.io/kube-linter/pkg/k8sutil"
7+
appsV1 "k8s.io/api/apps/v1"
8+
)
9+
10+
func StatefulSetSpec(obj k8sutil.Object) (appsV1.StatefulSetSpec, bool) {
11+
if obj == nil {
12+
return appsV1.StatefulSetSpec{}, false
13+
}
14+
15+
switch obj := obj.(type) {
16+
case *appsV1.StatefulSet:
17+
return obj.Spec, true
18+
default:
19+
kind := obj.GetObjectKind().GroupVersionKind().Kind
20+
if kind != "StatefulSet" {
21+
return appsV1.StatefulSetSpec{}, false
22+
}
23+
24+
objValue := reflect.Indirect(reflect.ValueOf(obj))
25+
spec := objValue.FieldByName("Spec")
26+
if !spec.IsValid() {
27+
return appsV1.StatefulSetSpec{}, false
28+
}
29+
statefulSetSpec, ok := spec.Interface().(appsV1.StatefulSetSpec)
30+
if ok {
31+
return statefulSetSpec, true
32+
}
33+
return appsV1.StatefulSetSpec{}, false
34+
}
35+
}

pkg/extract/sts_spec_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package extract
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
appsV1 "k8s.io/api/apps/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/runtime"
10+
"k8s.io/apimachinery/pkg/runtime/schema"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
type fakeStatefulSet struct {
16+
metav1.TypeMeta
17+
metav1.ObjectMeta
18+
Spec appsV1.StatefulSetSpec
19+
}
20+
21+
func (f *fakeStatefulSet) GetObjectKind() schema.ObjectKind {
22+
return &f.TypeMeta
23+
}
24+
25+
func (f *fakeStatefulSet) DeepCopyObject() runtime.Object {
26+
return &fakeStatefulSet{
27+
TypeMeta: f.TypeMeta,
28+
ObjectMeta: metav1.ObjectMeta{
29+
Name: f.Name,
30+
Namespace: f.Namespace,
31+
},
32+
Spec: f.Spec,
33+
}
34+
}
35+
36+
func (f *fakeStatefulSet) GetAnnotations() map[string]string {
37+
return map[string]string{"key": "value"} // Example annotation
38+
}
39+
40+
func (f *fakeStatefulSet) GetCreationTimestamp() metav1.Time {
41+
return metav1.Time{Time: time.Now()}
42+
}
43+
44+
func TestStatefulSetSpec(t *testing.T) {
45+
t.Run("nil object", func(t *testing.T) {
46+
spec, ok := StatefulSetSpec(nil)
47+
assert.False(t, ok)
48+
assert.Equal(t, appsV1.StatefulSetSpec{}, spec)
49+
})
50+
51+
t.Run("typed StatefulSet", func(t *testing.T) {
52+
sampleSpec := appsV1.StatefulSetSpec{
53+
ServiceName: "my-service",
54+
}
55+
obj := &appsV1.StatefulSet{
56+
Spec: sampleSpec,
57+
}
58+
spec, ok := StatefulSetSpec(obj)
59+
assert.True(t, ok)
60+
assert.Equal(t, sampleSpec, spec)
61+
})
62+
63+
t.Run("fallback via reflection", func(t *testing.T) {
64+
sampleSpec := appsV1.StatefulSetSpec{
65+
ServiceName: "reflected-service",
66+
}
67+
obj := &fakeStatefulSet{
68+
TypeMeta: metav1.TypeMeta{
69+
Kind: "StatefulSet",
70+
},
71+
ObjectMeta: metav1.ObjectMeta{
72+
Name: "fake-statefulset",
73+
Namespace: "default",
74+
},
75+
Spec: sampleSpec,
76+
}
77+
spec, ok := StatefulSetSpec(obj)
78+
assert.True(t, ok)
79+
assert.Equal(t, sampleSpec, spec)
80+
})
81+
82+
t.Run("wrong kind", func(t *testing.T) {
83+
obj := &fakeStatefulSet{
84+
TypeMeta: metav1.TypeMeta{
85+
Kind: "Deployment",
86+
},
87+
Spec: appsV1.StatefulSetSpec{},
88+
}
89+
spec, ok := StatefulSetSpec(obj)
90+
assert.False(t, ok)
91+
assert.Equal(t, appsV1.StatefulSetSpec{}, spec)
92+
})
93+
}

pkg/lintcontext/mocks/context.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,8 @@ func (l *MockLintContext) InvalidObjects() []lintcontext.InvalidObject {
2828
func NewMockContext() *MockLintContext {
2929
return &MockLintContext{objects: make(map[string]k8sutil.Object)}
3030
}
31+
32+
// AddObject adds an object to the MockLintContext
33+
func (l *MockLintContext) AddObject(key string, obj k8sutil.Object) {
34+
l.objects[key] = obj
35+
}

pkg/objectkinds/pvc.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package objectkinds
2+
3+
import (
4+
v1 "k8s.io/api/core/v1"
5+
"k8s.io/apimachinery/pkg/runtime/schema"
6+
)
7+
8+
const (
9+
PersistentVolumeClaim = "PersistentVolumeClaim"
10+
)
11+
12+
var (
13+
persistentvolumeclaimGVK = v1.SchemeGroupVersion.WithKind("PersistentVolumeClaim")
14+
)
15+
16+
func init() {
17+
RegisterObjectKind(PersistentVolumeClaim, MatcherFunc(func(gvk schema.GroupVersionKind) bool {
18+
return gvk == persistentvolumeclaimGVK
19+
}))
20+
}
21+
22+
func GetPersistentVolumeClaimAPIVersion() string {
23+
return persistentvolumeclaimGVK.GroupVersion().String()
24+
}

pkg/objectkinds/pvc_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package objectkinds_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"golang.stackrox.io/kube-linter/pkg/objectkinds"
8+
)
9+
10+
func TestGetPersistentVolumeClaimAPIVersion(t *testing.T) {
11+
apiVersion := objectkinds.GetPersistentVolumeClaimAPIVersion()
12+
assert.NotEmpty(t, apiVersion)
13+
}

pkg/templates/all/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import (
6161
_ "golang.stackrox.io/kube-linter/pkg/templates/targetport"
6262
_ "golang.stackrox.io/kube-linter/pkg/templates/unsafeprocmount"
6363
_ "golang.stackrox.io/kube-linter/pkg/templates/updateconfig"
64+
_ "golang.stackrox.io/kube-linter/pkg/templates/volumeclaimtemplates"
6465
_ "golang.stackrox.io/kube-linter/pkg/templates/wildcardinrules"
6566
_ "golang.stackrox.io/kube-linter/pkg/templates/writablehostmount"
6667
)

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

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)