Skip to content

Commit

Permalink
status update checks (#105)
Browse files Browse the repository at this point in the history
* status update checks

Signed-off-by: Amir Malka <[email protected]>

* added tests and updated readme

Signed-off-by: Amir Malka <[email protected]>

* fix test

Signed-off-by: Amir Malka <[email protected]>

* CR

Signed-off-by: Amir Malka <[email protected]>

---------

Signed-off-by: Amir Malka <[email protected]>
  • Loading branch information
amirmalka authored Apr 4, 2024
1 parent d4a5e23 commit c8186a0
Show file tree
Hide file tree
Showing 7 changed files with 418 additions and 5 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ go build -v ./...
go test -v -failfast -count=1 ./...
```

### Storage operations

During storage operations there are several opportunities to either reject the request or modify the stored object before it is written.

Each type of operation (Create/Update/Delete) has its own set of functions that will run in the lifecycle of the request.

These functions are declared in `pkg/registry/softwarecomposition/<type>/strategy.go`

Read more about each function and its use [here](https://github.com/kubernetes-sigs/apiserver-builder-alpha/blob/master/docs/concepts/api_building_overview.md#storage-operations)

### Authentication plugins

The normal build supports only a very spare selection of
Expand Down
44 changes: 42 additions & 2 deletions pkg/registry/softwarecomposition/applicationprofile/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names"

"github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers"
"github.com/kubescape/storage/pkg/apis/softwarecomposition"
"github.com/kubescape/storage/pkg/utils"
)

// NewStrategy creates and returns a applicationProfileStrategy instance
Expand Down Expand Up @@ -57,10 +59,36 @@ func (applicationProfileStrategy) PrepareForCreate(ctx context.Context, obj runt
}

func (applicationProfileStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newAP := obj.(*softwarecomposition.ApplicationProfile)
oldAP := old.(*softwarecomposition.ApplicationProfile)

// completion status cannot be transitioned from 'complete' -> 'partial'
// in such case, we reject status updates
if oldAP.Annotations[helpers.CompletionMetadataKey] == helpers.Complete && newAP.Annotations[helpers.CompletionMetadataKey] == helpers.Partial {
newAP.Annotations[helpers.CompletionMetadataKey] = helpers.Complete

if v, ok := oldAP.Annotations[helpers.StatusMetadataKey]; ok {
newAP.Annotations[helpers.StatusMetadataKey] = v
} else {
delete(newAP.Annotations, helpers.StatusMetadataKey)
}
}
}

func (applicationProfileStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
return field.ErrorList{}
ap := obj.(*softwarecomposition.ApplicationProfile)

allErrors := field.ErrorList{}

if err := utils.ValidateCompletionAnnotation(ap.Annotations); err != nil {
allErrors = append(allErrors, err)
}

if err := utils.ValidateStatusAnnotation(ap.Annotations); err != nil {
allErrors = append(allErrors, err)
}

return allErrors
}

// WarningsOnCreate returns warnings for the creation of the given object.
Expand All @@ -80,7 +108,19 @@ func (applicationProfileStrategy) Canonicalize(obj runtime.Object) {
}

func (applicationProfileStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return field.ErrorList{}
ap := obj.(*softwarecomposition.ApplicationProfile)

allErrors := field.ErrorList{}

if err := utils.ValidateCompletionAnnotation(ap.Annotations); err != nil {
allErrors = append(allErrors, err)
}

if err := utils.ValidateStatusAnnotation(ap.Annotations); err != nil {
allErrors = append(allErrors, err)
}

return allErrors
}

// WarningsOnUpdate returns warnings for the given update.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package applicationprofile

import (
"context"
"reflect"
"testing"

"github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers"
"github.com/kubescape/storage/pkg/apis/softwarecomposition"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestPrepareForUpdate(t *testing.T) {
tests := []struct {
name string
oldAnnotations map[string]string
newAnnotations map[string]string
expected map[string]string
}{
{
name: "transition from complete (with status) to partial - rejected",
oldAnnotations: map[string]string{
helpers.CompletionMetadataKey: "complete",
helpers.StatusMetadataKey: "initializing",
},
newAnnotations: map[string]string{
helpers.CompletionMetadataKey: "partial",
helpers.StatusMetadataKey: "ready",
},
expected: map[string]string{
helpers.CompletionMetadataKey: "complete",
helpers.StatusMetadataKey: "initializing",
},
},
{
name: "transition from partial (with status) to complete - accepted",
oldAnnotations: map[string]string{
helpers.CompletionMetadataKey: "partial",
helpers.StatusMetadataKey: "initializing",
},
newAnnotations: map[string]string{
helpers.CompletionMetadataKey: "partial",
helpers.StatusMetadataKey: "ready",
},
expected: map[string]string{
helpers.CompletionMetadataKey: "partial",
helpers.StatusMetadataKey: "ready",
},
},
{
name: "transition from partial (without status) to complete - accepted",
oldAnnotations: map[string]string{
helpers.CompletionMetadataKey: "partial",
},
newAnnotations: map[string]string{
helpers.CompletionMetadataKey: "complete",
helpers.StatusMetadataKey: "ready",
},
expected: map[string]string{
helpers.CompletionMetadataKey: "complete",
helpers.StatusMetadataKey: "ready",
},
},
{
name: "transition from complete (without status) to partial - rejected",
oldAnnotations: map[string]string{
helpers.CompletionMetadataKey: "complete",
},
newAnnotations: map[string]string{
helpers.CompletionMetadataKey: "partial",
helpers.StatusMetadataKey: "initializing",
},
expected: map[string]string{
helpers.CompletionMetadataKey: "complete",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := applicationProfileStrategy{}

obj := &softwarecomposition.ApplicationProfile{ObjectMeta: metav1.ObjectMeta{Annotations: tt.newAnnotations}}
old := &softwarecomposition.ApplicationProfile{ObjectMeta: metav1.ObjectMeta{Annotations: tt.oldAnnotations}}

s.PrepareForUpdate(context.Background(), obj, old)
if !reflect.DeepEqual(obj.Annotations, tt.expected) {
t.Errorf("PrepareForUpdate() = %v, want %v", obj.Annotations, tt.expected)
}
})
}
}
46 changes: 43 additions & 3 deletions pkg/registry/softwarecomposition/networkneighbors/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"fmt"

"github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers"
"github.com/kubescape/storage/pkg/apis/softwarecomposition"
"github.com/kubescape/storage/pkg/utils"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -53,11 +55,37 @@ func (networkNeighborsStrategy) NamespaceScoped() bool {
func (networkNeighborsStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
}

func (networkNeighborsStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
func (s networkNeighborsStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newNN := obj.(*softwarecomposition.NetworkNeighbors)
oldNN := old.(*softwarecomposition.NetworkNeighbors)

// completion status cannot be transitioned from 'complete' -> 'partial'
// in such case, we reject status updates
if oldNN.Annotations[helpers.CompletionMetadataKey] == helpers.Complete && newNN.Annotations[helpers.CompletionMetadataKey] == helpers.Partial {
newNN.Annotations[helpers.CompletionMetadataKey] = helpers.Complete

if v, ok := oldNN.Annotations[helpers.StatusMetadataKey]; ok {
newNN.Annotations[helpers.StatusMetadataKey] = v
} else {
delete(newNN.Annotations, helpers.StatusMetadataKey)
}
}
}

func (networkNeighborsStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
return field.ErrorList{}
nn := obj.(*softwarecomposition.NetworkNeighbors)

allErrors := field.ErrorList{}

if err := utils.ValidateCompletionAnnotation(nn.Annotations); err != nil {
allErrors = append(allErrors, err)
}

if err := utils.ValidateStatusAnnotation(nn.Annotations); err != nil {
allErrors = append(allErrors, err)
}

return allErrors
}

// WarningsOnCreate returns warnings for the creation of the given object.
Expand All @@ -77,7 +105,19 @@ func (networkNeighborsStrategy) Canonicalize(obj runtime.Object) {
}

func (networkNeighborsStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return field.ErrorList{}
nn := obj.(*softwarecomposition.NetworkNeighbors)

allErrors := field.ErrorList{}

if err := utils.ValidateCompletionAnnotation(nn.Annotations); err != nil {
allErrors = append(allErrors, err)
}

if err := utils.ValidateStatusAnnotation(nn.Annotations); err != nil {
allErrors = append(allErrors, err)
}

return allErrors
}

// WarningsOnUpdate returns warnings for the given update.
Expand Down
92 changes: 92 additions & 0 deletions pkg/registry/softwarecomposition/networkneighbors/strategy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package networkneighbors

import (
"context"
"reflect"
"testing"

"github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers"
"github.com/kubescape/storage/pkg/apis/softwarecomposition"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestPrepareForUpdate(t *testing.T) {
tests := []struct {
name string
oldAnnotations map[string]string
newAnnotations map[string]string
expected map[string]string
}{
{
name: "transition from complete (with status) to partial - rejected",
oldAnnotations: map[string]string{
helpers.CompletionMetadataKey: "complete",
helpers.StatusMetadataKey: "initializing",
},
newAnnotations: map[string]string{
helpers.CompletionMetadataKey: "partial",
helpers.StatusMetadataKey: "ready",
},
expected: map[string]string{
helpers.CompletionMetadataKey: "complete",
helpers.StatusMetadataKey: "initializing",
},
},
{
name: "transition from partial (with status) to complete - accepted",
oldAnnotations: map[string]string{
helpers.CompletionMetadataKey: "partial",
helpers.StatusMetadataKey: "initializing",
},
newAnnotations: map[string]string{
helpers.CompletionMetadataKey: "partial",
helpers.StatusMetadataKey: "ready",
},
expected: map[string]string{
helpers.CompletionMetadataKey: "partial",
helpers.StatusMetadataKey: "ready",
},
},
{
name: "transition from partial (without status) to complete - accepted",
oldAnnotations: map[string]string{
helpers.CompletionMetadataKey: "partial",
},
newAnnotations: map[string]string{
helpers.CompletionMetadataKey: "complete",
helpers.StatusMetadataKey: "ready",
},
expected: map[string]string{
helpers.CompletionMetadataKey: "complete",
helpers.StatusMetadataKey: "ready",
},
},
{
name: "transition from complete (without status) to partial - rejected",
oldAnnotations: map[string]string{
helpers.CompletionMetadataKey: "complete",
},
newAnnotations: map[string]string{
helpers.CompletionMetadataKey: "partial",
helpers.StatusMetadataKey: "initializing",
},
expected: map[string]string{
helpers.CompletionMetadataKey: "complete",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := networkNeighborsStrategy{}

obj := &softwarecomposition.NetworkNeighbors{ObjectMeta: metav1.ObjectMeta{Annotations: tt.newAnnotations}}
old := &softwarecomposition.NetworkNeighbors{ObjectMeta: metav1.ObjectMeta{Annotations: tt.oldAnnotations}}

s.PrepareForUpdate(context.Background(), obj, old)
if !reflect.DeepEqual(obj.Annotations, tt.expected) {
t.Errorf("PrepareForUpdate() = %v, want %v", obj.Annotations, tt.expected)
}
})
}
}
31 changes: 31 additions & 0 deletions pkg/utils/validations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package utils

import (
"github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers"
"k8s.io/apimachinery/pkg/util/validation/field"
)

func ValidateCompletionAnnotation(annotations map[string]string) *field.Error {
if v, ok := annotations[helpers.CompletionMetadataKey]; ok {
switch v {
case helpers.Complete, helpers.Partial:
return nil
default:
return field.Invalid(field.NewPath("metadata").Child("annotations").Child(helpers.CompletionMetadataKey), v, "invalid value")
}
}
return nil
}

func ValidateStatusAnnotation(annotations map[string]string) *field.Error {
if v, ok := annotations[helpers.StatusMetadataKey]; ok {
switch v {
case helpers.Initializing, helpers.Ready, helpers.Completed, helpers.Incomplete, helpers.Unauthorize, helpers.MissingRuntime, helpers.TooLarge:
return nil
default:
return field.Invalid(field.NewPath("metadata").Child("annotations").Child(helpers.StatusMetadataKey), v, "invalid value")
}
}

return nil
}
Loading

0 comments on commit c8186a0

Please sign in to comment.