diff --git a/operator/e2e/e2e_test.go b/operator/e2e/e2e_test.go index 118cdd28..997440fc 100644 --- a/operator/e2e/e2e_test.go +++ b/operator/e2e/e2e_test.go @@ -92,16 +92,45 @@ func TestE2E(t *testing.T) { if os.Getenv("PULUMI_BOT_TOKEN") == "" { t.Skip("missing PULUMI_BOT_TOKEN") } - + // 1. Test `WorkspaceReclaimPolicy` is unset. cmd := exec.Command("bash", "-c", "envsubst < e2e/testdata/git-auth-nonroot/* | kubectl apply -f -") require.NoError(t, run(cmd)) dumpLogs(t, "git-auth-nonroot", "pod/git-auth-nonroot-workspace-0") - stack, err := waitFor[pulumiv1.Stack]("stacks/git-auth-nonroot", "git-auth-nonroot", "condition=Ready", 5*time.Minute) + stack, err := waitFor[pulumiv1.Stack]("stacks/git-auth-nonroot", "git-auth-nonroot", 5*time.Minute, "condition=Ready") assert.NoError(t, err) assert.Equal(t, `"[secret]"`, string(stack.Status.Outputs["secretOutput"].Raw)) assert.Equal(t, `"foo"`, string(stack.Status.Outputs["simpleOutput"].Raw)) + + // Ensure that the workspace pod was not deleted after successful Stack reconciliation. + found, err := foundEvent("Pod", "git-auth-nonroot-workspace-0", "git-auth-nonroot", "Killing") + assert.NoError(t, err) + assert.False(t, found) + + // 2. Test `WorkspaceReclaimPolicy` is set to `Delete`. + + // Update the Stack spec to set the `WorkspaceReclaimPolicy` to `Delete`. + cmd = exec.Command("kubectl", "patch", "stacks", "--namespace", "git-auth-nonroot", "git-auth-nonroot", "--type=merge", "-p", `{"spec":{"workspaceReclaimPolicy":"Delete"}}`) + require.NoError(t, run(cmd)) + + // Wait for the Stack to be reconciled, and observedGeneration to be updated. + _, err = waitFor[pulumiv1.Stack]( + "stacks/git-auth-nonroot", + "git-auth-nonroot", + 5*time.Minute, + "condition=Ready", + "jsonpath={.status.observedGeneration}=2") + assert.NoError(t, err) + + // Ensure that the workspace pod is now deleted after successful Stack reconciliation. + found, err = foundEvent("Pod", "git-auth-nonroot-workspace-0", "git-auth-nonroot", "Killing") + assert.NoError(t, err) + assert.True(t, found) + if t.Failed() { + dumpLogs(t, "git-auth-nonroot", "pod/git-auth-nonroot-workspace-0") + } + }, }, { @@ -113,7 +142,7 @@ func TestE2E(t *testing.T) { require.NoError(t, run(cmd)) dumpLogs(t, "targets", "pod/targets-workspace-0") - stack, err := waitFor[pulumiv1.Stack]("stacks/targets", "targets", "condition=Ready", 5*time.Minute) + stack, err := waitFor[pulumiv1.Stack]("stacks/targets", "targets", 5*time.Minute, "condition=Ready") assert.NoError(t, err) assert.Contains(t, stack.Status.Outputs, "targeted") @@ -141,13 +170,22 @@ func dumpLogs(t *testing.T, namespace, name string) { }) } -func waitFor[T any](name, namespace, condition string, d time.Duration) (*T, error) { +func waitFor[T any](name, namespace string, d time.Duration, conditions ...string) (*T, error) { + if len(conditions) == 0 { + return nil, fmt.Errorf("no conditions provided") + } + cmd := exec.Command("kubectl", "wait", name, "-n", namespace, - "--for", condition, + "--for", conditions[0], fmt.Sprintf("--timeout=%ds", int(d.Seconds())), "--output=yaml", ) + // Add additional conditions if provided. + for _, condition := range conditions[1:] { + cmd.Args = append(cmd.Args, "--for", condition) + } + err := run(cmd) if err != nil { return nil, err @@ -163,6 +201,22 @@ func waitFor[T any](name, namespace, condition string, d time.Duration) (*T, err return &obj, nil } +// foundEvent checks if a Kubernetes event with the given kind, name, namespace, and reason exists. +func foundEvent(kind, name, namespace, reason string) (bool, error) { + cmd := exec.Command("kubectl", "get", "events", + "-n", namespace, + "--field-selector", fmt.Sprintf("involvedObject.kind=%s,involvedObject.name=%s,reason=%s", kind, name, reason), + "--output=name", + ) + err := run(cmd) + if err != nil { + return false, err + } + + buf, _ := cmd.Stdout.(*bytes.Buffer) + return strings.Contains(buf.String(), "event/"+name), nil +} + // run executes the provided command within this context func run(cmd *exec.Cmd) error { command := strings.Join(cmd.Args, " ") diff --git a/operator/internal/controller/pulumi/stack_controller.go b/operator/internal/controller/pulumi/stack_controller.go index 1143d0bc..8f203117 100644 --- a/operator/internal/controller/pulumi/stack_controller.go +++ b/operator/internal/controller/pulumi/stack_controller.go @@ -803,6 +803,7 @@ func (r *StackReconciler) Reconcile(ctx context.Context, request ctrl.Request) ( // Delete the workspace if the reclaim policy is set to delete. if instance.Spec.WorkspaceReclaimPolicy == shared.WorkspaceReclaimDelete { + log.Info("Deleting workspace as reclaim policy is set to delete") err := sess.DeleteWorkspace(ctx) if err != nil { return reconcile.Result{}, err