Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package nelm for openSUSE:Factory checked in at 2026-07-06 12:34:12 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/nelm (Old) and /work/SRC/openSUSE:Factory/.nelm.new.1982 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "nelm" Mon Jul 6 12:34:12 2026 rev:29 rq:1363943 version:1.24.3 Changes: -------- --- /work/SRC/openSUSE:Factory/nelm/nelm.changes 2026-06-28 21:09:00.291071219 +0200 +++ /work/SRC/openSUSE:Factory/.nelm.new.1982/nelm.changes 2026-07-06 12:36:53.660918491 +0200 @@ -1,0 +2,18 @@ +Mon Jul 06 05:09:45 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.24.3: + * Bug Fixes + - autodetection of ServiceAccount dependencies for + ClusterRoleBinding and RoleBinding resources (#632) (e9ea5da) + - make pods/controllers depend on rolebindings (#634) (233e123) +- Update to version 1.24.2: + * Bug Fixes + - better error message when kube client request timed out + (dc11213) + - close LegacyReportCh on action return (#629) (78033c6) + - deploy ns on pre-pre-install stage (#630) (816d8c1) + - panic if channel for legacy progress reporter closed (#618) + (0e6945c) + - pass NoPodLogs to kubedog (#622) (069609f) + +------------------------------------------------------------------- Old: ---- nelm-1.24.1.obscpio New: ---- nelm-1.24.3.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ nelm.spec ++++++ --- /var/tmp/diff_new_pack.uxAAih/_old 2026-07-06 12:36:54.804958174 +0200 +++ /var/tmp/diff_new_pack.uxAAih/_new 2026-07-06 12:36:54.808958312 +0200 @@ -17,7 +17,7 @@ Name: nelm -Version: 1.24.1 +Version: 1.24.3 Release: 0 Summary: Helm 3 alternative License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.uxAAih/_old 2026-07-06 12:36:54.860960116 +0200 +++ /var/tmp/diff_new_pack.uxAAih/_new 2026-07-06 12:36:54.868960394 +0200 @@ -1,9 +1,9 @@ <services> <service name="obs_scm" mode="manual"> - <param name="url">https://github.com/werf/nelm</param> + <param name="url">https://github.com/werf/nelm.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v1.24.1</param> + <param name="revision">refs/tags/v1.24.3</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.uxAAih/_old 2026-07-06 12:36:54.896961365 +0200 +++ /var/tmp/diff_new_pack.uxAAih/_new 2026-07-06 12:36:54.900961504 +0200 @@ -1,6 +1,8 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/werf/nelm</param> - <param name="changesrevision">9366af9135202b7d705c71fef6c7dbe0231c0345</param></service></servicedata> + <param name="changesrevision">9366af9135202b7d705c71fef6c7dbe0231c0345</param></service><service name="tar_scm"> + <param name="url">https://github.com/werf/nelm.git</param> + <param name="changesrevision">c0c3c7ab1457fbf634acee9b2d2a29b9cdc101d4</param></service></servicedata> (No newline at EOF) ++++++ nelm-1.24.1.obscpio -> nelm-1.24.3.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/CHANGELOG.md new/nelm-1.24.3/CHANGELOG.md --- old/nelm-1.24.1/CHANGELOG.md 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/CHANGELOG.md 2026-06-08 18:00:18.000000000 +0200 @@ -1,5 +1,24 @@ # Changelog +## [1.24.3](https://github.com/werf/nelm/compare/v1.24.2...v1.24.3) (2026-06-08) + + +### Bug Fixes + +* autodetection of ServiceAccount dependencies for ClusterRoleBinding and RoleBinding resources ([#632](https://github.com/werf/nelm/issues/632)) ([e9ea5da](https://github.com/werf/nelm/commit/e9ea5da1e1ef7606979aab5a45ea058b48837476)) +* make pods/controllers depend on rolebindings ([#634](https://github.com/werf/nelm/issues/634)) ([233e123](https://github.com/werf/nelm/commit/233e1232890bff0682ba9bb2c1638657aff17297)) + +## [1.24.2](https://github.com/werf/nelm/compare/v1.24.1...v1.24.2) (2026-06-04) + + +### Bug Fixes + +* better error message when kube client request timed out ([dc11213](https://github.com/werf/nelm/commit/dc11213b4fa89a749b5f7c5fb1199c611d646484)) +* close LegacyReportCh on action return ([#629](https://github.com/werf/nelm/issues/629)) ([78033c6](https://github.com/werf/nelm/commit/78033c656d87290b24c540ec292608bf3a21fb66)) +* deploy ns on pre-pre-install stage ([#630](https://github.com/werf/nelm/issues/630)) ([816d8c1](https://github.com/werf/nelm/commit/816d8c1fbc9610609bfdc61084b3b0848606b20d)) +* panic if channel for legacy progress reporter closed ([#618](https://github.com/werf/nelm/issues/618)) ([0e6945c](https://github.com/werf/nelm/commit/0e6945ce047c67c6e23a52de7ba06372ffcf383b)) +* pass NoPodLogs to kubedog ([#622](https://github.com/werf/nelm/issues/622)) ([069609f](https://github.com/werf/nelm/commit/069609f63b214323d7ca1ebc9818e200818888ca)) + ## [1.24.1](https://github.com/werf/nelm/compare/v1.24.0...v1.24.1) (2026-04-29) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/README.md new/nelm-1.24.3/README.md --- old/nelm-1.24.1/README.md 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/README.md 2026-06-08 18:00:18.000000000 +0200 @@ -1,3 +1,7 @@ +<p align="center"> + <img src="resources/images/nelm-logo.png" style="max-height:100%;" height="100"> +</p> + <!-- <p align="center"> <a href="https://godoc.org/github.com/werf/nelm"><img src="https://godoc.org/github.com/werf/nelm?status.svg" alt="GoDoc"></a> <a href="https://qlty.sh/gh/werf/projects/nelm"><img src="https://qlty.sh/gh/werf/projects/nelm/coverage.svg" alt="Code Coverage" /></a> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/action/release_install.go new/nelm-1.24.3/pkg/action/release_install.go --- old/nelm-1.24.1/pkg/action/release_install.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/action/release_install.go 2026-06-08 18:00:18.000000000 +0200 @@ -432,6 +432,7 @@ }, clientFactory, resource.BuildResourcesOptions{ Remote: true, DefaultDeletePropagation: metav1.DeletionPropagation(opts.DefaultDeletePropagation), + NoPodLogs: opts.NoPodLogs, }) if err != nil { return fmt.Errorf("build resources: %w", err) @@ -567,6 +568,9 @@ var reporter *plan.LegacyProgressReporter if opts.LegacyProgressReportCh != nil { reporter = plan.NewLegacyProgressReporter(opts.LegacyProgressReportCh) + defer func() { + close(opts.LegacyProgressReportCh) + }() } log.Default.Debug(ctx, "Execute release install plan") @@ -848,6 +852,7 @@ }, clientFactory, resource.BuildResourcesOptions{ Remote: true, DefaultDeletePropagation: metav1.DeletionPropagation(opts.DefaultDeletePropagation), + NoPodLogs: opts.NoPodLogs, }) if err != nil { return nil, nonCritErrs, critErrs.Add(fmt.Errorf("build resources: %w", err)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/action/release_rollback.go new/nelm-1.24.3/pkg/action/release_rollback.go --- old/nelm-1.24.1/pkg/action/release_rollback.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/action/release_rollback.go 2026-06-08 18:00:18.000000000 +0200 @@ -274,6 +274,7 @@ instResources, delResources, err := resource.BuildResources(ctx, deployType, releaseNamespace, prevRelResSpecs, newRelResSpecs, patchers, clientFactory, resource.BuildResourcesOptions{ Remote: true, DefaultDeletePropagation: metav1.DeletionPropagation(opts.DefaultDeletePropagation), + NoPodLogs: opts.NoPodLogs, }) if err != nil { return fmt.Errorf("build resources: %w", err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/action/release_uninstall.go new/nelm-1.24.3/pkg/action/release_uninstall.go --- old/nelm-1.24.1/pkg/action/release_uninstall.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/action/release_uninstall.go 2026-06-08 18:00:18.000000000 +0200 @@ -196,6 +196,7 @@ } log.Default.Debug(ctx, "Build release history") + history, err := release.BuildHistory(releaseName, releaseStorage, release.HistoryOptions{}) if err != nil { return fmt.Errorf("build release history: %w", err) @@ -213,6 +214,7 @@ deployType := common.DeployTypeUninstall log.Default.Debug(ctx, "Convert previous release to resource specs") + prevRelResSpecs, err := release.ReleaseToResourceSpecs(prevRelease, releaseNamespace, false) if err != nil { return fmt.Errorf("convert previous release to resource specs: %w", err) @@ -227,15 +229,18 @@ } log.Default.Debug(ctx, "Build resources") + instResources, delResources, err := resource.BuildResources(ctx, deployType, releaseNamespace, prevRelResSpecs, nil, patchers, clientFactory, resource.BuildResourcesOptions{ Remote: true, DefaultDeletePropagation: metav1.DeletionPropagation(opts.DefaultDeletePropagation), + NoPodLogs: opts.NoPodLogs, }) if err != nil { return fmt.Errorf("build resources: %w", err) } log.Default.Debug(ctx, "Build resource infos") + instResInfos, delResInfos, err := plan.BuildResourceInfos(ctx, deployType, releaseName, releaseNamespace, instResources, delResources, prevReleaseFailed, clientFactory, plan.BuildResourceInfosOptions{ NetworkParallelism: opts.NetworkParallelism, NoRemoveManualChanges: opts.NoRemoveManualChanges, @@ -246,12 +251,14 @@ } log.Default.Debug(ctx, "Build release infos") + relInfos, err := plan.BuildReleaseInfos(ctx, deployType, releases, nil) if err != nil { return fmt.Errorf("build release infos: %w", err) } log.Default.Debug(ctx, "Build delete plan") + deletePlan, err := plan.BuildPlan(instResInfos, delResInfos, relInfos, plan.BuildPlanOptions{ NoFinalTracking: opts.NoFinalTracking, }) @@ -271,6 +278,7 @@ logStore := kdutil.NewConcurrent(logstore.NewLogStore()) log.Default.Debug(ctx, "Start tracking") + var progressPrinter *track.ProgressTablesPrinter if !opts.NoProgressTablePrint { progressPrinter = track.NewProgressTablesPrinter(taskStore, logStore, track.ProgressTablesPrinterOptions{ @@ -285,9 +293,13 @@ var reporter *plan.LegacyProgressReporter if opts.LegacyProgressReportCh != nil { reporter = plan.NewLegacyProgressReporter(opts.LegacyProgressReportCh) + defer func() { + close(opts.LegacyProgressReportCh) + }() } log.Default.Debug(ctx, "Execute release delete plan") + executePlanErr := plan.ExecutePlan(ctx, releaseNamespace, deletePlan, taskStore, logStore, informerFactory, history, clientFactory, plan.ExecutePlanOptions{ LegacyProgressReporter: reporter, TrackingOptions: opts.TrackingOptions, @@ -322,6 +334,7 @@ criticalErrs.Add(critErrs) nonCriticalErrs.Add(nonCritErrs) + if runFailurePlanResult != nil { completedResourceOps = append(completedResourceOps, runFailurePlanResult.CompletedResourceOps...) canceledResourceOps = append(canceledResourceOps, runFailurePlanResult.CanceledResourceOps...) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/common/common.go new/nelm-1.24.3/pkg/common/common.go --- old/nelm-1.24.1/pkg/common/common.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/common/common.go 2026-06-08 18:00:18.000000000 +0200 @@ -105,6 +105,7 @@ DefaultReleaseHistoryLimit = 10 // DefaultResourceValidationKubeVersion Kubernetes version to use during resource validation by kubeconform DefaultResourceValidationKubeVersion = "1.35.0" + DefaultWebhookRetryTimeout = 2 * time.Minute KubectlEditFieldManager = "kubectl-edit" OldFieldManagerPrefix = "werf" OutputFormatJSON = "json" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/kube/client_kube.go new/nelm-1.24.3/pkg/kube/client_kube.go --- old/nelm-1.24.1/pkg/kube/client_kube.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/kube/client_kube.go 2026-06-08 18:00:18.000000000 +0200 @@ -349,7 +349,7 @@ } func retryOnWebhookErr(ctx context.Context, fn func() error) error { - retryCtx, cancel := context.WithTimeout(ctx, 2*time.Minute) + retryCtx, cancel := context.WithTimeoutCause(ctx, common.DefaultWebhookRetryTimeout, fmt.Errorf("context timed out: webhook retry timed out after %s", common.DefaultWebhookRetryTimeout.String())) defer cancel() if err := kvwait.PollUntilContextCancel(retryCtx, 2*time.Second, true, func(ctx context.Context) (bool, error) { @@ -365,7 +365,11 @@ return true, nil }); err != nil { - return fmt.Errorf("retry on webhook error: %w", err) + if retryCtx.Err() != nil { + return fmt.Errorf("retryable on webhook error: %w", context.Cause(retryCtx)) + } + + return fmt.Errorf("retryable on webhook error: %w", err) } return nil diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/plan/legacy_progress_reporter.go new/nelm-1.24.3/pkg/plan/legacy_progress_reporter.go --- old/nelm-1.24.1/pkg/plan/legacy_progress_reporter.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/plan/legacy_progress_reporter.go 2026-06-08 18:00:18.000000000 +0200 @@ -49,10 +49,14 @@ report = buildProgressReport(s.frozen, s.ops) }) - select { - case r.reportCh <- report: - case <-ctx.Done(): - } + func() { + defer func() { _ = recover() }() + + select { + case r.reportCh <- report: + case <-ctx.Done(): + } + }() } func (r *LegacyProgressReporter) startStage(p *Plan, resolvedNamespaces map[string]string) { @@ -124,6 +128,10 @@ typ progrep.OperationType } +func sendNonBlocking(ch chan<- progrep.ProgressReport, report progrep.ProgressReport) { + safeSend(ch, report) +} + func buildProgressReport(frozen []progrep.StageReport, ops []opEntry) progrep.ProgressReport { stageReports := make([]progrep.StageReport, 0, len(frozen)+1) @@ -183,9 +191,15 @@ } } -func sendNonBlocking(ch chan<- progrep.ProgressReport, report progrep.ProgressReport) { +func safeSend(ch chan<- progrep.ProgressReport, report progrep.ProgressReport) (sent bool) { + defer func() { + _ = recover() + }() + select { case ch <- report: + return true default: + return false } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/plan/legacy_progress_reporter_ai_test.go new/nelm-1.24.3/pkg/plan/legacy_progress_reporter_ai_test.go --- old/nelm-1.24.1/pkg/plan/legacy_progress_reporter_ai_test.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/plan/legacy_progress_reporter_ai_test.go 2026-06-08 18:00:18.000000000 +0200 @@ -267,6 +267,26 @@ assert.Equal(t, OperationStatusCompleted, op.Status) } +func TestAI_ReportStatus_DoesNotPanicOnClosedChannel(t *testing.T) { + ch := make(chan progrep.ProgressReport, 1) + reporter := NewLegacyProgressReporter(ch) + + ops := []*Operation{ + { + Type: OperationTypeCreate, Version: OperationVersionCreate, Category: OperationCategoryResource, + Config: &OperationConfigCreate{ResourceSpec: makeResourceSpec("cm1", "", gvkConfigMap)}, + }, + } + p := buildTestPlan(ops, nil) + reporter.startStage(p, map[string]string{ops[0].ID(): "default"}) + + close(ch) + + assert.NotPanics(t, func() { + reporter.ReportStatus(ops[0].ID(), progrep.OperationStatusCompleted) + }) +} + func TestAI_ReportStatus_SendsSnapshot(t *testing.T) { ch := make(chan progrep.ProgressReport, 64) reporter := NewLegacyProgressReporter(ch) @@ -446,6 +466,15 @@ } } +func TestAI_SendNonBlocking_DoesNotPanicOnClosedChannel(t *testing.T) { + ch := make(chan progrep.ProgressReport, 1) + close(ch) + + assert.NotPanics(t, func() { + sendNonBlocking(ch, progrep.ProgressReport{}) + }) +} + func TestAI_SendNonBlocking_DropsWhenFull(t *testing.T) { ch := make(chan progrep.ProgressReport, 1) @@ -528,6 +557,26 @@ assert.Equal(t, progrep.OperationStatusPending, activeOps[0].Status) } +func TestAI_Stop_DoesNotPanicOnClosedChannel(t *testing.T) { + ch := make(chan progrep.ProgressReport, 1) + reporter := NewLegacyProgressReporter(ch) + + ops := []*Operation{ + { + Type: OperationTypeCreate, Version: OperationVersionCreate, Category: OperationCategoryResource, + Config: &OperationConfigCreate{ResourceSpec: makeResourceSpec("cm1", "", gvkConfigMap)}, + }, + } + p := buildTestPlan(ops, nil) + reporter.startStage(p, map[string]string{ops[0].ID(): "default"}) + + close(ch) + + assert.NotPanics(t, func() { + reporter.Stop(context.Background()) + }) +} + func TestAI_Stop_SendsFinalReport(t *testing.T) { ch := make(chan progrep.ProgressReport, 64) reporter := NewLegacyProgressReporter(ch) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/resource/dependency.go new/nelm-1.24.3/pkg/resource/dependency.go --- old/nelm-1.24.1/pkg/resource/dependency.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/resource/dependency.go 2026-06-08 18:00:18.000000000 +0200 @@ -24,7 +24,7 @@ // Automatically detects internal dependencies on resources by examining specific fields in the // resource spec. As an example, examining "envFrom" in a Pod container spec produces an internal // dependency on a ConfigMap or a Secret. -func internalDeployDependencies(unstruct *unstructured.Unstructured) []*InternalDependency { +func internalDeployDependencies(unstruct *unstructured.Unstructured, otherUnstructs []*unstructured.Unstructured) []*InternalDependency { gvk := unstruct.GroupVersionKind() gk := gvk.GroupKind() @@ -32,41 +32,41 @@ switch gk { case schema.GroupKind{Kind: "Deployment", Group: "apps"}: if pod, found := nestedMap(unstruct.Object, "spec", "template"); found { - if deps, found := parsePod(unstruct, pod); found { + if deps, found := parsePod(unstruct, pod, otherUnstructs); found { dependencies = append(dependencies, deps...) } } case schema.GroupKind{Kind: "CronJob", Group: "batch"}: if pod, found := nestedMap(unstruct.Object, "spec", "jobTemplate", "spec", "template"); found { - if deps, found := parsePod(unstruct, pod); found { + if deps, found := parsePod(unstruct, pod, otherUnstructs); found { dependencies = append(dependencies, deps...) } } case schema.GroupKind{Kind: "DaemonSet", Group: "apps"}: if pod, found := nestedMap(unstruct.Object, "spec", "template"); found { - if deps, found := parsePod(unstruct, pod); found { + if deps, found := parsePod(unstruct, pod, otherUnstructs); found { dependencies = append(dependencies, deps...) } } case schema.GroupKind{Kind: "Job", Group: "batch"}: if pod, found := nestedMap(unstruct.Object, "spec", "template"); found { - if deps, found := parsePod(unstruct, pod); found { + if deps, found := parsePod(unstruct, pod, otherUnstructs); found { dependencies = append(dependencies, deps...) } } case schema.GroupKind{Kind: "Pod", Group: ""}: - if deps, found := parsePod(unstruct, unstruct.Object); found { + if deps, found := parsePod(unstruct, unstruct.Object, otherUnstructs); found { dependencies = append(dependencies, deps...) } case schema.GroupKind{Kind: "ReplicaSet", Group: "apps"}: if pod, found := nestedMap(unstruct.Object, "spec", "template"); found { - if deps, found := parsePod(unstruct, pod); found { + if deps, found := parsePod(unstruct, pod, otherUnstructs); found { dependencies = append(dependencies, deps...) } } case schema.GroupKind{Kind: "ReplicationController", Group: ""}: if pod, found := nestedMap(unstruct.Object, "spec", "template"); found { - if deps, found := parsePod(unstruct, pod); found { + if deps, found := parsePod(unstruct, pod, otherUnstructs); found { dependencies = append(dependencies, deps...) } } @@ -76,7 +76,7 @@ } if pod, found := nestedMap(unstruct.Object, "spec", "template"); found { - if deps, found := parsePod(unstruct, pod); found { + if deps, found := parsePod(unstruct, pod, otherUnstructs); found { dependencies = append(dependencies, deps...) } } @@ -84,12 +84,16 @@ if dep, found := parseRoleRef(*unstruct); found { dependencies = append(dependencies, dep) } + + if deps, found := parseBindingSubjects(unstruct); found { + dependencies = append(dependencies, deps...) + } } return dependencies } -func parsePod(unstruct *unstructured.Unstructured, pod interface{}) (dependencies []*InternalDependency, found bool) { +func parsePod(unstruct *unstructured.Unstructured, pod interface{}, otherUnstructs []*unstructured.Unstructured) (dependencies []*InternalDependency, found bool) { containers, _ := nestedSlice(pod, "spec", "containers") for _, container := range containers { if deps, found := parseContainer(unstruct, container); found { @@ -145,6 +149,10 @@ dependencies = append(dependencies, dep) } + if deps, found := parseBindingDependenciesForWorkload(pod, otherUnstructs); found { + dependencies = append(dependencies, deps...) + } + volumes, _ := nestedSlice(pod, "spec", "volumes") for _, volume := range volumes { if dep, found := parseVolume(unstruct, volume); found { @@ -205,6 +213,81 @@ return dependencies, len(dependencies) > 0 } +func parseBindingDependenciesForWorkload(pod interface{}, otherUnstructs []*unstructured.Unstructured) (dependencies []*InternalDependency, found bool) { + serviceAccountName, _ := nestedStringNotEmpty(pod, "spec", "serviceAccountName") + if serviceAccountName == "" { + serviceAccountName, _ = nestedStringNotEmpty(pod, "spec", "serviceAccount") + } + + if serviceAccountName == "" { + return nil, false + } + + bindingUnstructs := lo.Filter(otherUnstructs, func(u *unstructured.Unstructured, _ int) bool { + switch u.GroupVersionKind().GroupKind() { + case schema.GroupKind{Kind: "RoleBinding", Group: "rbac.authorization.k8s.io"}, + schema.GroupKind{Kind: "ClusterRoleBinding", Group: "rbac.authorization.k8s.io"}: + return true + default: + return false + } + }) + + for _, bindingUnstruct := range bindingUnstructs { + subjects, _ := nestedSlice(bindingUnstruct.Object, "subjects") + for _, subj := range subjects { + kind, ok := nestedString(subj, "kind") + if !ok || kind != "ServiceAccount" { + continue + } + + name, ok := nestedString(subj, "name") + if !ok || name != serviceAccountName { + continue + } + + dep := &InternalDependency{ + ResourceMatcher: newExactResourceMatcher(bindingUnstruct), + ResourceState: common.ResourceStatePresent, + } + dependencies = append(dependencies, dep) + + break + } + } + + return dependencies, len(dependencies) > 0 +} + +func parseBindingSubjects(unstruct *unstructured.Unstructured) (dependencies []*InternalDependency, found bool) { + subjects, _ := nestedSlice(unstruct.Object, "subjects") + for _, subj := range subjects { + kind, found := nestedString(subj, "kind") + if !found || kind != "ServiceAccount" { + continue + } + + serviceAccountName, found := nestedString(subj, "name") + if !found { + continue + } + + dep := &InternalDependency{ + ResourceMatcher: &spec.ResourceMatcher{ + Names: []string{serviceAccountName}, + Namespaces: []string{unstruct.GetNamespace()}, + Groups: []string{""}, + Kinds: []string{"ServiceAccount"}, + }, + ResourceState: common.ResourceStatePresent, + } + + dependencies = append(dependencies, dep) + } + + return dependencies, len(dependencies) > 0 +} + func parseCRD(unstruct *unstructured.Unstructured) (dep *InternalDependency, found bool) { kind, found := nestedString(unstruct.Object, "spec", "names", "kind") if !found { @@ -597,6 +680,30 @@ nestedPath = []string{"spec", "template", "spec", "serviceAccountName"} case schema.GroupKind{Kind: "CronJob", Group: "batch"}: nestedPath = []string{"spec", "jobTemplate", "spec", "template", "spec", "serviceAccountName"} + case schema.GroupKind{Kind: "ClusterRoleBinding", Group: "rbac.authorization.k8s.io"}, + schema.GroupKind{Kind: "RoleBinding", Group: "rbac.authorization.k8s.io"}: + subjects, _ := nestedSlice(otherUnstruct.Object, "subjects") + for _, subj := range subjects { + kind, ok := nestedString(subj, "kind") + if !ok || kind != "ServiceAccount" { + continue + } + + name, ok := nestedString(subj, "name") + if !ok || name != unstruct.GetName() { + continue + } + + dep := &InternalDependency{ + ResourceMatcher: newExactResourceMatcher(otherUnstruct), + ResourceState: common.ResourceStateAbsent, + } + dependencies = append(dependencies, dep) + + break + } + + continue default: continue } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/resource/helpers_ai_test.go new/nelm-1.24.3/pkg/resource/helpers_ai_test.go --- old/nelm-1.24.1/pkg/resource/helpers_ai_test.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/resource/helpers_ai_test.go 2026-06-08 18:00:18.000000000 +0200 @@ -103,7 +103,7 @@ unstruct := &unstructured.Unstructured{Object: obj} resSpec := spec.NewResourceSpec(unstruct, releaseNamespace, spec.ResourceSpecOptions{}) - instRes, err := resource.NewInstallableResource(resSpec, releaseNamespace, nil, resource.InstallableResourceOptions{}) + instRes, err := resource.NewInstallableResource(resSpec, nil, releaseNamespace, nil, resource.InstallableResourceOptions{}) require.NoError(t, err) return instRes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/resource/metadata.go new/nelm-1.24.3/pkg/resource/metadata.go --- old/nelm-1.24.1/pkg/resource/metadata.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/resource/metadata.go 2026-06-08 18:00:18.000000000 +0200 @@ -69,7 +69,7 @@ func deployConditions(meta *spec.ResourceMeta, hasManualInternalDeps bool) map[common.On][]common.Stage { if conditions := deployConditionsForAnnotation(meta, common.AnnotationKeyPatternDeployOn); len(conditions) > 0 { - if spec.IsCRD(meta.GroupVersionKind.GroupKind()) { + if spec.IsCRD(meta.GroupVersionKind.GroupKind()) || spec.IsNamespace(meta.GroupVersionKind) { for on := range conditions { conditions[on] = []common.Stage{common.StagePrePreInstall} } @@ -80,7 +80,7 @@ if spec.IsHook(meta.Annotations) { if conditions := deployConditionsForAnnotation(meta, common.AnnotationKeyPatternHook); len(conditions) > 0 { - if spec.IsCRD(meta.GroupVersionKind.GroupKind()) { + if spec.IsCRD(meta.GroupVersionKind.GroupKind()) || spec.IsNamespace(meta.GroupVersionKind) { for on := range conditions { conditions[on] = []common.Stage{common.StagePrePreInstall} } @@ -90,6 +90,14 @@ } } + if spec.IsNamespace(meta.GroupVersionKind) { + return map[common.On][]common.Stage{ + common.InstallOnInstall: {common.StagePrePreInstall}, + common.InstallOnUpgrade: {common.StagePrePreInstall}, + common.InstallOnRollback: {common.StagePrePreInstall}, + } + } + if spec.IsCRD(meta.GroupVersionKind.GroupKind()) { return map[common.On][]common.Stage{ common.InstallOnInstall: {common.StagePrePreInstall}, @@ -711,7 +719,11 @@ return regexByContainer } -func skipLogs(meta *spec.ResourceMeta) bool { +func skipLogs(meta *spec.ResourceMeta, noPodLogs bool) bool { + if noPodLogs { + return true + } + _, value, found := spec.FindAnnotationOrLabelByKeyPattern(meta.Annotations, common.AnnotationKeyPatternSkipLogs) if !found { return false @@ -1458,7 +1470,7 @@ return nil } - if spec.IsCRD(meta.GroupVersionKind.GroupKind()) { + if spec.IsCRD(meta.GroupVersionKind.GroupKind()) || spec.IsNamespace(meta.GroupVersionKind) { return lo.ToPtr(0) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/resource/resource.go new/nelm-1.24.3/pkg/resource/resource.go --- old/nelm-1.24.1/pkg/resource/resource.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/resource/resource.go 2026-06-08 18:00:18.000000000 +0200 @@ -54,7 +54,7 @@ // Construct an InstallableResource from a ResourceSpec. Must never contact the cluster, because // this is called even when no cluster access allowed. -func NewInstallableResource(res *spec.ResourceSpec, releaseNamespace string, clientFactory kube.ClientFactorier, opts InstallableResourceOptions) (*InstallableResource, error) { +func NewInstallableResource(res *spec.ResourceSpec, otherResSpecs []*spec.ResourceSpec, releaseNamespace string, clientFactory kube.ClientFactorier, opts InstallableResourceOptions) (*InstallableResource, error) { if err := validateHook(res.ResourceMeta); err != nil { return nil, fmt.Errorf("validate hook configuration: %w", err) } @@ -114,9 +114,13 @@ manIntDeps := manualInternalDeployDependencies(res.ResourceMeta) + otherUnstructs := lo.Map(otherResSpecs, func(resSpec *spec.ResourceSpec, _ int) *unstructured.Unstructured { + return resSpec.Unstruct + }) + return &InstallableResource{ ResourceSpec: res, - AutoInternalDependencies: internalDeployDependencies(res.Unstruct), + AutoInternalDependencies: internalDeployDependencies(res.Unstruct, otherUnstructs), DefaultReplicasOnCreation: defaultReplicasOnCreation(res.ResourceMeta, releaseNamespace), DeleteOnFailed: deleteOnFailed(res.ResourceMeta), DeleteOnSucceeded: deleteOnSucceeded(res.ResourceMeta), @@ -137,7 +141,7 @@ ShowLogsOnlyForContainers: showLogsOnlyForContainers(res.ResourceMeta), ShowLogsOnlyForNumberOfReplicas: showLogsOnlyForNumberOfReplicas(res.ResourceMeta), ShowServiceMessages: showServiceMessages(res.ResourceMeta), - SkipLogs: skipLogs(res.ResourceMeta), + SkipLogs: skipLogs(res.ResourceMeta, opts.NoPodLogs), SkipLogsForContainers: skipLogsForContainers(res.ResourceMeta), SkipLogsRegex: skipLogRegex(res.ResourceMeta), SkipLogsRegexForContainers: skipLogRegexesForContainers(res.ResourceMeta), @@ -148,6 +152,7 @@ type InstallableResourceOptions struct { DefaultDeletePropagation metav1.DeletionPropagation + NoPodLogs bool Remote bool } @@ -213,6 +218,7 @@ type BuildResourcesOptions struct { DefaultDeletePropagation metav1.DeletionPropagation + NoPodLogs bool Remote bool } @@ -231,8 +237,9 @@ var prevRelInstResources []*InstallableResource for _, resSpec := range prevRelResSpecs { - installableResource, err := NewInstallableResource(resSpec, releaseNamespace, clientFactory, InstallableResourceOptions{ + installableResource, err := NewInstallableResource(resSpec, lo.Without(prevRelResSpecs, resSpec), releaseNamespace, clientFactory, InstallableResourceOptions{ DefaultDeletePropagation: opts.DefaultDeletePropagation, + NoPodLogs: opts.NoPodLogs, Remote: opts.Remote, }) if err != nil { @@ -244,8 +251,9 @@ var newRelInstResources []*InstallableResource for _, resSpec := range newRelResSpecs { - installableResource, err := NewInstallableResource(resSpec, releaseNamespace, clientFactory, InstallableResourceOptions{ + installableResource, err := NewInstallableResource(resSpec, lo.Without(newRelResSpecs, resSpec), releaseNamespace, clientFactory, InstallableResourceOptions{ DefaultDeletePropagation: opts.DefaultDeletePropagation, + NoPodLogs: opts.NoPodLogs, Remote: opts.Remote, }) if err != nil { @@ -340,8 +348,9 @@ FilePath: instRes.FilePath, }) - instRes, err = NewInstallableResource(resSpec, releaseNamespace, clientFactory, InstallableResourceOptions{ + instRes, err = NewInstallableResource(resSpec, newRelResSpecs, releaseNamespace, clientFactory, InstallableResourceOptions{ DefaultDeletePropagation: opts.DefaultDeletePropagation, + NoPodLogs: opts.NoPodLogs, Remote: opts.Remote, }) if err != nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/resource/resource_test.go new/nelm-1.24.3/pkg/resource/resource_test.go --- old/nelm-1.24.1/pkg/resource/resource_test.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/resource/resource_test.go 2026-06-08 18:00:18.000000000 +0200 @@ -389,9 +389,10 @@ }, input: func() *spec.ResourceSpec { resSpec := defaultResourceSpec(s.releaseNamespace) - resSpec.SetAnnotations(lo.Assign(resSpec.Annotations, map[string]string{ - "werf.io/deploy-dependency-backend": "state=present,name=backend", - }, + resSpec.SetAnnotations(lo.Assign( + resSpec.Annotations, map[string]string{ + "werf.io/deploy-dependency-backend": "state=present,name=backend", + }, )) return resSpec @@ -506,6 +507,212 @@ }, name: `for Deployment resource with auto internal dependency on configmap`, }, + { + expect: func(resSpec *spec.ResourceSpec) *resource.InstallableResource { + res := defaultInstallableResource(resSpec) + res.AutoInternalDependencies = []*resource.InternalDependency{ + { + ResourceMatcher: &spec.ResourceMatcher{ + Names: []string{"test-cr"}, + Groups: []string{"rbac.authorization.k8s.io"}, + Kinds: []string{"ClusterRole"}, + }, + ResourceState: common.ResourceStatePresent, + }, + { + ResourceMatcher: &spec.ResourceMatcher{ + Names: []string{"test-sa"}, + Namespaces: []string{""}, + Groups: []string{""}, + Kinds: []string{"ServiceAccount"}, + }, + ResourceState: common.ResourceStatePresent, + }, + } + + return res + }, + input: func() *spec.ResourceSpec { + return spec.NewResourceSpec(&unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": map[string]interface{}{ + "name": "test-crb", + }, + "subjects": []interface{}{ + map[string]interface{}{ + "kind": "ServiceAccount", + "name": "test-sa", + "namespace": "test-namespace", + }, + }, + "roleRef": map[string]interface{}{ + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "test-cr", + }, + }, + }, s.releaseNamespace, spec.ResourceSpecOptions{}) + }, + name: `for ClusterRoleBinding resource with auto internal dependency on ServiceAccount from subjects`, + }, + { + expect: func(resSpec *spec.ResourceSpec) *resource.InstallableResource { + res := defaultDeploymentInstallableResource(resSpec) + res.AutoInternalDependencies = []*resource.InternalDependency{ + { + ResourceMatcher: &spec.ResourceMatcher{ + Names: []string{"my-sa"}, + Namespaces: []string{""}, + Groups: []string{""}, + Kinds: []string{"ServiceAccount"}, + }, + ResourceState: common.ResourceStatePresent, + }, + { + ResourceMatcher: &spec.ResourceMatcher{ + Names: []string{"my-rb"}, + Namespaces: []string{""}, + Groups: []string{"rbac.authorization.k8s.io"}, + Kinds: []string{"RoleBinding"}, + }, + ResourceState: common.ResourceStatePresent, + }, + } + + return res + }, + input: func() *spec.ResourceSpec { + resSpec := defaultDeploymentResourceSpec(s.releaseNamespace) + err := unstructured.SetNestedField(resSpec.Unstruct.UnstructuredContent(), "my-sa", "spec", "template", "spec", "serviceAccountName") + s.Require().NoError(err) + + return resSpec + }, + name: `for Deployment with auto dependency on RoleBinding via shared ServiceAccount`, + otherInput: func() []*spec.ResourceSpec { + return []*spec.ResourceSpec{ + spec.NewResourceSpec(&unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": map[string]interface{}{ + "name": "my-rb", + "namespace": "test-namespace", + }, + "subjects": []interface{}{ + map[string]interface{}{ + "kind": "ServiceAccount", + "name": "my-sa", + "namespace": "test-namespace", + }, + }, + "roleRef": map[string]interface{}{ + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "my-role", + }, + }, + }, s.releaseNamespace, spec.ResourceSpecOptions{}), + } + }, + }, + { + expect: func(resSpec *spec.ResourceSpec) *resource.InstallableResource { + res := defaultDeploymentInstallableResource(resSpec) + res.AutoInternalDependencies = []*resource.InternalDependency{ + { + ResourceMatcher: &spec.ResourceMatcher{ + Names: []string{"my-sa"}, + Namespaces: []string{""}, + Groups: []string{""}, + Kinds: []string{"ServiceAccount"}, + }, + ResourceState: common.ResourceStatePresent, + }, + { + ResourceMatcher: &spec.ResourceMatcher{ + Names: []string{"my-crb"}, + Namespaces: []string{""}, + Groups: []string{"rbac.authorization.k8s.io"}, + Kinds: []string{"ClusterRoleBinding"}, + }, + ResourceState: common.ResourceStatePresent, + }, + } + + return res + }, + input: func() *spec.ResourceSpec { + resSpec := defaultDeploymentResourceSpec(s.releaseNamespace) + err := unstructured.SetNestedField(resSpec.Unstruct.UnstructuredContent(), "my-sa", "spec", "template", "spec", "serviceAccountName") + s.Require().NoError(err) + + return resSpec + }, + name: `for Deployment with auto dependency on ClusterRoleBinding via shared ServiceAccount`, + otherInput: func() []*spec.ResourceSpec { + return []*spec.ResourceSpec{ + spec.NewResourceSpec(&unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": map[string]interface{}{ + "name": "my-crb", + }, + "subjects": []interface{}{ + map[string]interface{}{ + "kind": "ServiceAccount", + "name": "my-sa", + "namespace": "test-namespace", + }, + }, + "roleRef": map[string]interface{}{ + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "my-cr", + }, + }, + }, s.releaseNamespace, spec.ResourceSpecOptions{}), + } + }, + }, + { + expect: func(resSpec *spec.ResourceSpec) *resource.InstallableResource { + return defaultDeploymentInstallableResource(resSpec) + }, + input: func() *spec.ResourceSpec { + return defaultDeploymentResourceSpec(s.releaseNamespace) + }, + name: `for Deployment with no explicit ServiceAccount produces no binding dependency`, + otherInput: func() []*spec.ResourceSpec { + return []*spec.ResourceSpec{ + spec.NewResourceSpec(&unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "RoleBinding", + "metadata": map[string]interface{}{ + "name": "my-rb", + "namespace": "test-namespace", + }, + "subjects": []interface{}{ + map[string]interface{}{ + "kind": "ServiceAccount", + "name": "other-sa", + "namespace": "test-namespace", + }, + }, + "roleRef": map[string]interface{}{ + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "my-role", + }, + }, + }, s.releaseNamespace, spec.ResourceSpecOptions{}), + } + }, + }, } for _, tc := range testCases { @@ -996,10 +1203,11 @@ } type installableResourceTestCase struct { - expect func(resSpec *spec.ResourceSpec) *resource.InstallableResource - input func() *spec.ResourceSpec - name string - skip bool + expect func(resSpec *spec.ResourceSpec) *resource.InstallableResource + input func() *spec.ResourceSpec + name string + otherInput func() []*spec.ResourceSpec + skip bool } type DeletableResourceSuite struct { @@ -1016,6 +1224,68 @@ } } +func (s *DeletableResourceSuite) TestNewDeletableResourceForAutoDependencies() { + testCases := []deletableResourceTestCase{ + { + expectFunc: func(resSpec *spec.ResourceSpec) *resource.DeletableResource { + res := defaultDeletableResource(resSpec.ResourceMeta) + res.AutoInternalDependencies = []*resource.InternalDependency{ + { + ResourceMatcher: &spec.ResourceMatcher{ + Names: []string{"test-crb"}, + Namespaces: []string{""}, + Groups: []string{"rbac.authorization.k8s.io"}, + Kinds: []string{"ClusterRoleBinding"}, + }, + ResourceState: common.ResourceStateAbsent, + }, + } + + return res + }, + inputFunc: func() *spec.ResourceSpec { + return spec.NewResourceSpec(&unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": map[string]interface{}{ + "name": "test-sa", + }, + }, + }, s.releaseNamespace, spec.ResourceSpecOptions{}) + }, + name: "for ServiceAccount resource with auto internal delete dependency on ClusterRoleBinding from subjects", + otherInputSpec: []*spec.ResourceSpec{ + spec.NewResourceSpec(&unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "rbac.authorization.k8s.io/v1", + "kind": "ClusterRoleBinding", + "metadata": map[string]interface{}{ + "name": "test-crb", + }, + "subjects": []interface{}{ + map[string]interface{}{ + "kind": "ServiceAccount", + "name": "test-sa", + "namespace": "test-namespace", + }, + }, + "roleRef": map[string]interface{}{ + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "test-cr", + }, + }, + }, s.releaseNamespace, spec.ResourceSpecOptions{}), + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, runDeletableResourceTest(tc, s)) + } +} + func (s *DeletableResourceSuite) TestNewDeletableResourceForDefaults() { testCases := []deletableResourceTestCase{ { @@ -1087,10 +1357,11 @@ } type deletableResourceTestCase struct { - expectFunc func(resSpec *spec.ResourceSpec) *resource.DeletableResource - inputFunc func() *spec.ResourceSpec - name string - skip bool + expectFunc func(resSpec *spec.ResourceSpec) *resource.DeletableResource + inputFunc func() *spec.ResourceSpec + name string + otherInputSpec []*spec.ResourceSpec + skip bool } func TestResourceSuites(t *testing.T) { @@ -1161,6 +1432,11 @@ res := defaultInstallableResource(resSpec) res.Ownership = common.OwnershipAnyone res.KeepOnDelete = true + res.DeployConditions = map[common.On][]common.Stage{ + common.InstallOnInstall: {common.StagePrePreInstall}, + common.InstallOnUpgrade: {common.StagePrePreInstall}, + common.InstallOnRollback: {common.StagePrePreInstall}, + } return res } @@ -1269,7 +1545,12 @@ resSpec := tc.inputFunc() - res := resource.NewDeletableResource(resSpec, []*spec.ResourceSpec{}, s.releaseNamespace, resource.DeletableResourceOptions{}) + otherSpecs := tc.otherInputSpec + if otherSpecs == nil { + otherSpecs = []*spec.ResourceSpec{} + } + + res := resource.NewDeletableResource(resSpec, otherSpecs, s.releaseNamespace, resource.DeletableResourceOptions{}) expectRes := tc.expectFunc(resSpec) @@ -1287,7 +1568,12 @@ resSpec := tc.input() - res, err := resource.NewInstallableResource(resSpec, s.releaseNamespace, s.clientFactory, resource.InstallableResourceOptions{}) + var otherResSpecs []*spec.ResourceSpec + if tc.otherInput != nil { + otherResSpecs = tc.otherInput() + } + + res, err := resource.NewInstallableResource(resSpec, otherResSpecs, s.releaseNamespace, s.clientFactory, resource.InstallableResourceOptions{}) s.Require().NoError(err) expectRes := tc.expect(resSpec) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/pkg/resource/spec/util.go new/nelm-1.24.3/pkg/resource/spec/util.go --- old/nelm-1.24.1/pkg/resource/spec/util.go 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/pkg/resource/spec/util.go 2026-06-08 18:00:18.000000000 +0200 @@ -91,6 +91,10 @@ } } +func IsNamespace(gvk schema.GroupVersionKind) bool { + return gvk.Group == "" && gvk.Kind == "Namespace" +} + func IsReleaseNamespace(resourceName string, resourceGVK schema.GroupVersionKind, releaseNamespace string) bool { return resourceGVK.Group == "" && resourceGVK.Kind == "Namespace" && resourceName == releaseNamespace } Binary files old/nelm-1.24.1/resources/images/nelm-logo.png and new/nelm-1.24.3/resources/images/nelm-logo.png differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nelm-1.24.1/trdl_channels.yaml new/nelm-1.24.3/trdl_channels.yaml --- old/nelm-1.24.1/trdl_channels.yaml 2026-05-12 15:56:02.000000000 +0200 +++ new/nelm-1.24.3/trdl_channels.yaml 2026-06-08 18:00:18.000000000 +0200 @@ -2,12 +2,12 @@ - name: "1" channels: - name: alpha - version: 1.24.0 + version: 1.24.1 - name: beta - version: 1.23.2 + version: 1.24.0 - name: ea - version: 1.22.0 + version: 1.23.2 - name: stable version: 1.22.0 - name: rock-solid - version: 1.21.0 + version: 1.22.0 ++++++ nelm.obsinfo ++++++ --- /var/tmp/diff_new_pack.uxAAih/_old 2026-07-06 12:36:56.681023248 +0200 +++ /var/tmp/diff_new_pack.uxAAih/_new 2026-07-06 12:36:56.689023526 +0200 @@ -1,5 +1,5 @@ name: nelm -version: 1.24.1 -mtime: 1778594162 -commit: 9366af9135202b7d705c71fef6c7dbe0231c0345 +version: 1.24.3 +mtime: 1780934418 +commit: c0c3c7ab1457fbf634acee9b2d2a29b9cdc101d4 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/nelm/vendor.tar.gz /work/SRC/openSUSE:Factory/.nelm.new.1982/vendor.tar.gz differ: char 13, line 1
