@@ -28,12 +28,14 @@ import (
2828 predicateutil "github.com/kcp-dev/api-syncagent/internal/controllerutil/predicate"
2929 "github.com/kcp-dev/api-syncagent/internal/projection"
3030 "github.com/kcp-dev/api-syncagent/internal/resources/reconciling"
31+ "github.com/kcp-dev/api-syncagent/internal/validation"
3132 syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
3233
3334 kcpdevv1alpha1 "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
3435
3536 "k8s.io/apimachinery/pkg/labels"
3637 "k8s.io/apimachinery/pkg/runtime/schema"
38+ "k8s.io/apimachinery/pkg/types"
3739 "k8s.io/apimachinery/pkg/util/sets"
3840 "k8s.io/client-go/tools/record"
3941 "sigs.k8s.io/controller-runtime/pkg/builder"
@@ -110,10 +112,16 @@ func Add(
110112
111113func (r * Reconciler ) Reconcile (ctx context.Context , request reconcile.Request ) (reconcile.Result , error ) {
112114 r .log .Debug ("Processing" )
113- return reconcile.Result {}, r .reconcile (ctx )
115+
116+ apiExport := & kcpdevv1alpha1.APIExport {}
117+ if err := r .kcpClient .Get (ctx , types.NamespacedName {Name : r .apiExportName }, apiExport ); err != nil {
118+ return reconcile.Result {}, ctrlruntimeclient .IgnoreNotFound (err )
119+ }
120+
121+ return reconcile.Result {}, r .reconcile (ctx , apiExport )
114122}
115123
116- func (r * Reconciler ) reconcile (ctx context.Context ) error {
124+ func (r * Reconciler ) reconcile (ctx context.Context , apiExport * kcpdevv1alpha1. APIExport ) error {
117125 // find all PublishedResources
118126 pubResources := & syncagentv1alpha1.PublishedResourceList {}
119127 if err := r .localClient .List (ctx , pubResources , & ctrlruntimeclient.ListOptions {
@@ -122,22 +130,45 @@ func (r *Reconciler) reconcile(ctx context.Context) error {
122130 return fmt .Errorf ("failed to list PublishedResources: %w" , err )
123131 }
124132
125- // filter out those PRs that have not yet been processed into an ARS
133+ // filter out those PRs that have not yet been processed into an ARS or that are invalid
126134 filteredPubResources := slices .DeleteFunc (pubResources .Items , func (pr syncagentv1alpha1.PublishedResource ) bool {
135+ // TODO: Turn this into a webhook or CEL expressions.
136+ if err := validation .ValidatePublishedResource (& pr ); err != nil {
137+ r .log .With ("pr" , pr .Name , "error" , err ).Warn ("Ignoring invalid PublishedResource." )
138+ return true
139+ }
140+
127141 return pr .Status .ResourceSchemaName == ""
128142 })
129143
130144 // for each PR, we note down the created ARS and also the GVKs of related resources
131- arsList := sets .New [string ]()
132- claimedResources := sets .New [permissionClaim ]()
145+ newARSList := sets .New [string ]()
146+ for _ , pubResource := range filteredPubResources {
147+ newARSList .Insert (pubResource .Status .ResourceSchemaName )
148+ }
133149
134- // PublishedResources use kinds, but the PermissionClaims use resource names (plural),
135- // so we must translate accordingly
136- mapper := r .kcpClient .RESTMapper ()
150+ // To determine if the GVR of a related resource needs to be listed as a permission claim,
151+ // we first need to figure out all the GVRs our APIExport contains. This is not just the
152+ // list of ARS we just built, but also potentially other ARS's that exist in the APIExport
153+ // and that we would not touch.
154+ allARSList := mergeResourceSchemas (apiExport .Spec .LatestResourceSchemas , newARSList )
155+
156+ // turn the flat list of schema names ("version.resource.group") into a lookup table consisting
157+ // of group/resource only
158+ ourOwnResources := sets .New [schema.GroupResource ]()
159+ for _ , schemaName := range allARSList {
160+ gvr , err := parseSchemaName (schemaName )
161+ if err != nil {
162+ return fmt .Errorf ("failed to assemble own resources: %w" , err )
163+ }
137164
138- for _ , pubResource := range filteredPubResources {
139- arsList .Insert (pubResource .Status .ResourceSchemaName )
165+ ourOwnResources .Insert (gvr .GroupResource ())
166+ }
167+
168+ // Now we can finally assemble the list of required permission claims.
169+ claimedResources := sets .New [permissionClaim ]()
140170
171+ for _ , pubResource := range filteredPubResources {
141172 // to evaluate the namespace filter, the agent needs to fetch the namespace
142173 if filter := pubResource .Spec .Filter ; filter != nil && filter .Namespace != nil {
143174 claimedResources .Insert (permissionClaim {
@@ -153,40 +184,27 @@ func (r *Reconciler) reconcile(ctx context.Context) error {
153184 })
154185 }
155186
156- // Add a claim for every related resource, but make sure to project the GVK
157- // of related resources to their kcp-side equivalent first if they originate
158- // on the service cluster.
187+ // Add a claim for every foreign (!) related resource, but make sure to
188+ // project the GVR of related resources to their kcp-side equivalent first
189+ // if they originate on the service cluster.
159190 for _ , rr := range pubResource .Spec .Related {
160- kcpGVK := projection .RelatedResourceKcpGVK (& rr )
161-
162- versions := []string {}
163- if kcpGVK .Version != "" {
164- versions = append (versions , kcpGVK .Version )
165- }
166-
167- mapping , err := mapper .RESTMapping (schema.GroupKind {
168- Group : kcpGVK .Group ,
169- Kind : kcpGVK .Kind ,
170- }, versions ... )
171- if err != nil {
172- return fmt .Errorf ("unknown related resource kind %q: %w" , rr .Kind , err )
173- }
191+ kcpGVR := projection .RelatedResourceKcpGVR (& rr )
174192
193+ // We always want to see namespaces, for simplicity. Technically if we knew the scope
194+ // of every related resource, we could determine if a namespace claim is truly necessary.
175195 claimedResources .Insert (permissionClaim {
176- Group : kcpGVK .Group ,
177- Resource : mapping .Resource .Resource ,
178- IdentityHash : rr .IdentityHash ,
196+ Group : "" ,
197+ Resource : "namespaces" ,
179198 })
180- }
181- }
182199
183- // We always want to see namespaces, for simplicity. Technically if we knew the scope
184- // of every related resource, we could determine if a namespace claim is truly necessary.
185- if claimedResources .Len () > 0 {
186- claimedResources .Insert (permissionClaim {
187- Group : "" ,
188- Resource : "namespaces" ,
189- })
200+ if ! ourOwnResources .Has (kcpGVR .GroupResource ()) {
201+ claimedResources .Insert (permissionClaim {
202+ Group : kcpGVR .Group ,
203+ Resource : kcpGVR .Resource ,
204+ IdentityHash : rr .IdentityHash ,
205+ })
206+ }
207+ }
190208 }
191209
192210 // We always want to create events.
@@ -195,14 +213,9 @@ func (r *Reconciler) reconcile(ctx context.Context) error {
195213 Resource : "events" ,
196214 })
197215
198- if arsList .Len () == 0 {
199- r .log .Debug ("No ready PublishedResources available." )
200- return nil
201- }
202-
203216 // reconcile an APIExport in kcp
204217 factories := []reconciling.NamedAPIExportReconcilerFactory {
205- r .createAPIExportReconciler (arsList , claimedResources , r .agentName , r .apiExportName , r .recorder ),
218+ r .createAPIExportReconciler (newARSList , claimedResources , r .agentName , r .apiExportName , r .recorder ),
206219 }
207220
208221 if err := reconciling .ReconcileAPIExports (ctx , factories , "" , r .kcpClient ); err != nil {
0 commit comments