From fd31f087eed8f6a3776dd06904758de18cd10f98 Mon Sep 17 00:00:00 2001 From: Dafydd Date: Thu, 4 Dec 2025 11:03:41 +0000 Subject: [PATCH] add some tests for validating datasource stacks structure --- .../pkg/apis/collections/v1alpha1/register.go | 2 +- .../v1alpha1/datasources_object_gen.ts | 47 -------------- .../v1alpha1/types.metadata.gen.ts | 30 --------- .../datasources/v1alpha1/types.spec.gen.ts | 53 --------------- .../apis/collections/datasources_validator.go | 41 +++++------- .../collections/datasources_validator_test.go | 65 +++++++++++++++++++ 6 files changed, 82 insertions(+), 156 deletions(-) delete mode 100644 apps/collections/plugin/src/generated/datasources/v1alpha1/datasources_object_gen.ts delete mode 100644 apps/collections/plugin/src/generated/datasources/v1alpha1/types.metadata.gen.ts delete mode 100644 apps/collections/plugin/src/generated/datasources/v1alpha1/types.spec.gen.ts diff --git a/apps/collections/pkg/apis/collections/v1alpha1/register.go b/apps/collections/pkg/apis/collections/v1alpha1/register.go index e75dada6bc0..f92a5cbdf20 100644 --- a/apps/collections/pkg/apis/collections/v1alpha1/register.go +++ b/apps/collections/pkg/apis/collections/v1alpha1/register.go @@ -33,7 +33,7 @@ var StarsResourceInfo = utils.NewResourceInfo(APIGroup, APIVersion, ) var DatasourceStacksResourceInfo = utils.NewResourceInfo(APIGroup, APIVersion, - "datasourcestacks", "datasourcestack", "DataSourceStacks", + "datasourcestacks", "datasourcestack", "DataSourceStack", func() runtime.Object { return &DataSourceStack{} }, func() runtime.Object { return &DataSourceStackList{} }, utils.TableColumns{ diff --git a/apps/collections/plugin/src/generated/datasources/v1alpha1/datasources_object_gen.ts b/apps/collections/plugin/src/generated/datasources/v1alpha1/datasources_object_gen.ts deleted file mode 100644 index 1d556466dfd..00000000000 --- a/apps/collections/plugin/src/generated/datasources/v1alpha1/datasources_object_gen.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file was generated by grafana-app-sdk. DO NOT EDIT. - */ -import { Spec } from './types.spec.gen'; - -export interface Metadata { - name: string; - namespace: string; - generateName?: string; - selfLink?: string; - uid?: string; - resourceVersion?: string; - generation?: number; - creationTimestamp?: string; - deletionTimestamp?: string; - deletionGracePeriodSeconds?: number; - labels?: Record; - annotations?: Record; - ownerReferences?: OwnerReference[]; - finalizers?: string[]; - managedFields?: ManagedFieldsEntry[]; -} - -export interface OwnerReference { - apiVersion: string; - kind: string; - name: string; - uid: string; - controller?: boolean; - blockOwnerDeletion?: boolean; -} - -export interface ManagedFieldsEntry { - manager?: string; - operation?: string; - apiVersion?: string; - time?: string; - fieldsType?: string; - subresource?: string; -} - -export interface Datasources { - kind: string; - apiVersion: string; - metadata: Metadata; - spec: Spec; -} diff --git a/apps/collections/plugin/src/generated/datasources/v1alpha1/types.metadata.gen.ts b/apps/collections/plugin/src/generated/datasources/v1alpha1/types.metadata.gen.ts deleted file mode 100644 index 4377f3c1d08..00000000000 --- a/apps/collections/plugin/src/generated/datasources/v1alpha1/types.metadata.gen.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -// metadata contains embedded CommonMetadata and can be extended with custom string fields -// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here -// without external reference as using the CommonMetadata reference breaks thema codegen. -export interface Metadata { - updateTimestamp: string; - createdBy: string; - uid: string; - creationTimestamp: string; - deletionTimestamp?: string; - finalizers: string[]; - resourceVersion: string; - generation: number; - updatedBy: string; - labels: Record; -} - -export const defaultMetadata = (): Metadata => ({ - updateTimestamp: "", - createdBy: "", - uid: "", - creationTimestamp: "", - finalizers: [], - resourceVersion: "", - generation: 0, - updatedBy: "", - labels: {}, -}); - diff --git a/apps/collections/plugin/src/generated/datasources/v1alpha1/types.spec.gen.ts b/apps/collections/plugin/src/generated/datasources/v1alpha1/types.spec.gen.ts deleted file mode 100644 index 10d3d0e5000..00000000000 --- a/apps/collections/plugin/src/generated/datasources/v1alpha1/types.spec.gen.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -export type TemplateSpec = Record; - -export const defaultTemplateSpec = (): TemplateSpec => ({}); - -export interface DataSourceTemplateSpec { - // type - group: string; - // variable name / display name - name: string; -} - -export const defaultDataSourceTemplateSpec = (): DataSourceTemplateSpec => ({ - group: "", - name: "", -}); - -export interface Mode { - name: string; - uid: string; - definition: ModeSpec; -} - -export const defaultMode = (): Mode => ({ - name: "", - uid: "", - definition: defaultModeSpec(), -}); - -export type ModeSpec = Record; - -export const defaultModeSpec = (): ModeSpec => ({}); - -export interface DataSourceRef { - // grafana data source uid - name: string; -} - -export const defaultDataSourceRef = (): DataSourceRef => ({ - name: "", -}); - -export interface Spec { - template: TemplateSpec; - modes: Mode[]; -} - -export const defaultSpec = (): Spec => ({ - template: defaultTemplateSpec(), - modes: [], -}); - diff --git a/pkg/registry/apis/collections/datasources_validator.go b/pkg/registry/apis/collections/datasources_validator.go index f02bb3f6e47..187523b1335 100644 --- a/pkg/registry/apis/collections/datasources_validator.go +++ b/pkg/registry/apis/collections/datasources_validator.go @@ -18,17 +18,13 @@ func GetDatasourceStacksValidator() builder.APIGroupValidation { } func (v *DatasourceStacksValidator) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { - if a.GetKind().Kind != collections.DatasourceStacksResourceInfo.GroupVersionKind().Kind { - return nil - } - obj := a.GetObject() - if obj == nil { - return fmt.Errorf("object is nil (%s %s)", a.GetName(), a.GetKind().GroupVersion().String()) - } - operation := a.GetOperation() + if operation == admission.Connect { + return fmt.Errorf("Connect operation is not allowed (%s %s)", a.GetName(), a.GetKind().GroupVersion().String()) + } + if operation != admission.Create && operation != admission.Update { return nil } @@ -40,33 +36,28 @@ func (v *DatasourceStacksValidator) Validate(ctx context.Context, a admission.At // get the keys from the template template := cast.Spec.Template - templateKeys := map[string]bool{} - // keys must be unique - for key := range template { - if _, ok := templateKeys[key]; ok { - return fmt.Errorf("template keys must be unique. key '%s' already exists (%s %s)", key, a.GetName(), a.GetKind().GroupVersion().String()) - } - templateKeys[key] = true - } - - // template names must be unique templateNames := map[string]bool{} - for name := range template { - if _, ok := templateNames[name]; ok { - return fmt.Errorf("template names must be unique. name '%s' already exists (%s %s)", name, a.GetName(), a.GetKind().GroupVersion().String()) + for _, item := range template { + // template items cannot be empty + if item.Group == "" || item.Name == "" { + return fmt.Errorf("template items cannot be empty (%s %s)", a.GetName(), a.GetKind().GroupVersion().String()) } - templateNames[name] = true + // template names must be unique + if _, exists := templateNames[item.Name]; exists { + return fmt.Errorf("template item names must be unique. name '%s' already exists (%s %s)", item.Name, a.GetName(), a.GetKind().GroupVersion().String()) + } + templateNames[item.Name] = true } // for each mode, check that the keys are in the template modes := cast.Spec.Modes - // if a key is not in the template, return an error for _, mode := range modes { for key := range mode.Definition { - if _, ok := templateKeys[key]; !ok { - return fmt.Errorf("key %s is not in the DataSourceStack template. The template keys are %v (%s %s)", key, templateKeys, a.GetName(), a.GetKind().GroupVersion().String()) + // if a key is not in the template, return an error + if _, ok := template[key]; !ok { + return fmt.Errorf("key '%s' is not in the DataSourceStack template (%s %s)", key, a.GetName(), a.GetKind().GroupVersion().String()) } } } diff --git a/pkg/registry/apis/collections/datasources_validator_test.go b/pkg/registry/apis/collections/datasources_validator_test.go index 238eb5490e7..77b3d1644cd 100644 --- a/pkg/registry/apis/collections/datasources_validator_test.go +++ b/pkg/registry/apis/collections/datasources_validator_test.go @@ -29,6 +29,71 @@ func TestDataSourceValidator_Validate(t *testing.T) { object: &collectionsv1alpha1.Stars{}, expectError: false, }, + { + name: "should return error for Connect operation", + operation: admission.Connect, + object: &collectionsv1alpha1.DataSourceStack{}, + expectError: true, + }, + { + name: "template items cannot be empty", + operation: admission.Create, + object: &collectionsv1alpha1.DataSourceStack{ + Spec: collectionsv1alpha1.DataSourceStackSpec{ + Template: collectionsv1alpha1.DataSourceStackTemplateSpec{ + "key1": collectionsv1alpha1.DataSourceStackDataSourceStackTemplateItem{}, + }, + }, + }, + expectError: true, + errorMsg: "template items cannot be empty (test-datasourcestack collections.grafana.app/v1alpha1)", + }, + { + name: "template item name keys must be unique", + operation: admission.Create, + object: &collectionsv1alpha1.DataSourceStack{ + Spec: collectionsv1alpha1.DataSourceStackSpec{ + Template: collectionsv1alpha1.DataSourceStackTemplateSpec{ + "key1": collectionsv1alpha1.DataSourceStackDataSourceStackTemplateItem{ + Name: "foo", + Group: "foo.grafana", + }, + "key2": collectionsv1alpha1.DataSourceStackDataSourceStackTemplateItem{ + Name: "foo", + Group: "foo.grafana", + }, + }, + }, + }, + expectError: true, + errorMsg: "template item names must be unique. name 'foo' already exists (test-datasourcestack collections.grafana.app/v1alpha1)", + }, + { + name: "mode keys must exist in the template", + operation: admission.Create, + object: &collectionsv1alpha1.DataSourceStack{ + Spec: collectionsv1alpha1.DataSourceStackSpec{ + Template: collectionsv1alpha1.DataSourceStackTemplateSpec{ + "key1": collectionsv1alpha1.DataSourceStackDataSourceStackTemplateItem{ + Name: "foo", + Group: "foo.grafana", + }, + }, + Modes: []collectionsv1alpha1.DataSourceStackModeSpec{ + { + Name: "prod", + Definition: collectionsv1alpha1.DataSourceStackMode{ + "notintemplate": collectionsv1alpha1.DataSourceStackModeItem{ + DataSourceRef: "foo", + }, + }, + }, + }, + }, + }, + expectError: true, + errorMsg: "key 'notintemplate' is not in the DataSourceStack template (test-datasourcestack collections.grafana.app/v1alpha1)", + }, } for _, tt := range tests {