Skip to content

Commit

Permalink
refactor based on #1602
Browse files Browse the repository at this point in the history
  • Loading branch information
tbavelier committed Jan 20, 2025
1 parent 98ae1c6 commit b953307
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 149 deletions.
34 changes: 34 additions & 0 deletions internal/controller/datadoggenericresource/notebooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,44 @@ import (
"strconv"

"github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1"
"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/DataDog/datadog-api-client-go/v2/api/datadogV1"
)

type NotebookHandler struct{}

func (h *NotebookHandler) createResourcefunc(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
createdNotebook, err := createNotebook(r.datadogAuth, r.datadogNotebooksClient, instance)
if err != nil {
logger.Error(err, "error creating notebook")
updateErrStatus(status, now, v1alpha1.DatadogSyncStatusCreateError, "CreatingCustomResource", err)
return err
}
logger.Info("created a new notebook", "notebook Id", createdNotebook.Data.GetId())
status.Id = notebookInt64ToString(createdNotebook.Data.GetId())
createdTime := metav1.NewTime(*createdNotebook.Data.GetAttributes().Created)
status.Created = &createdTime
status.LastForceSyncTime = &createdTime
status.Creator = *createdNotebook.Data.GetAttributes().Author.Handle
status.SyncStatus = v1alpha1.DatadogSyncStatusOK
status.CurrentHash = hash
return nil
}

func (h *NotebookHandler) getResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := getNotebook(r.datadogAuth, r.datadogNotebooksClient, instance.Status.Id)
return err
}
func (h *NotebookHandler) updateResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := updateNotebook(r.datadogAuth, r.datadogNotebooksClient, instance)
return err
}
func (h *NotebookHandler) deleteResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return deleteNotebook(r.datadogAuth, r.datadogNotebooksClient, instance.Status.Id)
}

func getNotebook(auth context.Context, client *datadogV1.NotebooksApi, notebookStringID string) (datadogV1.NotebookResponse, error) {
notebookID, err := notebookStringToInt64(notebookStringID)
if err != nil {
Expand Down
14 changes: 14 additions & 0 deletions internal/controller/datadoggenericresource/resource_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package datadoggenericresource

import (
"github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1"
"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type ResourceHandler interface {
createResourcefunc(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error
getResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error
updateResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error
deleteResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error
}
52 changes: 52 additions & 0 deletions internal/controller/datadoggenericresource/synthetics.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,62 @@ import (
"encoding/json"

"github.com/DataDog/datadog-operator/api/datadoghq/v1alpha1"
"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/DataDog/datadog-api-client-go/v2/api/datadogV1"
)

type SyntheticsAPITestHandler struct{}

func (h *SyntheticsAPITestHandler) createResourcefunc(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
createdTest, err := createSyntheticsAPITest(r.datadogAuth, r.datadogSyntheticsClient, instance)
if err != nil {
logger.Error(err, "error creating API test")
updateErrStatus(status, now, v1alpha1.DatadogSyncStatusCreateError, "CreatingCustomResource", err)
return err
}
additionalProperties := createdTest.AdditionalProperties
return updateStatusFromSyntheticsTest(&createdTest, additionalProperties, status, logger, hash)
}

func (h *SyntheticsAPITestHandler) getResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := getSyntheticsTest(r.datadogAuth, r.datadogSyntheticsClient, instance.Status.Id)
return err
}
func (h *SyntheticsAPITestHandler) updateResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := updateSyntheticsAPITest(r.datadogAuth, r.datadogSyntheticsClient, instance)
return err
}
func (h *SyntheticsAPITestHandler) deleteResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return deleteSyntheticTest(r.datadogAuth, r.datadogSyntheticsClient, instance.Status.Id)
}

type SyntheticsBrowserTestHandler struct{}

func (h *SyntheticsBrowserTestHandler) createResourcefunc(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
createdTest, err := createSyntheticBrowserTest(r.datadogAuth, r.datadogSyntheticsClient, instance)
if err != nil {
logger.Error(err, "error creating browser test")
updateErrStatus(status, now, v1alpha1.DatadogSyncStatusCreateError, "CreatingCustomResource", err)
return err
}
additionalProperties := createdTest.AdditionalProperties
return updateStatusFromSyntheticsTest(&createdTest, additionalProperties, status, logger, hash)
}

func (h *SyntheticsBrowserTestHandler) getResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := getSyntheticsTest(r.datadogAuth, r.datadogSyntheticsClient, instance.Status.Id)
return err
}
func (h *SyntheticsBrowserTestHandler) updateResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := updateSyntheticsBrowserTest(r.datadogAuth, r.datadogSyntheticsClient, instance)
return err
}
func (h *SyntheticsBrowserTestHandler) deleteResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return deleteSyntheticTest(r.datadogAuth, r.datadogSyntheticsClient, instance.Status.Id)
}

// Synthetic tests (encompass browser and API tests): get
func getSyntheticsTest(auth context.Context, client *datadogV1.SyntheticsApi, testID string) (datadogV1.SyntheticsTestDetails, error) {
test, _, err := client.GetTest(auth, testID)
Expand Down
170 changes: 43 additions & 127 deletions internal/controller/datadoggenericresource/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,125 +23,57 @@ const (
operationUpdate operation = "update"
)

type apiHandlerKey struct {
resourceType v1alpha1.SupportedResourcesType
op operation
type MockHandler struct{}

func (h *MockHandler) createResourcefunc(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
status.Id = "mock-id"
status.Created = &now
status.LastForceSyncTime = &now
status.Creator = "mock-creator"
status.SyncStatus = v1alpha1.DatadogSyncStatusOK
status.CurrentHash = hash
return nil
}

// Delete, Get and Update operations share the same signature
type apiHandlerFunc func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error

var apiHandlers = map[apiHandlerKey]apiHandlerFunc{
{v1alpha1.SyntheticsBrowserTest, operationGet}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := getSyntheticsTest(r.datadogAuth, r.datadogSyntheticsClient, instance.Status.Id)
return err
},
{v1alpha1.SyntheticsBrowserTest, operationUpdate}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := updateSyntheticsBrowserTest(r.datadogAuth, r.datadogSyntheticsClient, instance)
return err
},
{v1alpha1.SyntheticsBrowserTest, operationDelete}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return deleteSyntheticTest(r.datadogAuth, r.datadogSyntheticsClient, instance.Status.Id)
},
{v1alpha1.SyntheticsAPITest, operationGet}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := getSyntheticsTest(r.datadogAuth, r.datadogSyntheticsClient, instance.Status.Id)
return err
},
{v1alpha1.SyntheticsAPITest, operationUpdate}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := updateSyntheticsAPITest(r.datadogAuth, r.datadogSyntheticsClient, instance)
return err
},
{v1alpha1.SyntheticsAPITest, operationDelete}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return deleteSyntheticTest(r.datadogAuth, r.datadogSyntheticsClient, instance.Status.Id)
},
{v1alpha1.Notebook, operationGet}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := getNotebook(r.datadogAuth, r.datadogNotebooksClient, instance.Status.Id)
return err
},
{v1alpha1.Notebook, operationUpdate}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
_, err := updateNotebook(r.datadogAuth, r.datadogNotebooksClient, instance)
return err
},
{v1alpha1.Notebook, operationDelete}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return deleteNotebook(r.datadogAuth, r.datadogNotebooksClient, instance.Status.Id)
},
{mockSubresource, operationGet}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return nil
},
{mockSubresource, operationUpdate}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return nil
},
{mockSubresource, operationDelete}: func(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return nil
},
func (h *MockHandler) getResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return nil
}
func (h *MockHandler) updateResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return nil
}
func (h *MockHandler) deleteResourcefunc(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return nil
}

// Common handler executor (delete, get and update)
func executeHandler(operation operation, r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
key := apiHandlerKey{resourceType: instance.Spec.Type, op: operation}
if handler, found := apiHandlers[key]; found {
return handler(r, instance)
}
return unsupportedInstanceType(instance)
func apiDelete(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return getHandler(instance.Spec.Type).deleteResourcefunc(r, instance)
}

// Create is handled separately due to the dynamic signature and need to extract/update status based on the returned struct
type createHandlerFunc func(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error
func apiGet(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return getHandler(instance.Spec.Type).getResourcefunc(r, instance)
}

var createHandlers = map[v1alpha1.SupportedResourcesType]createHandlerFunc{
v1alpha1.SyntheticsBrowserTest: func(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
createdTest, err := createSyntheticBrowserTest(r.datadogAuth, r.datadogSyntheticsClient, instance)
if err != nil {
logger.Error(err, "error creating browser test")
updateErrStatus(status, now, v1alpha1.DatadogSyncStatusCreateError, "CreatingCustomResource", err)
return err
}
additionalProperties := createdTest.AdditionalProperties
return updateStatusFromSyntheticsTest(&createdTest, additionalProperties, status, logger, hash)
},
v1alpha1.SyntheticsAPITest: func(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
createdTest, err := createSyntheticsAPITest(r.datadogAuth, r.datadogSyntheticsClient, instance)
if err != nil {
logger.Error(err, "error creating API test")
updateErrStatus(status, now, v1alpha1.DatadogSyncStatusCreateError, "CreatingCustomResource", err)
return err
}
additionalProperties := createdTest.AdditionalProperties
return updateStatusFromSyntheticsTest(&createdTest, additionalProperties, status, logger, hash)
},
v1alpha1.Notebook: func(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
createdNotebook, err := createNotebook(r.datadogAuth, r.datadogNotebooksClient, instance)
if err != nil {
logger.Error(err, "error creating notebook")
updateErrStatus(status, now, v1alpha1.DatadogSyncStatusCreateError, "CreatingCustomResource", err)
return err
}
logger.Info("created a new notebook", "notebook Id", createdNotebook.Data.GetId())
status.Id = notebookInt64ToString(createdNotebook.Data.GetId())
createdTime := metav1.NewTime(*createdNotebook.Data.GetAttributes().Created)
status.Created = &createdTime
status.LastForceSyncTime = &createdTime
status.Creator = *createdNotebook.Data.GetAttributes().Author.Handle
status.SyncStatus = v1alpha1.DatadogSyncStatusOK
status.CurrentHash = hash
return nil
},
mockSubresource: func(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
status.Id = "mock-id"
status.Created = &now
status.LastForceSyncTime = &now
status.Creator = "mock-creator"
status.SyncStatus = v1alpha1.DatadogSyncStatusOK
status.CurrentHash = hash
return nil
},
func apiUpdate(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return getHandler(instance.Spec.Type).updateResourcefunc(r, instance)
}

func apiCreateAndUpdateStatus(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
return getHandler(instance.Spec.Type).createResourcefunc(r, logger, instance, status, now, hash)
}

func executeCreateHandler(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
if handler, found := createHandlers[instance.Spec.Type]; found {
return handler(r, logger, instance, status, now, hash)
func getHandler(resourceType v1alpha1.SupportedResourcesType) ResourceHandler {
switch resourceType {
case v1alpha1.Notebook:
return &NotebookHandler{}
case v1alpha1.SyntheticsAPITest:
return &SyntheticsAPITestHandler{}
case v1alpha1.SyntheticsBrowserTest:
return &SyntheticsBrowserTestHandler{}
case mockSubresource:
return &MockHandler{}
default:
panic(unsupportedInstanceType(resourceType))
}
return unsupportedInstanceType(instance)
}

func translateClientError(err error, msg string) error {
Expand All @@ -162,24 +94,8 @@ func translateClientError(err error, msg string) error {
return fmt.Errorf(msg+": %w", err)
}

func unsupportedInstanceType(instance *v1alpha1.DatadogGenericResource) error {
return fmt.Errorf("unsupported type: %s", instance.Spec.Type)
}

func apiDelete(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return executeHandler(operationDelete, r, instance)
}

func apiGet(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return executeHandler(operationGet, r, instance)
}

func apiUpdate(r *Reconciler, instance *v1alpha1.DatadogGenericResource) error {
return executeHandler(operationUpdate, r, instance)
}

func apiCreateAndUpdateStatus(r *Reconciler, logger logr.Logger, instance *v1alpha1.DatadogGenericResource, status *v1alpha1.DatadogGenericResourceStatus, now metav1.Time, hash string) error {
return executeCreateHandler(r, logger, instance, status, now, hash)
func unsupportedInstanceType(resourceType v1alpha1.SupportedResourcesType) error {
return fmt.Errorf("unsupported type: %s", resourceType)
}

func updateStatusFromSyntheticsTest(createdTest interface{ GetPublicId() string }, additionalProperties map[string]interface{}, status *v1alpha1.DatadogGenericResourceStatus, logger logr.Logger, hash string) error {
Expand Down
27 changes: 5 additions & 22 deletions internal/controller/datadoggenericresource/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func Test_executeHandler(t *testing.T) {
mockReconciler := &Reconciler{}
instance := &v1alpha1.DatadogGenericResource{
Spec: v1alpha1.DatadogGenericResourceSpec{
Type: mockSubresource,
},
}

// Valid operation and subresource case
err := executeHandler(operationGet, mockReconciler, instance)
assert.NoError(t, err)

// Valid operation and invalid subresource case
instance.Spec.Type = "unsupportedType"
err = executeHandler(operationGet, mockReconciler, instance)
assert.EqualError(t, err, "unsupported type: unsupportedType")
}

func Test_executeCreateHandler(t *testing.T) {
func Test_apiCreateAndUpdateStatus(t *testing.T) {
mockReconciler := &Reconciler{}
logger := &logr.Logger{}
instance := &v1alpha1.DatadogGenericResource{
Expand All @@ -45,13 +27,14 @@ func Test_executeCreateHandler(t *testing.T) {
status := &v1alpha1.DatadogGenericResourceStatus{}

// Valid subresource case
err := executeCreateHandler(mockReconciler, *logger, instance, status, metav1.Now(), "test-hash")
err := apiCreateAndUpdateStatus(mockReconciler, *logger, instance, status, metav1.Now(), "test-hash")
assert.NoError(t, err)

// Invalid subresource case
instance.Spec.Type = "unsupportedType"
err = executeCreateHandler(mockReconciler, *logger, instance, status, metav1.Now(), "test-hash")
assert.EqualError(t, err, "unsupported type: unsupportedType")
assert.PanicsWithError(t, "unsupported type: unsupportedType", func() {
apiCreateAndUpdateStatus(mockReconciler, *logger, instance, status, metav1.Now(), "test-hash")
})
}

func Test_apiGet(t *testing.T) {
Expand Down

0 comments on commit b953307

Please sign in to comment.