Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kl for openSUSE:Factory checked in at 2026-04-18 21:38:56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kl (Old) and /work/SRC/openSUSE:Factory/.kl.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kl" Sat Apr 18 21:38:56 2026 rev:6 rq:1347856 version:0.9.1 Changes: -------- --- /work/SRC/openSUSE:Factory/kl/kl.changes 2026-04-13 23:19:47.754375395 +0200 +++ /work/SRC/openSUSE:Factory/.kl.new.11940/kl.changes 2026-04-18 21:38:57.675934541 +0200 @@ -1,0 +2,7 @@ +Fri Apr 17 20:02:02 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 0.9.1: + * fix: keep terminated containers and logs until manually + deselected + +------------------------------------------------------------------- Old: ---- kl-0.9.0.obscpio New: ---- kl-0.9.1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kl.spec ++++++ --- /var/tmp/diff_new_pack.i1BtJ1/_old 2026-04-18 21:38:58.731977786 +0200 +++ /var/tmp/diff_new_pack.i1BtJ1/_new 2026-04-18 21:38:58.735977950 +0200 @@ -17,7 +17,7 @@ Name: kl -Version: 0.9.0 +Version: 0.9.1 Release: 0 Summary: An interactive Kubernetes log viewer for your terminal License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.i1BtJ1/_old 2026-04-18 21:38:58.771979425 +0200 +++ /var/tmp/diff_new_pack.i1BtJ1/_new 2026-04-18 21:38:58.775979588 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/robinovitch61/kl.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">refs/tags/v0.9.0</param> + <param name="revision">refs/tags/v0.9.1</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.i1BtJ1/_old 2026-04-18 21:38:58.799980571 +0200 +++ /var/tmp/diff_new_pack.i1BtJ1/_new 2026-04-18 21:38:58.807980899 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/robinovitch61/kl.git</param> - <param name="changesrevision">46461cf69f1c723ade8d5425567f4640eedb3ba4</param></service></servicedata> + <param name="changesrevision">6b0fe84afedeab1d40b5a82402a361eb07ba3152</param></service></servicedata> (No newline at EOF) ++++++ kl-0.9.0.obscpio -> kl-0.9.1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kl-0.9.0/internal/app.go new/kl-0.9.1/internal/app.go --- old/kl-0.9.0/internal/app.go 2026-04-13 04:18:04.000000000 +0200 +++ new/kl-0.9.1/internal/app.go 2026-04-13 20:13:48.000000000 +0200 @@ -958,8 +958,8 @@ return m, nil } - var err error var newLogs []model.PageLog + var err error for i := range msg.NewLogs { shortName := k8s_model.ContainerNameAndPrefix{} if m.containerToShortName != nil { @@ -1068,6 +1068,7 @@ case entity.StopScannerKeepLogs: cmds = append(cmds, command.StopLogScannerCmd(ent, true)) case entity.RemoveEntity: + m = m.removeLogsForContainer(ent.Container) m.entityTree.Remove(ent) case entity.RemoveLogs: m = m.removeLogsForContainer(ent.Container) @@ -1101,16 +1102,13 @@ } func (m Model) updateShortNamesInBuffer() (Model, error) { - bufferedLogs := m.pageLogBuffer - m.pageLogBuffer = nil - for i := range bufferedLogs { - short, err := m.containerToShortName(bufferedLogs[i].Log.Container) + for i := range m.pageLogBuffer { + shortName, err := m.containerToShortName(m.pageLogBuffer[i].Log.Container) if err != nil { return m, err } - bufferedLogs[i].ContainerNames.Short = short + m.pageLogBuffer[i].ContainerNames.Short = shortName } - m.pageLogBuffer = bufferedLogs return m, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kl-0.9.0/internal/k8s/entity/entity.go new/kl-0.9.1/internal/k8s/entity/entity.go --- old/kl-0.9.0/internal/k8s/entity/entity.go 2026-04-13 04:18:04.000000000 +0200 +++ new/kl-0.9.1/internal/k8s/entity/entity.go 2026-04-13 20:13:48.000000000 +0200 @@ -165,7 +165,7 @@ tree.AddOrReplace(e) return e, tree, []EntityAction{StopScanner} case Deleted: - return e, tree, []EntityAction{RemoveLogs, RemoveEntity} + return e, tree, []EntityAction{RemoveEntity} default: panic(fmt.Sprintf("Deactivate called for entity in %v state", e.State)) } @@ -194,24 +194,25 @@ }() switch e.State { case Inactive: - tree.Remove(e) - return e, tree, []EntityAction{} + return e, tree, []EntityAction{RemoveEntity} case WantScanning: e.State = Deleted e.Container.Status = delta.Container.Status tree.AddOrReplace(e) return e, tree, []EntityAction{} case ScannerStarting: - tree.Remove(e) - return e, tree, []EntityAction{StopScanner} + return e, tree, []EntityAction{RemoveEntity, StopScanner} case Scanning: e.State = Deleted e.Container.Status = delta.Container.Status tree.AddOrReplace(e) return e, tree, []EntityAction{StopScannerKeepLogs, MarkLogsTerminated} case ScannerStopping: - tree.Remove(e) - return e, tree, []EntityAction{StopScanner} + return e, tree, []EntityAction{RemoveEntity} + case Deleted: + e.Container.Status = delta.Container.Status + tree.AddOrReplace(e) + return e, tree, []EntityAction{} default: panic(fmt.Sprintf("Delete called for entity in %v state", e.State)) } @@ -268,11 +269,18 @@ return e, tree, actions case Scanning: if e.Container.Status.State == container.ContainerTerminated { - e.State = WantScanning + e.State = Deleted tree.AddOrReplace(e) actions = append(actions, StopScannerKeepLogs) } return e, tree, actions + case Deleted: + if e.Container.Status.State == container.ContainerRunning { + e.State = ScannerStarting + tree.AddOrReplace(e) + actions = append(actions, StartScanner) + } + return e, tree, actions default: // an entity in any other state has its container updated and remains in the same entity state return e, tree, actions @@ -327,6 +335,11 @@ e.LogScanner = nil tree.AddOrReplace(e) return e, tree, []EntityAction{} + case ScannerStarting, Scanning: + // Old scanner stopped after entity was reactivated with a new scanner + // (e.g. Deleted → ScannerStarting via Update). Don't modify state or + // LogScanner as they pertain to the new scanner. + return e, tree, []EntityAction{} default: panic(fmt.Sprintf("ScannerStopped called for entity in %v state", e.State)) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kl-0.9.0/internal/k8s/entity/entity_test.go new/kl-0.9.1/internal/k8s/entity/entity_test.go --- old/kl-0.9.0/internal/k8s/entity/entity_test.go 2026-04-13 04:18:04.000000000 +0200 +++ new/kl-0.9.1/internal/k8s/entity/entity_test.go 2026-04-13 20:13:48.000000000 +0200 @@ -174,22 +174,7 @@ _, _, actions := ent.Deactivate(tree) - if len(actions) != 2 { - t.Fatalf("expected 2 actions, got %d: %v", len(actions), actions) - } - hasRemoveLogs := false - hasRemoveEntity := false - for _, a := range actions { - if a == entity.RemoveLogs { - hasRemoveLogs = true - } - if a == entity.RemoveEntity { - hasRemoveEntity = true - } - } - if !hasRemoveLogs || !hasRemoveEntity { - t.Errorf("expected RemoveLogs and RemoveEntity, got %v", actions) - } + assertActions(t, actions, []entity.EntityAction{entity.RemoveEntity}) } func TestDeactivate_FromInvalidState_Panics(t *testing.T) { @@ -240,10 +225,9 @@ _, _, actions := ent.Delete(tree, newTestDelta(container.ContainerTerminated, true, false)) - assertActions(t, actions, []entity.EntityAction{}) - // entity should be removed from tree - if tree.GetEntity(ent.Container) != nil { - t.Error("entity should have been removed from tree") + assertActions(t, actions, []entity.EntityAction{entity.RemoveEntity}) + if tree.GetEntity(ent.Container) == nil { + t.Error("entity should still be in tree") } } @@ -269,9 +253,24 @@ _, _, actions := ent.Delete(tree, newTestDelta(container.ContainerTerminated, true, false)) - assertActions(t, actions, []entity.EntityAction{entity.StopScanner}) - if tree.GetEntity(ent.Container) != nil { - t.Error("entity should have been removed from tree") + if len(actions) != 2 { + t.Fatalf("expected 2 actions, got %d: %v", len(actions), actions) + } + hasRemoveEntity := false + hasStopScanner := false + for _, a := range actions { + if a == entity.RemoveEntity { + hasRemoveEntity = true + } + if a == entity.StopScanner { + hasStopScanner = true + } + } + if !hasRemoveEntity || !hasStopScanner { + t.Errorf("expected RemoveEntity and StopScanner, got %v", actions) + } + if tree.GetEntity(ent.Container) == nil { + t.Error("entity should still be in tree") } } @@ -308,9 +307,9 @@ _, _, actions := ent.Delete(tree, newTestDelta(container.ContainerTerminated, true, false)) - assertActions(t, actions, []entity.EntityAction{entity.StopScanner}) - if tree.GetEntity(ent.Container) != nil { - t.Error("entity should have been removed from tree") + assertActions(t, actions, []entity.EntityAction{entity.RemoveEntity}) + if tree.GetEntity(ent.Container) == nil { + t.Error("entity should still be in tree") } } @@ -327,6 +326,20 @@ } } +func TestDelete_FromDeleted(t *testing.T) { + tree := newTestTree() + ent := newTestEntity(entity.Deleted, container.ContainerTerminated) + tree.AddOrReplace(ent) + + result, _, actions := ent.Delete(tree, newTestDelta(container.ContainerTerminated, true, false)) + + assertState(t, result, entity.Deleted) + assertActions(t, actions, []entity.EntityAction{}) + if tree.GetEntity(ent.Container) == nil { + t.Error("entity should still be in tree") + } +} + // --- Create --- func TestCreate_ToActivate_RunningContainer(t *testing.T) { @@ -421,7 +434,7 @@ result, _, actions := ent.Update(tree, newTestDelta(container.ContainerTerminated, false, false)) - assertState(t, result, entity.WantScanning) + assertState(t, result, entity.Deleted) if len(actions) != 2 { t.Fatalf("expected 2 actions, got %d: %v", len(actions), actions) } @@ -475,6 +488,42 @@ } } +func TestUpdate_Deleted_ContainerRestarted(t *testing.T) { + tree := newTestTree() + ent := newTestEntity(entity.Deleted, container.ContainerTerminated) + tree.AddOrReplace(ent) + + result, _, actions := ent.Update(tree, newTestDelta(container.ContainerRunning, false, false)) + + assertState(t, result, entity.ScannerStarting) + assertActions(t, actions, []entity.EntityAction{entity.StartScanner}) +} + +func TestUpdate_Deleted_ContainerStillTerminated(t *testing.T) { + tree := newTestTree() + ent := newTestEntity(entity.Deleted, container.ContainerTerminated) + tree.AddOrReplace(ent) + + // Repeated terminated updates (common during rollout restarts) should NOT + // restart scanning — we already have the terminated logs. + result, _, actions := ent.Update(tree, newTestDelta(container.ContainerTerminated, false, false)) + + assertState(t, result, entity.Deleted) + // MarkLogsTerminated is expected (idempotent) because MayHaveLogs()=true for Deleted + assertActions(t, actions, []entity.EntityAction{entity.MarkLogsTerminated}) +} + +func TestUpdate_Deleted_ContainerStillWaiting(t *testing.T) { + tree := newTestTree() + ent := newTestEntity(entity.Deleted, container.ContainerTerminated) + tree.AddOrReplace(ent) + + result, _, actions := ent.Update(tree, newTestDelta(container.ContainerWaiting, false, false)) + + assertState(t, result, entity.Deleted) + assertActions(t, actions, []entity.EntityAction{}) +} + // --- ScannerStarted --- func TestScannerStarted_Success(t *testing.T) { @@ -597,8 +646,37 @@ } } +func TestScannerStopped_FromScannerStarting(t *testing.T) { + tree := newTestTree() + ent := newTestEntity(entity.ScannerStarting, container.ContainerRunning) + tree.AddOrReplace(ent) + + result, _, actions := ent.ScannerStopped(tree) + + // Old scanner stopped after entity was reactivated — no state change + assertState(t, result, entity.ScannerStarting) + assertActions(t, actions, []entity.EntityAction{}) +} + +func TestScannerStopped_FromScanning(t *testing.T) { + tree := newTestTree() + ent := newTestEntity(entity.Scanning, container.ContainerRunning) + scanner := newTestScanner() + ent.LogScanner = &scanner + tree.AddOrReplace(ent) + + result, _, actions := ent.ScannerStopped(tree) + + // Old scanner stopped after entity was reactivated — no state change, keep LogScanner + assertState(t, result, entity.Scanning) + assertActions(t, actions, []entity.EntityAction{}) + if result.LogScanner == nil { + t.Error("expected LogScanner to be preserved (it belongs to the new scanner)") + } +} + func TestScannerStopped_FromInvalidState_Panics(t *testing.T) { - for _, state := range []entity.EntityState{entity.Inactive, entity.ScannerStarting, entity.Scanning} { + for _, state := range []entity.EntityState{entity.Inactive} { t.Run(state.String(), func(t *testing.T) { tree := newTestTree() ent := newTestEntity(state, container.ContainerRunning) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kl-0.9.0/internal/k8s/entity/entity_tree.go new/kl-0.9.1/internal/k8s/entity/entity_tree.go --- old/kl-0.9.0/internal/k8s/entity/entity_tree.go 2026-04-13 04:18:04.000000000 +0200 +++ new/kl-0.9.1/internal/k8s/entity/entity_tree.go 2026-04-13 20:13:48.000000000 +0200 @@ -653,10 +653,9 @@ toJoin = append(toJoin, v) } } - name := k8s_model.ContainerNameAndPrefix{ + return k8s_model.ContainerNameAndPrefix{ Prefix: strings.Join(toJoin, "/"), ContainerName: container.Name, - } - return name, nil + }, nil } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kl-0.9.0/internal/k8s/entity/entity_tree_test.go new/kl-0.9.1/internal/k8s/entity/entity_tree_test.go --- old/kl-0.9.0/internal/k8s/entity/entity_tree_test.go 2026-04-13 04:18:04.000000000 +0200 +++ new/kl-0.9.1/internal/k8s/entity/entity_tree_test.go 2026-04-13 20:13:48.000000000 +0200 @@ -588,7 +588,7 @@ for c, short := range expected { n, err := f(c) if err != nil { - t.Errorf("Expected no error, got %v", err) + t.Fatalf("Unexpected error for container %s: %v", c.Name, err) } if n != short { t.Errorf("Expected short name %s, got %s", short, n) @@ -644,9 +644,11 @@ }, } compare(f, expected) + + // looking up a container not in the tree should return an error _, err := f(container.Container{Name: "doesntexist"}) if err == nil { - t.Errorf("Expected error, got nil") + t.Errorf("Expected error for nonexistent container, got nil") } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kl-0.9.0/internal/page/logs.go new/kl-0.9.1/internal/page/logs.go --- old/kl-0.9.0/internal/page/logs.go 2026-04-13 04:18:04.000000000 +0200 +++ new/kl-0.9.1/internal/page/logs.go 2026-04-13 20:13:48.000000000 +0200 @@ -301,11 +301,11 @@ func (p LogsPage) WithUpdatedShortNames(f func(container.Container) (k8s_model.ContainerNameAndPrefix, error)) (LogsPage, error) { allLogs := p.logContainer.GetOrderedLogs() for i := range allLogs { - short, err := f(allLogs[i].Log.Container) + shortName, err := f(allLogs[i].Log.Container) if err != nil { return p, err } - allLogs[i].ContainerNames.Short = short + allLogs[i].ContainerNames.Short = shortName allLogs[i].CurrentName = getContainerName(allLogs[i], nameFormats[p.nameFormatIdx]) } p.setLogs(allLogs) ++++++ kl.obsinfo ++++++ --- /var/tmp/diff_new_pack.i1BtJ1/_old 2026-04-18 21:38:59.159995314 +0200 +++ /var/tmp/diff_new_pack.i1BtJ1/_new 2026-04-18 21:38:59.167995641 +0200 @@ -1,5 +1,5 @@ name: kl -version: 0.9.0 -mtime: 1776046684 -commit: 46461cf69f1c723ade8d5425567f4640eedb3ba4 +version: 0.9.1 +mtime: 1776104028 +commit: 6b0fe84afedeab1d40b5a82402a361eb07ba3152 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/kl/vendor.tar.gz /work/SRC/openSUSE:Factory/.kl.new.11940/vendor.tar.gz differ: char 139, line 1
