From a3bd4ce2d72de59b1259df7b70ba7937d9c3abc0 Mon Sep 17 00:00:00 2001 From: Armando Ruocco Date: Wed, 7 Aug 2024 13:44:08 +0200 Subject: [PATCH] feat: add `BuildSetStatusResponse` to plugin helper (#47) Signed-off-by: Armando Ruocco Signed-off-by: Leonardo Cecchi Co-authored-by: Leonardo Cecchi --- pkg/pluginhelper/helper.go | 4 +- pkg/pluginhelper/status.go | 70 +++++++++++++++++++++++++++++++++ pkg/pluginhelper/status_test.go | 35 +++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 pkg/pluginhelper/status.go create mode 100644 pkg/pluginhelper/status_test.go diff --git a/pkg/pluginhelper/helper.go b/pkg/pluginhelper/helper.go index 8f266aa..30c6e6c 100644 --- a/pkg/pluginhelper/helper.go +++ b/pkg/pluginhelper/helper.go @@ -182,14 +182,14 @@ func (*Data) DecodeBackup(backupDefinition []byte) (*apiv1.Backup, error) { return &backup, nil } -// GetKind gets the Kubernetes object kind from its JSON representation +// GetKind gets the Kubernetes object kind from its JSON representation. func GetKind(definition []byte) (string, error) { var genericObject struct { Kind string `json:"kind"` } if err := json.Unmarshal(definition, &genericObject); err != nil { - return "", err + return "", fmt.Errorf("while unmarshalling resource definition: %w", err) } return genericObject.Kind, nil diff --git a/pkg/pluginhelper/status.go b/pkg/pluginhelper/status.go new file mode 100644 index 0000000..5090e4c --- /dev/null +++ b/pkg/pluginhelper/status.go @@ -0,0 +1,70 @@ +package pluginhelper + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/cloudnative-pg/cnpg-i/pkg/operator" +) + +// ErrNilObject is used when a nill object is passed to the builder. +var ErrNilObject = errors.New("nil object passed, use NoOpResponse") + +// NotAnObjectError is used when the passed value cannot be represented +// as a JSON object. +type NotAnObjectError struct { + representation []byte +} + +func (err NotAnObjectError) Error() string { + return fmt.Sprintf( + "the passed variable cannot be serialized as a JSON object: %s", + err.representation, + ) +} + +// SetStatusResponseBuilder a SetStatus response builder. +type SetStatusResponseBuilder struct{} + +// NewSetStatusResponseBuilder is an helper that creates the SetStatus endpoint responses. +func NewSetStatusResponseBuilder() *SetStatusResponseBuilder { + return &SetStatusResponseBuilder{} +} + +// NoOpResponse this response will ensure that no changes will be done to the plugin status. +func (s SetStatusResponseBuilder) NoOpResponse() *operator.SetClusterStatusResponse { + return &operator.SetClusterStatusResponse{JsonStatus: nil} +} + +// SetEmptyStatusResponse will set the plugin status to an empty object '{}'. +func (s SetStatusResponseBuilder) SetEmptyStatusResponse() *operator.SetClusterStatusResponse { + b, err := json.Marshal(map[string]string{}) + if err != nil { + panic("JSON mashaller failed for empty map") + } + + return &operator.SetClusterStatusResponse{JsonStatus: b} +} + +// JSONStatusResponse requires a struct or map that can be translated to a JSON object, +// will set the status to the passed object. +func (s SetStatusResponseBuilder) JSONStatusResponse(obj any) (*operator.SetClusterStatusResponse, error) { + if obj == nil { + return nil, ErrNilObject + } + + jsonObject, err := json.Marshal(obj) + if err != nil { + return nil, fmt.Errorf("while marshalling resource definition: %w", err) + } + + var js map[string]interface{} + if err := json.Unmarshal(jsonObject, &js); err != nil { + return nil, NotAnObjectError{representation: jsonObject} + } + + return &operator.SetClusterStatusResponse{ + JsonStatus: jsonObject, + }, nil +} diff --git a/pkg/pluginhelper/status_test.go b/pkg/pluginhelper/status_test.go new file mode 100644 index 0000000..db25b7e --- /dev/null +++ b/pkg/pluginhelper/status_test.go @@ -0,0 +1,35 @@ +package pluginhelper + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("BuildSetStatusResponse", func() { + type test struct { + Name string `json:"string"` + } + + It("should properly form a response with an object, allowing the plugins to set the status", func() { + jsonBody := test{Name: "test"} + b, err := NewSetStatusResponseBuilder().JSONStatusResponse(&jsonBody) + Expect(err).NotTo(HaveOccurred()) + Expect(b.GetJsonStatus()).ToNot(BeEmpty()) + }) + + It("should properly form a response for a 'nil' value, allowing the plugins to do a 'noop'", func() { + b := NewSetStatusResponseBuilder().NoOpResponse() + Expect(b.GetJsonStatus()).To(BeNil()) + }) + + It("should serialize an empty JSONStatus, allowing the plugins to reset its status", func() { + b := NewSetStatusResponseBuilder().SetEmptyStatusResponse() + Expect(b.GetJsonStatus()).ToNot(BeEmpty()) + }) + + It("should return an error if it is an invalid JSON object", func() { + wrongType := 4 + _, err := NewSetStatusResponseBuilder().JSONStatusResponse(&wrongType) + Expect(err).To(HaveOccurred()) + }) +})