add some tests for validating datasource stacks structure

This commit is contained in:
Dafydd
2025-12-04 11:03:41 +00:00
parent 3ee834922b
commit fd31f087ee
6 changed files with 82 additions and 156 deletions
@@ -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{
@@ -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<string, string>;
annotations?: Record<string, string>;
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;
}
@@ -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<string, string>;
}
export const defaultMetadata = (): Metadata => ({
updateTimestamp: "",
createdBy: "",
uid: "",
creationTimestamp: "",
finalizers: [],
resourceVersion: "",
generation: 0,
updatedBy: "",
labels: {},
});
@@ -1,53 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
export type TemplateSpec = Record<string, DataSourceTemplateSpec>;
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<string, DataSourceRef>;
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: [],
});
@@ -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())
}
}
}
@@ -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 {