Skip to content

Commit cc43154

Browse files
authored
Merge pull request #28 from kcp-dev/sync-tests
🌱 Add first tests for the syncing logic
2 parents c513691 + 50f7cd0 commit cc43154

File tree

7 files changed

+557
-2
lines changed

7 files changed

+557
-2
lines changed

hack/ci/run-e2e-tests.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export AGENT_BINARY="$(realpath _build/api-syncagent)"
8080

8181
# time to run the tests
8282
echodate "Running e2e tests…"
83-
(set -x; go test -tags e2e -timeout 2h -v ./test/e2e/...)
83+
WHAT="${WHAT:-./test/e2e/...}"
84+
(set -x; go test -tags e2e -timeout 2h -v $WHAT)
8485

8586
echodate "Done. :-)"

internal/controller/apiexport/controller.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ func (r *Reconciler) reconcile(ctx context.Context) error {
139139
for _, pubResource := range filteredPubResources {
140140
arsList.Insert(pubResource.Status.ResourceSchemaName)
141141

142+
// to evaluate the namespace filter, the agent needs to fetch the namespace
143+
if filter := pubResource.Spec.Filter; filter != nil && filter.Namespace != nil {
144+
claimedResources.Insert("namespaces")
145+
}
146+
142147
for _, rr := range pubResource.Spec.Related {
143148
resource, err := mapper.ResourceFor(schema.GroupVersionResource{
144149
Resource: rr.Kind,

internal/controller/sync/controller.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ import (
3333
kcpcore "github.com/kcp-dev/kcp/sdk/apis/core"
3434
kcpdevcorev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
3535

36+
corev1 "k8s.io/api/core/v1"
37+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3638
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
39+
"k8s.io/apimachinery/pkg/labels"
3740
"k8s.io/apimachinery/pkg/types"
3841
"k8s.io/utils/ptr"
3942
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -165,6 +168,27 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
165168
return reconcile.Result{}, nil
166169
}
167170

171+
// if there is a namespace, get it if a namespace filter is also configured
172+
var namespace *corev1.Namespace
173+
if filter := r.pubRes.Spec.Filter; filter != nil && filter.Namespace != nil && remoteObj.GetNamespace() != "" {
174+
namespace = &corev1.Namespace{}
175+
key := types.NamespacedName{Name: remoteObj.GetNamespace()}
176+
177+
if err := r.vwClient.Get(wsCtx, key, namespace); err != nil {
178+
return reconcile.Result{}, fmt.Errorf("failed to retrieve remote object's namespace: %w", err)
179+
}
180+
}
181+
182+
// apply filtering rules to scope down the number of objects we sync
183+
include, err := r.objectMatchesFilter(remoteObj, namespace)
184+
if err != nil {
185+
return reconcile.Result{}, fmt.Errorf("failed to apply filtering rules: %w", err)
186+
}
187+
188+
if !include {
189+
return reconcile.Result{}, nil
190+
}
191+
168192
syncContext := sync.NewContext(ctx, wsCtx)
169193

170194
// if desired, fetch the cluster path as well (some downstream service providers might make use of it,
@@ -193,3 +217,34 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
193217

194218
return result, nil
195219
}
220+
221+
func (r *Reconciler) objectMatchesFilter(remoteObj *unstructured.Unstructured, namespace *corev1.Namespace) (bool, error) {
222+
if r.pubRes.Spec.Filter == nil {
223+
return true, nil
224+
}
225+
226+
objMatches, err := r.matchesFilter(remoteObj, r.pubRes.Spec.Filter.Resource)
227+
if err != nil || !objMatches {
228+
return false, err
229+
}
230+
231+
nsMatches, err := r.matchesFilter(namespace, r.pubRes.Spec.Filter.Namespace)
232+
if err != nil || !nsMatches {
233+
return false, err
234+
}
235+
236+
return true, nil
237+
}
238+
239+
func (r *Reconciler) matchesFilter(obj metav1.Object, selector *metav1.LabelSelector) (bool, error) {
240+
if selector == nil {
241+
return true, nil
242+
}
243+
244+
s, err := metav1.LabelSelectorAsSelector(selector)
245+
if err != nil {
246+
return false, err
247+
}
248+
249+
return s.Matches(labels.Set(obj.GetLabels())), nil
250+
}

internal/sync/object_syncer.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,10 @@ func (s *objectSyncer) ensureNamespace(ctx context.Context, log *zap.SugaredLogg
349349
return nil
350350
}
351351

352+
// Use a get-then-create approach to benefit from having a cache; otherwise if we always
353+
// send a create request, we're needlessly spamming the kube apiserver. Yes, this approach
354+
// is a race condition and we have to check for AlreadyExists later down the line, but that
355+
// only occurs on cold caches. During normal operations this should be more efficient.
352356
ns := &corev1.Namespace{}
353357
if err := client.Get(ctx, types.NamespacedName{Name: namespace}, ns); ctrlruntimeclient.IgnoreNotFound(err) != nil {
354358
return fmt.Errorf("failed to check: %w", err)
@@ -358,7 +362,7 @@ func (s *objectSyncer) ensureNamespace(ctx context.Context, log *zap.SugaredLogg
358362
ns.Name = namespace
359363

360364
log.Debugw("Creating namespace…", "namespace", namespace)
361-
if err := client.Create(ctx, ns); err != nil {
365+
if err := client.Create(ctx, ns); err != nil && !apierrors.IsAlreadyExists(err) {
362366
return fmt.Errorf("failed to create: %w", err)
363367
}
364368
}

0 commit comments

Comments
 (0)