diff --git a/internal/kubernetes/watcher/error.go b/internal/kubernetes/watcher/error.go new file mode 100644 index 000000000..3ecb3a6d3 --- /dev/null +++ b/internal/kubernetes/watcher/error.go @@ -0,0 +1,65 @@ +package watcher + +import ( + "fmt" + "strings" +) + +type ErrorNotFound struct { + Cluster string + Namespace string + Name string +} + +func (e *ErrorNotFound) Error() string { + return "not found: " + e.Cluster + "/" + e.Namespace + "/" + e.Name +} + +func (e *ErrorNotFound) GraphError() string { + return "Resource not found: " + e.Cluster + "/" + e.Namespace + "/" + e.Name +} + +func (e *ErrorNotFound) As(v any) bool { + if _, ok := v.(*ErrorNotFound); ok { + return true + } + + return false +} + +func (e *ErrorNotFound) Is(v error) bool { + if _, ok := v.(*ErrorNotFound); ok { + return true + } + + return false +} + +type ErrorUnknownEnvironment struct { + Environment string + Valid []string +} + +func (e *ErrorUnknownEnvironment) Error() string { + return "unknown environment: " + e.Environment +} + +func (e *ErrorUnknownEnvironment) GraphError() string { + return fmt.Sprintf("Unknown environment %q. Valid values are [%s]", e.Environment, strings.Join(e.Valid, ", ")) +} + +func (e *ErrorUnknownEnvironment) As(v any) bool { + if _, ok := v.(*ErrorUnknownEnvironment); ok { + return true + } + + return false +} + +func (e *ErrorUnknownEnvironment) Is(v error) bool { + if _, ok := v.(*ErrorUnknownEnvironment); ok { + return true + } + + return false +} diff --git a/internal/kubernetes/watcher/filter.go b/internal/kubernetes/watcher/filter.go index 3c0b008af..d36484544 100644 --- a/internal/kubernetes/watcher/filter.go +++ b/internal/kubernetes/watcher/filter.go @@ -1,36 +1,8 @@ package watcher -import "k8s.io/apimachinery/pkg/labels" - -type ErrorNotFound struct { - Cluster string - Namespace string - Name string -} - -func (e *ErrorNotFound) Error() string { - return "not found: " + e.Cluster + "/" + e.Namespace + "/" + e.Name -} - -func (e *ErrorNotFound) GraphError() string { - return "Resource not found: " + e.Cluster + "/" + e.Namespace + "/" + e.Name -} - -func (e *ErrorNotFound) As(v any) bool { - if _, ok := v.(*ErrorNotFound); ok { - return true - } - - return false -} - -func (e *ErrorNotFound) Is(v error) bool { - if _, ok := v.(*ErrorNotFound); ok { - return true - } - - return false -} +import ( + "k8s.io/apimachinery/pkg/labels" +) type EnvironmentWrapper[T Object] struct { Cluster string diff --git a/internal/kubernetes/watcher/watcher.go b/internal/kubernetes/watcher/watcher.go index 2f1bd0dd0..32dfeca45 100644 --- a/internal/kubernetes/watcher/watcher.go +++ b/internal/kubernetes/watcher/watcher.go @@ -2,7 +2,6 @@ package watcher import ( "context" - "fmt" "slices" "strings" @@ -54,13 +53,14 @@ type watcherSettings struct { type WatcherHook[T Object] func(cluster string, obj T) type Watcher[T Object] struct { - watchers []*clusterWatcher[T] - log logrus.FieldLogger - resourceCounter metric.Int64UpDownCounter - watchedType string - onAdd WatcherHook[T] - onUpdate WatcherHook[T] - onRemove WatcherHook[T] + watchers []*clusterWatcher[T] + environmentNames []string + log logrus.FieldLogger + resourceCounter metric.Int64UpDownCounter + watchedType string + onAdd WatcherHook[T] + onUpdate WatcherHook[T] + onRemove WatcherHook[T] } func newWatcher[T Object](mgr *Manager, obj T, settings *watcherSettings, log logrus.FieldLogger) *Watcher[T] { @@ -69,15 +69,16 @@ func newWatcher[T Object](mgr *Manager, obj T, settings *watcherSettings, log lo resourceCounter: mgr.resourceCounter, } for cluster, client := range mgr.managers { - cluster = environmentmapper.EnvironmentName(cluster) - watcher, gvr := newClusterWatcher(client, cluster, w, obj, settings, log.WithField("cluster", cluster)) + environmentName := environmentmapper.EnvironmentName(cluster) + watcher, gvr := newClusterWatcher(client, environmentName, w, obj, settings, log.WithField("cluster", environmentName)) if !watcher.isRegistered { continue } w.watchedType = gvr.String() w.watchers = append(w.watchers, watcher) - mgr.addCacheSync(cluster, gvr.String(), watcher.informer.Informer().HasSynced) + w.environmentNames = append(w.environmentNames, environmentName) + mgr.addCacheSync(environmentName, gvr.String(), watcher.informer.Informer().HasSynced) } return w } @@ -297,7 +298,7 @@ func (w *Watcher[T]) ImpersonatedClient(ctx context.Context, cluster string, opt } } - return nil, fmt.Errorf("no watcher for cluster %s", cluster) + return nil, w.unknownEnvironmentError(cluster) } func (w *Watcher[T]) ImpersonatedClientWithNamespace(ctx context.Context, cluster, namespace string, opts ...ImpersonatedClientOption) (dynamic.ResourceInterface, error) { @@ -316,7 +317,11 @@ func (w *Watcher[T]) SystemAuthenticatedClient(ctx context.Context, cluster stri } } - return nil, fmt.Errorf("no watcher for cluster %s", cluster) + return nil, w.unknownEnvironmentError(cluster) +} + +func (w *Watcher[T]) unknownEnvironmentError(cluster string) error { + return &ErrorUnknownEnvironment{Environment: cluster, Valid: w.environmentNames} } func (w *Watcher[T]) OnRemove(fn WatcherHook[T]) { diff --git a/internal/persistence/aivencredentials/queries.go b/internal/persistence/aivencredentials/queries.go index f4c858490..8729aec1d 100644 --- a/internal/persistence/aivencredentials/queries.go +++ b/internal/persistence/aivencredentials/queries.go @@ -5,6 +5,8 @@ import ( "crypto/sha256" "encoding/base64" "fmt" + "maps" + "slices" "strconv" "strings" "time" @@ -100,7 +102,8 @@ func getClient(ctx context.Context, environmentName string) (dynamic.Interface, clusterName := environmentmapper.ClusterName(environmentName) client, ok := l.dynamicClients[clusterName] if !ok { - return nil, fmt.Errorf("unknown environment: %s", environmentName) + valid := strings.Join(slices.Collect(maps.Keys(l.dynamicClients)), ", ") + return nil, apierror.Errorf("Unknown environment %q. Valid values are [%s]", environmentName, valid) } return client, nil }