grafana/apps/provisioning/pkg/connection/factory.go
Roberto Jiménez Sánchez d0b7d1007d
Provisioning: Make title mandatory for Connection resources (#117003)
* Make title mandatory in spec

* Add mandatory title validation for Connection resources

- Add validation in factory.Validate() to require non-empty title field
- Add unit tests for title validation (with and without title)
- Update all existing tests to include title field in Connection specs
- Fixed 35 connection objects across 4 integration test files

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 14:00:38 +00:00

145 lines
3.8 KiB
Go

package connection
import (
"context"
"fmt"
"sort"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
//go:generate mockery --name=Extra --structname=MockExtra --inpackage --filename=extra_mock.go --with-expecter
type Extra interface {
Type() provisioning.ConnectionType
Build(ctx context.Context, r *provisioning.Connection) (Connection, error)
Mutate(ctx context.Context, obj runtime.Object) error
Validate(ctx context.Context, obj runtime.Object) field.ErrorList
}
//go:generate mockery --name=Factory --structname=MockFactory --inpackage --filename=factory_mock.go --with-expecter
type Factory interface {
Types() []provisioning.ConnectionType
Build(ctx context.Context, r *provisioning.Connection) (Connection, error)
Mutate(ctx context.Context, obj runtime.Object) error
Validate(ctx context.Context, obj runtime.Object) field.ErrorList
}
type factory struct {
extras map[provisioning.ConnectionType]Extra
enabled map[provisioning.ConnectionType]struct{}
}
func ProvideFactory(enabled map[provisioning.ConnectionType]struct{}, extras []Extra) (Factory, error) {
f := &factory{
enabled: enabled,
extras: make(map[provisioning.ConnectionType]Extra, len(extras)),
}
for _, e := range extras {
if _, exists := f.extras[e.Type()]; exists {
return nil, fmt.Errorf("connection type %q is already registered", e.Type())
}
f.extras[e.Type()] = e
}
return f, nil
}
func (f *factory) Types() []provisioning.ConnectionType {
var types []provisioning.ConnectionType
for t := range f.enabled {
if _, exists := f.extras[t]; exists {
types = append(types, t)
}
}
sort.Slice(types, func(i, j int) bool {
return string(types[i]) < string(types[j])
})
return types
}
func (f *factory) Build(ctx context.Context, c *provisioning.Connection) (Connection, error) {
for _, e := range f.extras {
if e.Type() == c.Spec.Type {
if _, enabled := f.enabled[e.Type()]; !enabled {
return nil, fmt.Errorf("connection type %q is not enabled", e.Type())
}
return e.Build(ctx, c)
}
}
return nil, fmt.Errorf("connection type %q is not supported", c.Spec.Type)
}
func (f *factory) Mutate(ctx context.Context, obj runtime.Object) error {
for _, e := range f.extras {
if err := e.Mutate(ctx, obj); err != nil {
return err
}
}
return nil
}
func (f *factory) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
var list field.ErrorList
conn, ok := obj.(*provisioning.Connection)
if !ok {
return list
}
// Validate title is required
if conn.Spec.Title == "" {
list = append(list, field.Required(
field.NewPath("spec", "title"),
"title is required",
))
}
// Check if connection type is supported
var foundExtra Extra
for _, e := range f.extras {
if e.Type() == conn.Spec.Type {
foundExtra = e
break
}
}
if foundExtra == nil {
// Return error message matching Build() error format: "connection type %q is not supported"
list = append(list, field.Invalid(
field.NewPath("spec", "type"),
conn.Spec.Type,
fmt.Sprintf("connection type %q is not supported", conn.Spec.Type),
))
// Return early if type is not supported - no point validating further
return list
}
// Check if connection type is enabled
if _, enabled := f.enabled[conn.Spec.Type]; !enabled {
// Return error message matching Build() error format: "connection type %q is not enabled"
list = append(list, field.Invalid(
field.NewPath("spec", "type"),
conn.Spec.Type,
fmt.Sprintf("connection type %q is not enabled", conn.Spec.Type),
))
// Return early if type is not enabled - no point validating further
return list
}
// Validate using registered extras
for _, e := range f.extras {
list = append(list, e.Validate(ctx, obj)...)
}
return list
}
var (
_ Factory = (*factory)(nil)
)