From 3cd78150a523574c0ee7c6d723bdd314889525b9 Mon Sep 17 00:00:00 2001 From: Christer Edvartsen Date: Tue, 7 Oct 2025 12:31:08 +0200 Subject: [PATCH 1/2] feat: generate links in tables for some commands This is currently using an unreleased version of pterm, so should not be merged until a new release has been generated. --- go.mod | 3 +- go.sum | 6 ++-- internal/naisapi/command/status.go | 36 +++++++++++++------ internal/naisapi/command/team.go | 56 +++++++++++++++++++++++------- internal/naisapi/command/teams.go | 33 ++++++++++++------ 5 files changed, 97 insertions(+), 37 deletions(-) diff --git a/go.mod b/go.mod index f0b7402d..86660d51 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,8 @@ require ( github.com/nais/liberator v0.0.0-20250924103433-536eaed90405 github.com/nais/naistrix v0.6.0 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/pterm/pterm v0.12.81 + github.com/pterm/pterm v0.12.82-0.20251001224657-d5706ff0d019 + github.com/savioxavier/termlink v1.4.3 github.com/sethvargo/go-retry v0.3.0 github.com/stretchr/testify v1.11.1 github.com/suessflorian/gqlfetch v0.7.0 diff --git a/go.sum b/go.sum index 8dfdaa91..44d48a40 100644 --- a/go.sum +++ b/go.sum @@ -421,8 +421,8 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= -github.com/pterm/pterm v0.12.81 h1:ju+j5I2++FO1jBKMmscgh5h5DPFDFMB7epEjSoKehKA= -github.com/pterm/pterm v0.12.81/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/TakajZCw= +github.com/pterm/pterm v0.12.82-0.20251001224657-d5706ff0d019 h1:wgAHvozUSe4+RfK9qd+cnlmDJa3CNGnfz6vIMrmNfmA= +github.com/pterm/pterm v0.12.82-0.20251001224657-d5706ff0d019/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/TakajZCw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -434,6 +434,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= +github.com/savioxavier/termlink v1.4.3 h1:Gh6vrG7jSn21cRiYdQqFXYcdXfM+Fg14aG487JTfKpA= +github.com/savioxavier/termlink v1.4.3/go.mod h1:5T5ePUlWbxCHIwyF8/Ez1qufOoGM89RCg9NvG+3G3gc= github.com/securego/gosec/v2 v2.22.9 h1:njwnorLl1pJMkwaymi1iyWDy8xeaVUByW4oteJzYNHc= github.com/securego/gosec/v2 v2.22.9/go.mod h1:x3qEF4J5bkDFIm8siAwsYZ40Uu5tD4JWpfVDPx3P3+0= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= diff --git a/internal/naisapi/command/status.go b/internal/naisapi/command/status.go index a87126b6..6f4da38b 100644 --- a/internal/naisapi/command/status.go +++ b/internal/naisapi/command/status.go @@ -12,8 +12,18 @@ import ( "github.com/nais/cli/internal/naisapi/gql" "github.com/nais/naistrix" "github.com/nais/naistrix/output" + "github.com/savioxavier/termlink" ) +type team struct { + Slug string `json:"slug"` + Url string `json:"url"` +} + +func (t team) String() string { + return termlink.Link(t.Slug, t.Url) +} + type workload struct { Kind string `json:"kind"` Name string `json:"name"` @@ -37,9 +47,8 @@ func (f workloadsWithIssues) String() string { return strings.TrimRight(issues, "\n") } -type team struct { - // TODO: Once https://github.com/pterm/pterm/issues/697 is resolved, we can use a link to Console instead of just the slug. - Slug string `json:"slug"` +type statusEntry struct { + Team team `json:"team"` Workloads int `json:"workloads"` NotNais int `heading:"Not Nais" json:"notNais"` Issues workloadsWithIssues `heading:"Critical Issues" json:"failing"` @@ -52,13 +61,17 @@ func statusCommand(parentFlags *flag.Api) *naistrix.Command { Title: "Get a quick overview of the status of your teams.", Flags: flags, RunFunc: func(ctx context.Context, out naistrix.Output, _ []string) error { - var teams []team + user, err := naisapi.GetAuthenticatedUser(ctx) + if err != nil { + return err + } ret, err := naisapi.GetStatus(ctx, flags) if err != nil { return err } + var entries []statusEntry for _, t := range ret { workloadsWithCriticalIssues := make([]gql.TeamStatusMeUserTeamsTeamMemberConnectionNodesTeamMemberTeamWorkloadsWorkloadConnectionNodesWorkload, 0) for _, w := range t.Team.Workloads.Nodes { @@ -67,8 +80,11 @@ func statusCommand(parentFlags *flag.Api) *naistrix.Command { } } - n := team{ - Slug: t.Team.Slug, + n := statusEntry{ + Team: team{ + Slug: t.Team.Slug, + Url: fmt.Sprintf("https://%s/team/%s", user.ConsoleHost(), t.Team.Slug), + }, Workloads: t.Team.Workloads.PageInfo.TotalCount, NotNais: len(workloadsWithCriticalIssues), Issues: make(workloadsWithIssues, 0), @@ -84,19 +100,19 @@ func statusCommand(parentFlags *flag.Api) *naistrix.Command { } n.Issues = append(n.Issues, a) } - teams = append(teams, n) + entries = append(entries, n) } - if len(teams) == 0 { + if len(entries) == 0 { out.Println("No teams found.") return nil } if flags.Output == "json" { - return out.JSON(output.JSONWithPrettyOutput()).Render(teams) + return out.JSON(output.JSONWithPrettyOutput()).Render(entries) } - return out.Table().Render(teams) + return out.Table().Render(entries) }, } } diff --git a/internal/naisapi/command/team.go b/internal/naisapi/command/team.go index bd36dfc1..6a04c630 100644 --- a/internal/naisapi/command/team.go +++ b/internal/naisapi/command/team.go @@ -2,6 +2,7 @@ package command import ( "context" + "fmt" "strings" "github.com/nais/cli/internal/naisapi" @@ -9,9 +10,19 @@ import ( "github.com/nais/cli/internal/naisapi/gql" "github.com/nais/naistrix" "github.com/nais/naistrix/output" + "github.com/savioxavier/termlink" "k8s.io/utils/strings/slices" ) +type teamWorkload struct { + Name string `json:"name"` + Url string `json:"url"` +} + +func (tw teamWorkload) String() string { + return termlink.Link(tw.Name, tw.Url) +} + func teamCommand(parentFlags *flag.Api) *naistrix.Command { flags := &flag.Team{Api: parentFlags} return &naistrix.Command{ @@ -282,14 +293,18 @@ func listWorkloads(parentFlags *flag.Team) *naistrix.Command { }, Flags: flags, RunFunc: func(ctx context.Context, out naistrix.Output, args []string) error { - // TODO: Once pterm/pterm#697 is resolved, we can use a link to Console instead of just the workload name. - type workload struct { - Name string `json:"name"` - Environment string `json:"environment"` - Type string `json:"type"` - State string `json:"state"` - Vulnerabilities int `json:"vulnerabilities"` - Issues int `heading:"Critical Issues" json:"issues"` + user, err := naisapi.GetAuthenticatedUser(ctx) + if err != nil { + return err + } + + type entry struct { + Workload teamWorkload `json:"workload"` + Environment string `json:"environment"` + Type string `json:"type"` + State string `json:"state"` + Vulnerabilities int `json:"vulnerabilities"` + Issues int `heading:"Critical Issues" json:"issues"` } teamSlug := args[0] @@ -298,7 +313,7 @@ func listWorkloads(parentFlags *flag.Team) *naistrix.Command { return err } - workloads := make([]workload, len(ret)) + entries := make([]entry, len(ret)) for i, w := range ret { state := "(unknown)" switch actual := w.(type) { @@ -308,8 +323,23 @@ func listWorkloads(parentFlags *flag.Team) *naistrix.Command { state = string(actual.GetJobState()) } - workloads[i] = workload{ - Name: w.GetName(), + workloadType := "app" + if w.GetTypename() == "Job" { + workloadType = "job" + } + + entries[i] = entry{ + Workload: teamWorkload{ + Name: w.GetName(), + Url: fmt.Sprintf( + "https://%s/team/%s/%s/%s/%s", + user.ConsoleHost(), + teamSlug, + w.GetTeamEnvironment().Environment.Name, + workloadType, + w.GetName(), + ), + }, Environment: w.GetTeamEnvironment().Environment.Name, Type: w.GetTypename(), State: state, @@ -319,7 +349,7 @@ func listWorkloads(parentFlags *flag.Team) *naistrix.Command { } if flags.Output == "json" { - return out.JSON(output.JSONWithPrettyOutput()).Render(workloads) + return out.JSON(output.JSONWithPrettyOutput()).Render(entries) } if len(ret) == 0 { @@ -327,7 +357,7 @@ func listWorkloads(parentFlags *flag.Team) *naistrix.Command { return nil } - return out.Table().Render(workloads) + return out.Table().Render(entries) }, AutoCompleteFunc: func(ctx context.Context, _ []string, toComplete string) ([]string, string) { if len(toComplete) < 2 { diff --git a/internal/naisapi/command/teams.go b/internal/naisapi/command/teams.go index a745fe92..aa089071 100644 --- a/internal/naisapi/command/teams.go +++ b/internal/naisapi/command/teams.go @@ -2,6 +2,7 @@ package command import ( "context" + "fmt" "github.com/nais/cli/internal/naisapi" "github.com/nais/cli/internal/naisapi/command/flag" @@ -20,13 +21,17 @@ func teamsCommand(parentFlags *flag.Api) *naistrix.Command { Title: "Get a list of your teams.", Flags: flags, RunFunc: func(ctx context.Context, out naistrix.Output, _ []string) error { - // TODO: Once https://github.com/pterm/pterm/issues/697 is resolved, we can use a link to Console instead of just the slug. - type team struct { - Slug string `json:"slug"` + user, err := naisapi.GetAuthenticatedUser(ctx) + if err != nil { + return err + } + + type entry struct { + Team team `json:"team"` Description string `json:"description"` } - var teams []team + var entries []entry if flags.All { ret, err := naisapi.GetAllTeams(ctx) @@ -35,8 +40,11 @@ func teamsCommand(parentFlags *flag.Api) *naistrix.Command { } for _, t := range ret.Teams.Nodes { - teams = append(teams, team{ - Slug: t.Slug, + entries = append(entries, entry{ + Team: team{ + Slug: t.Slug, + Url: fmt.Sprintf("https://%s/team/%s", user.ConsoleHost(), t.Slug), + }, Description: t.Purpose, }) } @@ -47,23 +55,26 @@ func teamsCommand(parentFlags *flag.Api) *naistrix.Command { } for _, t := range userTeams { - teams = append(teams, team{ - Slug: t.Team.Slug, + entries = append(entries, entry{ + Team: team{ + Slug: t.Team.Slug, + Url: fmt.Sprintf("https://%s/team/%s", user.ConsoleHost(), t.Team.Slug), + }, Description: t.Team.Purpose, }) } } if flags.Output == "json" { - return out.JSON(output.JSONWithPrettyOutput()).Render(teams) + return out.JSON(output.JSONWithPrettyOutput()).Render(entries) } - if len(teams) == 0 { + if len(entries) == 0 { out.Println("No teams found.") return nil } - return out.Table().Render(teams) + return out.Table().Render(entries) }, } } From 0406651360da03ef17d89aad101b4992705d1558 Mon Sep 17 00:00:00 2001 From: Christer Edvartsen Date: Fri, 17 Oct 2025 08:27:54 +0200 Subject: [PATCH 2/2] build: bump to latest github.com/pterm/pterm --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 86660d51..c9acae56 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/nais/liberator v0.0.0-20250924103433-536eaed90405 github.com/nais/naistrix v0.6.0 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/pterm/pterm v0.12.82-0.20251001224657-d5706ff0d019 + github.com/pterm/pterm v0.12.82 github.com/savioxavier/termlink v1.4.3 github.com/sethvargo/go-retry v0.3.0 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 44d48a40..59e97659 100644 --- a/go.sum +++ b/go.sum @@ -421,8 +421,8 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= -github.com/pterm/pterm v0.12.82-0.20251001224657-d5706ff0d019 h1:wgAHvozUSe4+RfK9qd+cnlmDJa3CNGnfz6vIMrmNfmA= -github.com/pterm/pterm v0.12.82-0.20251001224657-d5706ff0d019/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/TakajZCw= +github.com/pterm/pterm v0.12.82 h1:+D9wYhCaeaK0FIQoZtqbNQuNpe2lB2tajKKsTd5paVQ= +github.com/pterm/pterm v0.12.82/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/TakajZCw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=