From 73933f7625b35d5f11f42d4cc9ceed3b9ca6fa04 Mon Sep 17 00:00:00 2001 From: "STeve (Xin) Huang" Date: Thu, 9 Oct 2025 10:44:45 -0400 Subject: [PATCH] fix valid until always show Xm0s, and use truncate instead of round --- tool/tsh/common/tsh.go | 29 +++++++++++++++++----------- tool/tsh/common/tsh_test.go | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index a55cd167898b0..43db25283b23a 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -5202,19 +5202,26 @@ func onShow(cf *CLIConf) error { return nil } +func humanFriendlyValidUntilDuration(validUntil time.Time, clock clockwork.Clock) string { + duration := clock.Until(validUntil) + switch { + case duration <= 0: + return "EXPIRED" + case duration < time.Minute: + return "valid for <1m" + default: + return fmt.Sprintf("valid for %s", + // Since duration is truncated to minute, duration.String() always + // ends with 0s. + strings.TrimRight(duration.Truncate(time.Minute).String(), "0s"), + ) + } +} + // printStatus prints the status of the profile. func printStatus(debug bool, p *profileInfo, env map[string]string, isActive bool) { + clock := clockwork.NewRealClock() var prefix string - humanDuration := "EXPIRED" - duration := time.Until(p.ValidUntil) - if duration.Nanoseconds() > 0 { - humanDuration = fmt.Sprintf("valid for %v", duration.Round(time.Minute)) - // If certificate is valid for less than a minute, display "<1m" instead of "0s". - if duration < time.Minute { - humanDuration = "valid for <1m" - } - } - proxyURL := p.getProxyURLLine(isActive, env) cluster := p.getClusterLine(isActive, env) kubeCluster := p.getKubeClusterLine(isActive, env) @@ -5290,7 +5297,7 @@ func printStatus(debug bool, p *profileInfo, env map[string]string, isActive boo if p.GitHubIdentity != nil { fmt.Printf(" GitHub username: %s\n", p.GitHubIdentity.Username) } - fmt.Printf(" Valid until: %v [%v]\n", p.ValidUntil, humanDuration) + fmt.Printf(" Valid until: %v [%v]\n", p.ValidUntil, humanFriendlyValidUntilDuration(p.ValidUntil, clock)) fmt.Printf(" Extensions: %v\n", strings.Join(p.Extensions, ", ")) if debug { diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index ce0a6b1daeecc..48e470a4ad64d 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -51,6 +51,7 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" otlp "go.opentelemetry.io/proto/otlp/trace/v1" @@ -7674,3 +7675,40 @@ func TestSSHForkAfterAuthentication(t *testing.T) { }) } } + +func Test_humanFriendlyValidUntilDuration(t *testing.T) { + clock := clockwork.NewFakeClock() + tests := []struct { + name string + input time.Time + expect string + }{ + { + name: "expired", + input: clock.Now().Add(-time.Minute), + expect: "EXPIRED", + }, + { + name: "less than one minute", + input: clock.Now().Add(time.Second * 30), + expect: "valid for <1m", + }, + { + name: "truncate", + input: clock.Now().Add(time.Minute*5 + time.Second*50), + expect: "valid for 5m", + }, + { + name: "hours", + input: clock.Now().Add(time.Hour*12 + time.Minute*34 + time.Second*10), + expect: "valid for 12h34m", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := humanFriendlyValidUntilDuration(test.input, clock) + require.Equal(t, test.expect, actual) + }) + } +}