Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kargo-cli for openSUSE:Factory checked in at 2026-06-13 18:48:29 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kargo-cli (Old) and /work/SRC/openSUSE:Factory/.kargo-cli.new.1981 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kargo-cli" Sat Jun 13 18:48:29 2026 rev:55 rq:1359087 version:1.10.7 Changes: -------- --- /work/SRC/openSUSE:Factory/kargo-cli/kargo-cli.changes 2026-06-10 15:53:30.073583463 +0200 +++ /work/SRC/openSUSE:Factory/.kargo-cli.new.1981/kargo-cli.changes 2026-06-13 18:50:36.281573634 +0200 @@ -1,0 +2,6 @@ +Sat Jun 13 10:09:11 UTC 2026 - Johannes Kastl <[email protected]> + +- update to 1.10.7: + no CLI-related changes or dependency updates + +------------------------------------------------------------------- Old: ---- kargo-cli-1.10.6.obscpio New: ---- kargo-cli-1.10.7.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kargo-cli.spec ++++++ --- /var/tmp/diff_new_pack.D9aLjh/_old 2026-06-13 18:50:37.533625650 +0200 +++ /var/tmp/diff_new_pack.D9aLjh/_new 2026-06-13 18:50:37.533625650 +0200 @@ -19,7 +19,7 @@ %define executable_name kargo Name: kargo-cli -Version: 1.10.6 +Version: 1.10.7 Release: 0 Summary: CLI for the Kubernetes Application lifecycle orchestration License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.D9aLjh/_old 2026-06-13 18:50:37.565626980 +0200 +++ /var/tmp/diff_new_pack.D9aLjh/_new 2026-06-13 18:50:37.573627312 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/akuity/kargo</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v1.10.6</param> + <param name="revision">v1.10.7</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.D9aLjh/_old 2026-06-13 18:50:37.597628309 +0200 +++ /var/tmp/diff_new_pack.D9aLjh/_new 2026-06-13 18:50:37.601628475 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/akuity/kargo</param> - <param name="changesrevision">1ec8d6094bdb9b2f29f4858f788f2af13ac37fb1</param></service></servicedata> + <param name="changesrevision">17077e406ed309fcc830d08776d9b9a47fe6e134</param></service></servicedata> (No newline at EOF) ++++++ kargo-cli-1.10.6.obscpio -> kargo-cli-1.10.7.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.6/pkg/webhook/kubernetes/admission.go new/kargo-cli-1.10.7/pkg/webhook/kubernetes/admission.go --- old/kargo-cli-1.10.6/pkg/webhook/kubernetes/admission.go 2026-06-09 14:17:09.000000000 +0200 +++ new/kargo-cli-1.10.7/pkg/webhook/kubernetes/admission.go 2026-06-12 02:30:12.000000000 +0200 @@ -2,10 +2,22 @@ import ( "regexp" + "slices" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) +// kubernetesGarbageCollectorUsernames is the set of well-known Kubernetes +// system identities that perform garbage collection and namespace teardown. +// These must be allowed to delete replicated secrets when a namespace is +// removed so that namespaces do not become stuck in Terminating. +var kubernetesGarbageCollectorUsernames = map[string]struct{}{ + "system:serviceaccount:kube-system:namespace-controller": {}, + "system:serviceaccount:kube-system:generic-garbage-collector": {}, + // when --use-service-account-credentials=false or unset + "system:kube-controller-manager": {}, +} + type IsRequestFromKargoControlplaneFn func(admission.Request) bool func IsRequestFromKargoControlplane(regex *regexp.Regexp) IsRequestFromKargoControlplaneFn { @@ -17,3 +29,18 @@ return regex.Match([]byte(req.UserInfo.Username)) } } + +// IsRequestFromKubernetesGarbageCollector returns true when the admission +// request originates from one of the well-known Kubernetes system controllers +// responsible for garbage collection and namespace teardown. +func IsRequestFromKubernetesGarbageCollector(req admission.Request) bool { + _, ok := kubernetesGarbageCollectorUsernames[req.UserInfo.Username] + return ok +} + +// IsRequestFromClusterAdmin returns true when the admission request originates +// from a member of the system:masters group, which maps to cluster-admin +// privileges and is used for break-glass operations. +func IsRequestFromClusterAdmin(req admission.Request) bool { + return slices.Contains(req.UserInfo.Groups, "system:masters") +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.6/pkg/webhook/kubernetes/admission_test.go new/kargo-cli-1.10.7/pkg/webhook/kubernetes/admission_test.go --- old/kargo-cli-1.10.6/pkg/webhook/kubernetes/admission_test.go 2026-06-09 14:17:09.000000000 +0200 +++ new/kargo-cli-1.10.7/pkg/webhook/kubernetes/admission_test.go 2026-06-12 02:30:12.000000000 +0200 @@ -11,6 +11,85 @@ "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) +func TestIsRequestFromKubernetesGarbageCollector(t *testing.T) { + testCases := []struct { + name string + username string + expected bool + }{ + { + name: "namespace-controller is allowed", + username: "system:serviceaccount:kube-system:namespace-controller", + expected: true, + }, + { + name: "generic-garbage-collector is allowed", + username: "system:serviceaccount:kube-system:generic-garbage-collector", + expected: true, + }, + { + name: "kube-controller-manager is allowed", + username: "system:kube-controller-manager", + expected: true, + }, + { + name: "other user is not allowed", + username: "some-user", + expected: false, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + req := admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + UserInfo: authnv1.UserInfo{Username: testCase.username}, + }, + } + require.Equal(t, testCase.expected, IsRequestFromKubernetesGarbageCollector(req)) + }) + } +} + +func TestIsRequestFromClusterAdmin(t *testing.T) { + testCases := []struct { + name string + groups []string + expected bool + }{ + { + name: "system:masters group member is allowed", + groups: []string{"system:masters"}, + expected: true, + }, + { + name: "system:masters among multiple groups is allowed", + groups: []string{"some-group", "system:masters"}, + expected: true, + }, + { + name: "no groups is not allowed", + expected: false, + }, + { + name: "other groups only is not allowed", + groups: []string{"some-group"}, + expected: false, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + req := admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + UserInfo: authnv1.UserInfo{Groups: testCase.groups}, + }, + } + require.Equal(t, testCase.expected, IsRequestFromClusterAdmin(req)) + }) + } +} + func TestIsRequestFromKargoControlplane(t *testing.T) { testCases := map[string]struct { regex *regexp.Regexp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.6/pkg/webhook/kubernetes/replicatedresource/webhook.go new/kargo-cli-1.10.7/pkg/webhook/kubernetes/replicatedresource/webhook.go --- old/kargo-cli-1.10.6/pkg/webhook/kubernetes/replicatedresource/webhook.go 2026-06-09 14:17:09.000000000 +0200 +++ new/kargo-cli-1.10.7/pkg/webhook/kubernetes/replicatedresource/webhook.go 2026-06-12 02:30:12.000000000 +0200 @@ -3,6 +3,7 @@ import ( "context" + admissionv1 "k8s.io/api/admission/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -38,6 +39,15 @@ if req.UserInfo.Username == w.cfg.ManagementControllerUsername { return admission.Allowed("request from Kargo management controller") } + // Namespace and GC controller are allowed to delete replicated resources + if req.Operation == admissionv1.Delete && + libWebhook.IsRequestFromKubernetesGarbageCollector(req) { + return admission.Allowed("request from Kubernetes system controller") + } + // Cluster administrators are allowed to perform any operation + if libWebhook.IsRequestFromClusterAdmin(req) { + return admission.Allowed("request from cluster administrator") + } return admission.Denied( "replicated resources are managed by Kargo" + " and cannot be created, modified, or deleted by end users", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.6/pkg/webhook/kubernetes/replicatedresource/webhook_test.go new/kargo-cli-1.10.7/pkg/webhook/kubernetes/replicatedresource/webhook_test.go --- old/kargo-cli-1.10.6/pkg/webhook/kubernetes/replicatedresource/webhook_test.go 2026-06-09 14:17:09.000000000 +0200 +++ new/kargo-cli-1.10.7/pkg/webhook/kubernetes/replicatedresource/webhook_test.go 2026-06-12 02:30:12.000000000 +0200 @@ -76,6 +76,92 @@ }, }, { + name: "namespace-controller DELETE is allowed", + req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Operation: admissionv1.Delete, + UserInfo: authnv1.UserInfo{ + Username: "system:serviceaccount:kube-system:namespace-controller", + }, + }, + }, + assert: func(t *testing.T, resp admission.Response) { + require.True(t, resp.Allowed) + }, + }, + { + name: "generic-garbage-collector DELETE is allowed", + req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Operation: admissionv1.Delete, + UserInfo: authnv1.UserInfo{ + Username: "system:serviceaccount:kube-system:generic-garbage-collector", + }, + }, + }, + assert: func(t *testing.T, resp admission.Response) { + require.True(t, resp.Allowed) + }, + }, + { + name: "kube-controller-manager DELETE is allowed", + req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Operation: admissionv1.Delete, + UserInfo: authnv1.UserInfo{ + Username: "system:kube-controller-manager", + }, + }, + }, + assert: func(t *testing.T, resp admission.Response) { + require.True(t, resp.Allowed) + }, + }, + { + name: "system:masters group member DELETE is allowed", + req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Operation: admissionv1.Delete, + UserInfo: authnv1.UserInfo{ + Username: "cluster-admin", + Groups: []string{"system:masters"}, + }, + }, + }, + assert: func(t *testing.T, resp admission.Response) { + require.True(t, resp.Allowed) + }, + }, + { + name: "system:masters group member UPDATE is allowed", + req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Operation: admissionv1.Update, + UserInfo: authnv1.UserInfo{ + Username: "cluster-admin", + Groups: []string{"system:masters"}, + }, + }, + }, + assert: func(t *testing.T, resp admission.Response) { + require.True(t, resp.Allowed) + }, + }, + { + name: "garbage collector non-DELETE is denied", + req: admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Operation: admissionv1.Update, + UserInfo: authnv1.UserInfo{ + Username: "system:serviceaccount:kube-system:namespace-controller", + }, + }, + }, + assert: func(t *testing.T, resp admission.Response) { + require.False(t, resp.Allowed) + }, + }, + { name: "other controlplane component is denied", req: admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.6/ui/src/features/project/pipelines/graph/use-events-watcher.ts new/kargo-cli-1.10.7/ui/src/features/project/pipelines/graph/use-events-watcher.ts --- old/kargo-cli-1.10.6/ui/src/features/project/pipelines/graph/use-events-watcher.ts 2026-06-09 14:17:09.000000000 +0200 +++ new/kargo-cli-1.10.7/ui/src/features/project/pipelines/graph/use-events-watcher.ts 2026-06-12 02:30:12.000000000 +0200 @@ -4,7 +4,6 @@ import { Watcher } from '@ui/features/project/pipelines/watcher'; import { queryCache } from '@ui/features/utils/cache'; import { Stage, Warehouse } from '@ui/gen/api/v1alpha1/generated_pb'; -import { useDocumentEvent } from '@ui/utils/document'; export const useEventsWatcher = ( project: string, @@ -15,26 +14,44 @@ warehouses?: string[] ) => { const client = useQueryClient(); - const isWindowVisible = useDocumentEvent( - 'visibilitychange', - () => document.visibilityState === 'visible' - ); useEffect(() => { - if (!isWindowVisible || !project) { + if (!project) { return; } - const watcher = new Watcher(project, client); + let watcher: Watcher | undefined; - watcher.watchStages(act?.onStage, warehouses); - watcher.watchWarehouses({ - onWarehouseEvent: act?.onWarehouse, - refreshHook: queryCache.freight.refetchQueryFreight - }); + // (Re)establish the watch streams. Aborts any previous watcher first so we + // never leak a connection. + const connect = () => { + watcher?.cancelWatch(); + watcher = new Watcher(project, client); + watcher.watchStages(act?.onStage, warehouses); + watcher.watchWarehouses({ + onWarehouseEvent: act?.onWarehouse, + refreshHook: queryCache.freight.refetchQueryFreight + }); + }; + + connect(); + + // Reconnect when the tab becomes visible again. The watch is left running + // while the tab is hidden (we do NOT cancel on leave), but a hidden tab can + // be throttled/frozen and have its stream silently dropped -- reconnecting + // on return guarantees a live stream again. + const onVisibilityChange = () => { + // Reuse the existing connection if it is still alive; only reconnect when + // the stream has actually ended (e.g. dropped while the tab was hidden). + if (document.visibilityState === 'visible' && !watcher?.isActive()) { + connect(); + } + }; + document.addEventListener('visibilitychange', onVisibilityChange); return () => { - watcher.cancelWatch(); + document.removeEventListener('visibilitychange', onVisibilityChange); + watcher?.cancelWatch(); }; - }, [isWindowVisible, project, (warehouses || []).join(',')]); + }, [project, (warehouses || []).join(',')]); }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.6/ui/src/features/project/pipelines/use-watch-freight.ts new/kargo-cli-1.10.7/ui/src/features/project/pipelines/use-watch-freight.ts --- old/kargo-cli-1.10.6/ui/src/features/project/pipelines/use-watch-freight.ts 2026-06-09 14:17:09.000000000 +0200 +++ new/kargo-cli-1.10.7/ui/src/features/project/pipelines/use-watch-freight.ts 2026-06-12 02:30:12.000000000 +0200 @@ -95,14 +95,24 @@ (current) => deleteFreight(current, freight) ); } else { - const updatedFreight = upsertFreight(currentFreight, freight); - const queryFreightKey = createConnectQueryKey({ - cardinality: 'finite', - schema: queryFreight, - input: { project }, - transport: transportWithAuth - }); - client.setQueryData(queryFreightKey, updatedFreight); + // Update all queryFreight caches for this project, including + // warehouse-filtered variants, which use a different cache key. + // Using setQueriesData (rather than setQueryData) ensures we only + // touch caches that already have an active query backing them, + // avoiding orphaned entries with no queryFn that would crash on + // refetch. + client.setQueriesData<QueryFreightResponse>( + { + queryKey: createConnectQueryKey({ + cardinality: 'finite', + schema: queryFreight, + input: { project }, + transport: transportWithAuth + }), + exact: false + }, + (current) => upsertFreight(current, freight) + ); } } }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.6/ui/src/features/project/pipelines/watcher.ts new/kargo-cli-1.10.7/ui/src/features/project/pipelines/watcher.ts --- old/kargo-cli-1.10.6/ui/src/features/project/pipelines/watcher.ts 2026-06-09 14:17:09.000000000 +0200 +++ new/kargo-cli-1.10.7/ui/src/features/project/pipelines/watcher.ts 2026-06-12 02:30:12.000000000 +0200 @@ -54,6 +54,10 @@ client: QueryClient; promiseClient: Client<typeof KargoService>; project: string; + // Set once either stream ends or errors for a reason other than an + // intentional cancel. Used by isActive() so callers can reuse a live watcher + // instead of needlessly reconnecting. + private ended = false; constructor(project: string, client: QueryClient) { this.cancel = new AbortController(); @@ -66,6 +70,18 @@ this.cancel.abort(); } + // Whether the watch is still running: not cancelled and neither stream has + // ended/errored. + isActive() { + return !this.cancel.signal.aborted && !this.ended; + } + + private markEnded() { + if (!this.cancel.signal.aborted) { + this.ended = true; + } + } + async watchStages( // utilise the fact that something changed in this stage // avoid as much as re-construction of data as possible by using this parameter @@ -130,7 +146,9 @@ onStageEvent?.(stage); } - ); + ) + .catch(() => {}) + .finally(() => this.markEnded()); } async watchWarehouses(opts?: { @@ -204,6 +222,8 @@ opts?.onWarehouseEvent?.(warehouse); } - ); + ) + .catch(() => {}) + .finally(() => this.markEnded()); } } ++++++ kargo-cli.obsinfo ++++++ --- /var/tmp/diff_new_pack.D9aLjh/_old 2026-06-13 18:50:41.493790176 +0200 +++ /var/tmp/diff_new_pack.D9aLjh/_new 2026-06-13 18:50:41.501790508 +0200 @@ -1,5 +1,5 @@ name: kargo-cli -version: 1.10.6 -mtime: 1781007429 -commit: 1ec8d6094bdb9b2f29f4858f788f2af13ac37fb1 +version: 1.10.7 +mtime: 1781224212 +commit: 17077e406ed309fcc830d08776d9b9a47fe6e134 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/kargo-cli/vendor.tar.gz /work/SRC/openSUSE:Factory/.kargo-cli.new.1981/vendor.tar.gz differ: char 13, line 1
