Skip to content

Commit 943f5b1

Browse files
authored
Merge pull request #491 from vdice/fix/executor-finalizer-cross-namespace
fix: scope SpinAppExecutor finalizer check to executor's namespace
2 parents 3d5008d + 761767e commit 943f5b1

2 files changed

Lines changed: 100 additions & 1 deletion

File tree

internal/controller/spinappexecutor_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func (r *SpinAppExecutorReconciler) handleDeletion(ctx context.Context, executor
103103
log := logging.FromContext(ctx)
104104

105105
var spinApps spinv1alpha1.SpinAppList
106-
if err := r.Client.List(ctx, &spinApps, client.MatchingFields{spinAppExecutorKey: executor.Name}); err != nil {
106+
if err := r.Client.List(ctx, &spinApps, client.InNamespace(executor.Namespace), client.MatchingFields{spinAppExecutorKey: executor.Name}); err != nil {
107107
log.Error(err, "Unable to list SpinApps")
108108
return err
109109
}

internal/controller/spinappexecutor_controller_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ import (
2525
spinv1alpha1 "github.com/spinframework/spin-operator/api/v1alpha1"
2626
"github.com/spinframework/spin-operator/internal/generics"
2727
"github.com/stretchr/testify/require"
28+
corev1 "k8s.io/api/core/v1"
29+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2830
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2931
ctrl "sigs.k8s.io/controller-runtime"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
3033
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3134
controllerconfig "sigs.k8s.io/controller-runtime/pkg/config"
3235
"sigs.k8s.io/controller-runtime/pkg/log/zap"
@@ -145,3 +148,99 @@ func testContainerdShimSpinExecutor() *spinv1alpha1.SpinAppExecutor {
145148
},
146149
}
147150
}
151+
152+
func TestSpinAppExecutorReconcile_CrossNamespaceDoesNotBlockDeletion(t *testing.T) {
153+
t.Parallel()
154+
155+
envTest, mgr, _ := setupExecutorController(t)
156+
157+
ctx, cancelFunc := context.WithTimeout(context.Background(), 10*time.Second)
158+
defer cancelFunc()
159+
160+
var wg sync.WaitGroup
161+
wg.Add(1)
162+
go func() {
163+
require.NoError(t, mgr.Start(ctx))
164+
wg.Done()
165+
}()
166+
167+
// Create two namespaces
168+
nsA := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-ns-a"}}
169+
nsB := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-ns-b"}}
170+
require.NoError(t, envTest.k8sClient.Create(ctx, nsA))
171+
require.NoError(t, envTest.k8sClient.Create(ctx, nsB))
172+
173+
// Create an executor with the same name in both namespaces
174+
executorA := &spinv1alpha1.SpinAppExecutor{
175+
ObjectMeta: metav1.ObjectMeta{
176+
Name: "containerd-shim-spin",
177+
Namespace: "test-ns-a",
178+
},
179+
Spec: spinv1alpha1.SpinAppExecutorSpec{
180+
CreateDeployment: true,
181+
DeploymentConfig: &spinv1alpha1.ExecutorDeploymentConfig{
182+
RuntimeClassName: generics.Ptr("wasmtime-spin-v2"),
183+
},
184+
},
185+
}
186+
executorB := &spinv1alpha1.SpinAppExecutor{
187+
ObjectMeta: metav1.ObjectMeta{
188+
Name: "containerd-shim-spin",
189+
Namespace: "test-ns-b",
190+
},
191+
Spec: spinv1alpha1.SpinAppExecutorSpec{
192+
CreateDeployment: true,
193+
DeploymentConfig: &spinv1alpha1.ExecutorDeploymentConfig{
194+
RuntimeClassName: generics.Ptr("wasmtime-spin-v2"),
195+
},
196+
},
197+
}
198+
require.NoError(t, envTest.k8sClient.Create(ctx, executorA))
199+
require.NoError(t, envTest.k8sClient.Create(ctx, executorB))
200+
201+
// Wait for finalizers to be added by the controller
202+
require.Eventually(t, func() bool {
203+
var exec spinv1alpha1.SpinAppExecutor
204+
if err := envTest.k8sClient.Get(ctx, client.ObjectKeyFromObject(executorA), &exec); err != nil {
205+
return false
206+
}
207+
return len(exec.Finalizers) > 0
208+
}, 5*time.Second, 100*time.Millisecond, "executor A should have finalizer")
209+
210+
require.Eventually(t, func() bool {
211+
var exec spinv1alpha1.SpinAppExecutor
212+
if err := envTest.k8sClient.Get(ctx, client.ObjectKeyFromObject(executorB), &exec); err != nil {
213+
return false
214+
}
215+
return len(exec.Finalizers) > 0
216+
}, 5*time.Second, 100*time.Millisecond, "executor B should have finalizer")
217+
218+
// Create a SpinApp in namespace B that references the executor by name
219+
spinApp := &spinv1alpha1.SpinApp{
220+
ObjectMeta: metav1.ObjectMeta{
221+
Name: "test-app",
222+
Namespace: "test-ns-b",
223+
},
224+
Spec: spinv1alpha1.SpinAppSpec{
225+
Executor: "containerd-shim-spin",
226+
Image: "ghcr.io/spinkube/containerd-shim-spin/examples/spin-rust-hello:v0.15.1",
227+
Replicas: 1,
228+
},
229+
}
230+
require.NoError(t, envTest.k8sClient.Create(ctx, spinApp))
231+
232+
// Delete executor in namespace A — this should succeed because
233+
// the SpinApp is in namespace B, not namespace A.
234+
require.NoError(t, envTest.k8sClient.Delete(ctx, executorA))
235+
236+
// The executor in namespace A should be fully deleted (finalizer removed)
237+
require.Eventually(t, func() bool {
238+
var exec spinv1alpha1.SpinAppExecutor
239+
err := envTest.k8sClient.Get(ctx, client.ObjectKeyFromObject(executorA), &exec)
240+
return apierrors.IsNotFound(err) // NotFound means it was deleted
241+
}, 5*time.Second, 100*time.Millisecond, "executor A should be deleted — SpinApp in namespace B should not block it")
242+
243+
// Verify executor in namespace B still exists (it has a dependent SpinApp)
244+
var execB spinv1alpha1.SpinAppExecutor
245+
require.NoError(t, envTest.k8sClient.Get(ctx, client.ObjectKeyFromObject(executorB), &execB))
246+
}

0 commit comments

Comments
 (0)