Skip to content

Commit e7fab85

Browse files
committed
replace validation hack with CEL expressions
On-behalf-of: @SAP [email protected]
1 parent cbdb583 commit e7fab85

File tree

6 files changed

+246
-92
lines changed

6 files changed

+246
-92
lines changed

deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,12 @@ spec:
354354
resource, like a Secret containing generated credentials. Related objects are
355355
synced along the main resource.
356356
items:
357+
description: |-
358+
RelatedResourceSpec describes a single related resource, which might point to
359+
any number of actual Kubernetes objects.
360+
361+
(in the following rule, group is optional becaue core/v1 is represented by group="")
362+
group is included here because when an identityHash is used, core/v1 cannot possible be targetted
357363
properties:
358364
group:
359365
description: |-
@@ -378,6 +384,9 @@ spec:
378384
379385
Deprecated: Use "Resource" instead. This field is limited to "ConfigMap" and "Secret" and will
380386
be removed in the future. Kind and Resource cannot be specified at the same time.
387+
enum:
388+
- ConfigMap
389+
- Secret
381390
type: string
382391
mutation:
383392
description: |-
@@ -734,6 +743,15 @@ spec:
734743
- object
735744
- origin
736745
type: object
746+
x-kubernetes-validations:
747+
- message: must specify either kind (deprecated) or group, version, resource
748+
rule: has(self.kind) != (has(self.version) || has(self.resource))
749+
- message: resource and version must be configured together or not at all
750+
rule: has(self.resource) == has(self.version)
751+
- message: configuring a group also requires a version and resource
752+
rule: '!has(self.group) || (has(self.resource) && has(self.version))'
753+
- message: identity hashes can only be used with GVRs
754+
rule: '!has(self.identityHash) || (has(self.group) && has(self.version) && has(self.resource))'
737755
type: array
738756
resource:
739757
description: |-

internal/controller/apiexport/controller.go

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package apiexport
1919
import (
2020
"context"
2121
"fmt"
22-
"slices"
2322

2423
"github.com/kcp-dev/logicalcluster/v3"
2524
"go.uber.org/zap"
@@ -30,7 +29,6 @@ import (
3029
"github.com/kcp-dev/api-syncagent/internal/kcp"
3130
"github.com/kcp-dev/api-syncagent/internal/projection"
3231
"github.com/kcp-dev/api-syncagent/internal/resources/reconciling"
33-
"github.com/kcp-dev/api-syncagent/internal/validation"
3432
syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
3533

3634
kcpdevv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
@@ -131,31 +129,20 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
131129
}
132130

133131
func (r *Reconciler) reconcile(ctx context.Context, apiExport *kcpdevv1alpha1.APIExport) error {
134-
// find all PublishedResources
135-
pubResources := &syncagentv1alpha1.PublishedResourceList{}
136-
if err := r.localClient.List(ctx, pubResources, &ctrlruntimeclient.ListOptions{
137-
LabelSelector: r.prFilter,
138-
}); err != nil {
139-
return fmt.Errorf("failed to list PublishedResources: %w", err)
140-
}
141-
142-
// filter out those PRs that are invalid; we keep those that are not yet converted into ARS,
132+
// find all PublishedResources; we keep those that are not yet converted into ARS,
143133
// just to reduce the amount of re-reconciles when the agent processes a number of PRs in a row
144134
// and would constantly update the APIExport; instead we rely on kcp to handle the eventual
145135
// consistency.
146136
// This allows us to already determine all resources we "own", which helps us in
147137
// bilding the proper permission claims if one of the related resources is using a resource
148138
// type managed via PublishedResource. Otherwise the controller might not see the PR for the
149139
// related resource and temporarily incorrectly assume it needs to add a permission claim.
150-
filteredPubResources := slices.DeleteFunc(pubResources.Items, func(pr syncagentv1alpha1.PublishedResource) bool {
151-
// TODO: Turn this into a webhook or CEL expressions.
152-
err := validation.ValidatePublishedResource(&pr)
153-
if err != nil {
154-
r.log.With("pr", pr.Name, "error", err).Warn("Ignoring invalid PublishedResource.")
155-
}
156-
157-
return err != nil
158-
})
140+
pubResources := &syncagentv1alpha1.PublishedResourceList{}
141+
if err := r.localClient.List(ctx, pubResources, &ctrlruntimeclient.ListOptions{
142+
LabelSelector: r.prFilter,
143+
}); err != nil {
144+
return fmt.Errorf("failed to list PublishedResources: %w", err)
145+
}
159146

160147
// Create two lists of schema names: ready schemas are those already processed by the
161148
// apiresourceschema controller, the other list includes all possible schema names.
@@ -164,7 +151,7 @@ func (r *Reconciler) reconcile(ctx context.Context, apiExport *kcpdevv1alpha1.AP
164151
// downstream components.
165152
readySchemaNames := sets.New[string]()
166153
allSchemaNames := sets.New[string]()
167-
for _, pubResource := range filteredPubResources {
154+
for _, pubResource := range pubResources.Items {
168155
schemaName, err := r.getSchemaName(ctx, &pubResource)
169156
if err != nil {
170157
return fmt.Errorf("failed to determine schema name for PublishedResource %s: %w", pubResource.Name, err)
@@ -198,7 +185,7 @@ func (r *Reconciler) reconcile(ctx context.Context, apiExport *kcpdevv1alpha1.AP
198185
// Now we can finally assemble the list of required permission claims.
199186
claimedResources := sets.New[permissionClaim]()
200187

201-
for _, pubResource := range filteredPubResources {
188+
for _, pubResource := range pubResources.Items {
202189
// to evaluate the namespace filter, the agent needs to fetch the namespace
203190
if filter := pubResource.Spec.Filter; filter != nil && filter.Namespace != nil {
204191
claimedResources.Insert(permissionClaim{

internal/controller/syncmanager/controller.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"github.com/kcp-dev/api-syncagent/internal/controllerutil"
2929
"github.com/kcp-dev/api-syncagent/internal/controllerutil/predicate"
3030
"github.com/kcp-dev/api-syncagent/internal/discovery"
31-
"github.com/kcp-dev/api-syncagent/internal/validation"
3231
syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
3332

3433
kcpapisv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
@@ -416,12 +415,6 @@ func isSyncEnabled(pr *syncagentv1alpha1.PublishedResource) bool {
416415
func (r *Reconciler) ensureSyncControllers(ctx context.Context, log *zap.SugaredLogger, publishedResources []syncagentv1alpha1.PublishedResource) error {
417416
requiredWorkers := sets.New[string]()
418417
for _, pr := range publishedResources {
419-
// TODO: Turn this into a webhook or CEL expressions.
420-
if err := validation.ValidatePublishedResource(&pr); err != nil {
421-
r.log.With("pr", pr.Name, "error", err).Warn("Ignoring invalid PublishedResource.")
422-
continue
423-
}
424-
425418
if isSyncEnabled(&pr) {
426419
requiredWorkers.Insert(getPublishedResourceKey(&pr))
427420
}

internal/validation/published_resource.go

Lines changed: 0 additions & 63 deletions
This file was deleted.

sdk/apis/syncagent/v1alpha1/published_resource.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,15 @@ const (
195195
RelatedResourceOriginKcp RelatedResourceOrigin = "kcp"
196196
)
197197

198+
// RelatedResourceSpec describes a single related resource, which might point to
199+
// any number of actual Kubernetes objects.
200+
//
201+
// (in the following rule, group is optional becaue core/v1 is represented by group="")
202+
// +kubebuilder:validation:XValidation:rule="has(self.kind) != (has(self.version) || has(self.resource))",message="must specify either kind (deprecated) or group, version, resource"
203+
// +kubebuilder:validation:XValidation:rule="has(self.resource) == has(self.version)",message="resource and version must be configured together or not at all"
204+
// +kubebuilder:validation:XValidation:rule="!has(self.group) || (has(self.resource) && has(self.version))",message="configuring a group also requires a version and resource"
205+
// group is included here because when an identityHash is used, core/v1 cannot possible be targetted
206+
// +kubebuilder:validation:XValidation:rule="!has(self.identityHash) || (has(self.group) && has(self.version) && has(self.resource))",message="identity hashes can only be used with GVRs"
198207
type RelatedResourceSpec struct {
199208
// Identifier is a unique name for this related resource. The name must be unique within one
200209
// PublishedResource and is the key by which consumers (end users) can identify and consume the
@@ -220,6 +229,8 @@ type RelatedResourceSpec struct {
220229
//
221230
// Deprecated: Use "Resource" instead. This field is limited to "ConfigMap" and "Secret" and will
222231
// be removed in the future. Kind and Resource cannot be specified at the same time.
232+
//
233+
// +kubebuilder:validation:Enum=ConfigMap;Secret
223234
Kind string `json:"kind,omitempty"`
224235

225236
// IdentityHash is the identity hash of a kcp APIExport, in case the given Kind is

0 commit comments

Comments
 (0)