Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/generated/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,26 @@ KubeLinter supports the following templates:
**Supported Objects**: DeploymentLike


## StatefulSet VolumeClaimTemplate Annotation

**Key**: `statefulset-volumeclaimtemplate-annotation`

**Description**: Check if StatefulSet's VolumeClaimTemplate contains a specific annotation

**Supported Objects**: DeploymentLike


**Parameters**:

```yaml
- description: Annotation specifies the required annotation to match.
name: annotation
negationAllowed: true
regexAllowed: true
required: true
type: string
```

## Target Port

**Key**: `target-port`
Expand Down
18 changes: 18 additions & 0 deletions e2etests/bats-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1109,3 +1109,21 @@ get_value_from() {
@test "flag-read-from-stdin" {
echo "---" | ${KUBE_LINTER_BIN} lint -
}

@test "statefulset-volumeclaimtemplate-annotation" {
tmp="tests/checks/statefulset-volumeclaimtemplate-annotation.yml"
cmd="${KUBE_LINTER_BIN} lint --config e2etests/testdata/statefulset-volumeclaimtemplate-annotation-config.yaml --do-not-auto-add-defaults --format json ${tmp}"
run ${cmd}

print_info "${status}" "${output}" "${cmd}" "${tmp}"
[ "$status" -eq 1 ]

message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message')
failing_resource=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.Name')
count=$(get_value_from "${lines[0]}" '.Reports | length')

[[ "${message1}" == "StatefulSet: StatefulSet's VolumeClaimTemplate is missing required annotation: required-annotation" ]]
[[ "${failing_resource}" == "bad-sts" ]]
[[ "${count}" == "1" ]]
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
checks:
addAllBuiltIn: false
customChecks:
- name: "statefulset-volumeclaimtemplate-annotation"
template: "statefulset-volumeclaimtemplate-annotation"
params:
annotation: required-annotation
35 changes: 35 additions & 0 deletions pkg/extract/sts_spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package extract

import (
"reflect"

"golang.stackrox.io/kube-linter/pkg/k8sutil"
appsV1 "k8s.io/api/apps/v1"
)

func StatefulSetSpec(obj k8sutil.Object) (appsV1.StatefulSetSpec, bool) {
if obj == nil {
return appsV1.StatefulSetSpec{}, false
}

switch obj := obj.(type) {
case *appsV1.StatefulSet:
return obj.Spec, true
default:
kind := obj.GetObjectKind().GroupVersionKind().Kind
if kind != "StatefulSet" {
return appsV1.StatefulSetSpec{}, false
}

objValue := reflect.Indirect(reflect.ValueOf(obj))
spec := objValue.FieldByName("Spec")
if !spec.IsValid() {
return appsV1.StatefulSetSpec{}, false
}
statefulSetSpec, ok := spec.Interface().(appsV1.StatefulSetSpec)
if ok {
return statefulSetSpec, true
}
return appsV1.StatefulSetSpec{}, false
}
}
93 changes: 93 additions & 0 deletions pkg/extract/sts_spec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package extract

import (
"testing"
"time"

appsV1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"

"github.com/stretchr/testify/assert"
)

type fakeStatefulSet struct {
metav1.TypeMeta
metav1.ObjectMeta
Spec appsV1.StatefulSetSpec
}

func (f *fakeStatefulSet) GetObjectKind() schema.ObjectKind {
return &f.TypeMeta
}

func (f *fakeStatefulSet) DeepCopyObject() runtime.Object {
return &fakeStatefulSet{
TypeMeta: f.TypeMeta,
ObjectMeta: metav1.ObjectMeta{
Name: f.Name,
Namespace: f.Namespace,
},
Spec: f.Spec,
}
}

func (f *fakeStatefulSet) GetAnnotations() map[string]string {
return map[string]string{"key": "value"} // Example annotation
}

func (f *fakeStatefulSet) GetCreationTimestamp() metav1.Time {
return metav1.Time{Time: time.Now()}
}

func TestStatefulSetSpec(t *testing.T) {
t.Run("nil object", func(t *testing.T) {
spec, ok := StatefulSetSpec(nil)
assert.False(t, ok)
assert.Equal(t, appsV1.StatefulSetSpec{}, spec)
})

t.Run("typed StatefulSet", func(t *testing.T) {
sampleSpec := appsV1.StatefulSetSpec{
ServiceName: "my-service",
}
obj := &appsV1.StatefulSet{
Spec: sampleSpec,
}
spec, ok := StatefulSetSpec(obj)
assert.True(t, ok)
assert.Equal(t, sampleSpec, spec)
})

t.Run("fallback via reflection", func(t *testing.T) {
sampleSpec := appsV1.StatefulSetSpec{
ServiceName: "reflected-service",
}
obj := &fakeStatefulSet{
TypeMeta: metav1.TypeMeta{
Kind: "StatefulSet",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-statefulset",
Namespace: "default",
},
Spec: sampleSpec,
}
spec, ok := StatefulSetSpec(obj)
assert.True(t, ok)
assert.Equal(t, sampleSpec, spec)
})

t.Run("wrong kind", func(t *testing.T) {
obj := &fakeStatefulSet{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
},
Spec: appsV1.StatefulSetSpec{},
}
spec, ok := StatefulSetSpec(obj)
assert.False(t, ok)
assert.Equal(t, appsV1.StatefulSetSpec{}, spec)
})
}
5 changes: 5 additions & 0 deletions pkg/lintcontext/mocks/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ func (l *MockLintContext) InvalidObjects() []lintcontext.InvalidObject {
func NewMockContext() *MockLintContext {
return &MockLintContext{objects: make(map[string]k8sutil.Object)}
}

// AddObject adds an object to the MockLintContext
func (l *MockLintContext) AddObject(key string, obj k8sutil.Object) {
l.objects[key] = obj
}
24 changes: 24 additions & 0 deletions pkg/objectkinds/pvc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package objectkinds

import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)

const (
PersistentVolumeClaim = "PersistentVolumeClaim"
)

var (
persistentvolumeclaimGVK = v1.SchemeGroupVersion.WithKind("PersistentVolumeClaim")
)

func init() {
RegisterObjectKind(PersistentVolumeClaim, MatcherFunc(func(gvk schema.GroupVersionKind) bool {
return gvk == persistentvolumeclaimGVK
}))
}

func GetPersistentVolumeClaimAPIVersion() string {
return persistentvolumeclaimGVK.GroupVersion().String()
}
13 changes: 13 additions & 0 deletions pkg/objectkinds/pvc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package objectkinds_test

import (
"testing"

"github.com/stretchr/testify/assert"
"golang.stackrox.io/kube-linter/pkg/objectkinds"
)

func TestGetPersistentVolumeClaimAPIVersion(t *testing.T) {
apiVersion := objectkinds.GetPersistentVolumeClaimAPIVersion()
assert.NotEmpty(t, apiVersion)
}
1 change: 1 addition & 0 deletions pkg/templates/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import (
_ "golang.stackrox.io/kube-linter/pkg/templates/targetport"
_ "golang.stackrox.io/kube-linter/pkg/templates/unsafeprocmount"
_ "golang.stackrox.io/kube-linter/pkg/templates/updateconfig"
_ "golang.stackrox.io/kube-linter/pkg/templates/volumeclaimtemplates"
_ "golang.stackrox.io/kube-linter/pkg/templates/wildcardinrules"
_ "golang.stackrox.io/kube-linter/pkg/templates/writablehostmount"
)

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading