diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index e54a8c2fc28..39161b622cd 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -123,8 +123,9 @@ type APIBuilder struct { extras []Extra extraWorkers []jobs.Worker - restConfigGetter func(context.Context) (*clientrest.Config, error) - registry prometheus.Registerer + restConfigGetter func(context.Context) (*clientrest.Config, error) + registry prometheus.Registerer + namespaceLimitsProvider NamespaceLimitsProvider } // NewAPIBuilder creates an API builder. @@ -151,6 +152,7 @@ func NewAPIBuilder( registry prometheus.Registerer, newStandaloneClientFactoryFunc func(loopbackConfigProvider apiserver.RestConfigProvider) resources.ClientFactory, // optional, only used for standalone apiserver useExclusivelyAccessCheckerForAuthz bool, + namespaceLimitsProvider NamespaceLimitsProvider, ) *APIBuilder { var clients resources.ClientFactory if newStandaloneClientFactoryFunc != nil { @@ -194,6 +196,7 @@ func NewAPIBuilder( registry: registry, validator: repository.NewValidator(minSyncInterval, allowedTargets, allowImageRendering), useExclusivelyAccessCheckerForAuthz: useExclusivelyAccessCheckerForAuthz, + namespaceLimitsProvider: namespaceLimitsProvider, } for _, builder := range extraBuilders { @@ -268,6 +271,7 @@ func RegisterAPIService( allowedTargets = append(allowedTargets, provisioning.SyncTargetType(target)) } + limitsProvider := NewFixedNamespaceLimitsProvider(10) builder := NewAPIBuilder( cfg.DisableControllers, repoFactory, @@ -288,6 +292,7 @@ func RegisterAPIService( reg, nil, false, // TODO: first, test this on the MT side before we enable it by default in ST as well + limitsProvider, ) apiregistration.RegisterAPI(builder) return builder, nil @@ -801,7 +806,7 @@ func invalidRepositoryError(name string, list field.ErrorList) error { } func (b *APIBuilder) VerifyAgainstExistingRepositories(ctx context.Context, cfg *provisioning.Repository) *field.Error { - return VerifyAgainstExistingRepositories(ctx, b.store, cfg) + return VerifyAgainstExistingRepositories(ctx, b.store, cfg, b.namespaceLimitsProvider) } func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartHookFunc, error) { diff --git a/pkg/registry/apis/provisioning/validation.go b/pkg/registry/apis/provisioning/validation.go index ff84872403c..5a40d3ac0c0 100644 --- a/pkg/registry/apis/provisioning/validation.go +++ b/pkg/registry/apis/provisioning/validation.go @@ -20,6 +20,11 @@ type RepositoryLister interface { List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) } +// NamespaceLimitsProvider provides repository limits for a given namespace +type NamespaceLimitsProvider interface { + GetMaxRepositories(ctx context.Context, namespace string) (int, error) +} + // GetRepositoriesInNamespace retrieves all repositories in a given namespace func GetRepositoriesInNamespace(ctx context.Context, store RepositoryLister) ([]provisioning.Repository, error) { var allRepositories []provisioning.Repository @@ -51,7 +56,7 @@ func GetRepositoriesInNamespace(ctx context.Context, store RepositoryLister) ([] } // VerifyAgainstExistingRepositories validates a repository configuration against existing repositories -func VerifyAgainstExistingRepositories(ctx context.Context, store RepositoryLister, cfg *provisioning.Repository) *field.Error { +func VerifyAgainstExistingRepositories(ctx context.Context, store RepositoryLister, cfg *provisioning.Repository, limitsProvider NamespaceLimitsProvider) *field.Error { ctx, _, err := identity.WithProvisioningIdentity(ctx, cfg.Namespace) if err != nil { return &field.Error{Type: field.ErrorTypeInternal, Detail: err.Error()} @@ -115,10 +120,31 @@ func VerifyAgainstExistingRepositories(ctx context.Context, store RepositoryList count++ } } - if count >= 10 { + + maxRepositories, err := limitsProvider.GetMaxRepositories(ctx, cfg.Namespace) + if err != nil { + return &field.Error{Type: field.ErrorTypeInternal, Detail: fmt.Sprintf("Failed to get repository limit: %v", err)} + } + + if count >= maxRepositories { return field.Forbidden(field.NewPath("spec"), - "Maximum number of 10 repositories reached") + fmt.Sprintf("Maximum number of %d repositories reached", maxRepositories)) } return nil } + +// FixedNamespaceLimitsProvider returns a fixed limit for all namespaces +type FixedNamespaceLimitsProvider struct { + limit int +} + +// NewFixedNamespaceLimitsProvider creates a new FixedNamespaceLimitsProvider with the specified limit +func NewFixedNamespaceLimitsProvider(limit int) *FixedNamespaceLimitsProvider { + return &FixedNamespaceLimitsProvider{limit: limit} +} + +// GetMaxRepositories returns the fixed limit for any namespace +func (f *FixedNamespaceLimitsProvider) GetMaxRepositories(ctx context.Context, namespace string) (int, error) { + return f.limit, nil +}