Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f8ca033
feat(app): improve environment-aware autocomplete
rbjornstad Mar 17, 2026
7329849
feat(job): improve environment-aware autocomplete
rbjornstad Mar 17, 2026
e5fe2f0
fix(issues): add -e shorthand for environment filter
rbjornstad Mar 17, 2026
41eba83
feat(kafka): filter environment autocomplete by topics
rbjornstad Mar 17, 2026
d328200
feat(opensearch): filter environment autocomplete by instances
rbjornstad Mar 17, 2026
86c1ecb
feat(valkey): filter environment autocomplete by instances
rbjornstad Mar 17, 2026
ec5d29a
feat(secret): improve environment-aware UX and activity ordering
rbjornstad Mar 17, 2026
cd2a210
refactor: apply fix/fmt cleanups for autocomplete helpers
rbjornstad Mar 17, 2026
b2ff21c
fix: address copilot review feedback on autocomplete parsing
rbjornstad Mar 17, 2026
e2cb983
fix: address remaining Copilot feedback
rbjornstad Mar 17, 2026
a61d3a9
fix: apply go fix for reflect.Pointer on go1.26
rbjornstad Mar 17, 2026
7867517
fix: remove duplicate genqlient operation blocks
rbjornstad Mar 17, 2026
6d462d7
refactor: deduplicate team flag parsing in autocomplete
rbjornstad Mar 17, 2026
744d3e1
fix: reorder import statements for consistency
rbjornstad Mar 17, 2026
3ac9a14
fix: address latest Copilot feedback
rbjornstad Mar 17, 2026
875d284
fix: remove unused secret environment resolver
rbjornstad Mar 17, 2026
4e2caa3
fix: tighten autocomplete subcommand path detection
rbjornstad Mar 17, 2026
58d6ad4
fix: handle flags in autocomplete argv parsing
rbjornstad Mar 17, 2026
73f9137
fix: finalize strict env requirement for secrets get
rbjornstad Mar 17, 2026
3e3bc8f
refactor: harden autocomplete team and command parsing
rbjornstad Mar 17, 2026
12206d9
refactor: improve autocomplete fallback and arg parsing
rbjornstad Mar 17, 2026
60f555e
refactor: standardize formatting in TestPositionalArgAfterSubcommand
rbjornstad Mar 17, 2026
f143ec9
refactor: remove duplicate env and team helper logic
rbjornstad Mar 17, 2026
9110d77
fix: use CLI team fallback in secret get env completion
rbjornstad Mar 17, 2026
09cbc2b
fix: improve env completion fallbacks and guidance text
rbjornstad Mar 18, 2026
4b429a1
fix: go mod tidy
rbjornstad Mar 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ require (
golang.org/x/crypto v0.48.0
golang.org/x/oauth2 v0.34.0
golang.org/x/sync v0.19.0
golang.org/x/term v0.40.0
google.golang.org/api v0.246.0
google.golang.org/grpc v1.78.0
gopkg.in/yaml.v3 v3.0.1
Expand Down Expand Up @@ -243,7 +244,6 @@ require (
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.42.0 // indirect
Expand Down
43 changes: 43 additions & 0 deletions internal/app/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package app
import (
"context"
"fmt"
"sort"

"github.com/nais/cli/internal/naisapi"
"github.com/nais/cli/internal/naisapi/gql"
Expand Down Expand Up @@ -109,3 +110,45 @@ func ApplicationEnvironments(ctx context.Context, team, appName string) ([]strin
}
return ret, nil
}

func TeamApplicationEnvironments(ctx context.Context, team string) ([]string, error) {
_ = `# @genqlient
query TeamApplicationEnvironments($team: Slug!) {
team(slug: $team) {
applications(first: 1000) {
nodes {
teamEnvironment {
environment {
name
}
}
}
}
}
}
`

client, err := naisapi.GraphqlClient(ctx)
if err != nil {
return nil, err
}

resp, err := gql.TeamApplicationEnvironments(ctx, client, team)
if err != nil {
return nil, err
}

seen := make(map[string]struct{})
ret := make([]string, 0)
for _, node := range resp.Team.Applications.Nodes {
env := node.TeamEnvironment.Environment.Name
if _, ok := seen[env]; ok {
continue
}
seen[env] = struct{}{}
ret = append(ret, env)
}

sort.Strings(ret)
return ret, nil
}
100 changes: 92 additions & 8 deletions internal/app/command/flag/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package flag
import (
"context"
"fmt"
"os"
"time"

activityutil "github.com/nais/cli/internal/activity"
"github.com/nais/cli/internal/app"
"github.com/nais/cli/internal/cliflags"
"github.com/nais/cli/internal/flags"
"github.com/nais/cli/internal/naisapi"
"github.com/nais/cli/internal/naisapi/gql"
Expand All @@ -20,11 +22,89 @@ type App struct {
type Environments []string

func (e *Environments) AutoComplete(ctx context.Context, args *naistrix.Arguments, str string, flags any) ([]string, string) {
envs, err := naisapi.GetAllEnvironments(ctx)
team := appTeamFromFlags(flags)
if cliTeam := cliflags.FirstFlagValue(os.Args, "-t", "--team"); cliTeam != "" {
team = cliTeam
}
if team == "" {
return autoCompleteEnvironments(ctx)
}

if appName := appNameForEnvironmentCompletion(args); appName != "" {
envs, err := app.ApplicationEnvironments(ctx, team, appName)
if err == nil && len(envs) > 0 {
return envs, "Available environments"
}
}

envs, err := app.TeamApplicationEnvironments(ctx, team)
if err != nil {
return nil, fmt.Sprintf("Failed to fetch environments for auto-completion: %v", err)
}
return envs, "Available environments"
if len(envs) > 0 {
return envs, "Available environments"
}

return nil, "No environments with applications found for this team."
}

func appTeamFromFlags(flags any) string {
switch f := flags.(type) {
case *Activity:
if f.App != nil && f.App.Team != "" {
return string(f.App.Team)
}
return string(f.Team)
case *Issues:
if f.App != nil && f.App.Team != "" {
return string(f.App.Team)
}
return string(f.Team)
case *List:
if f.App != nil && f.App.Team != "" {
return string(f.App.Team)
}
return string(f.Team)
case *Restart:
return string(f.Team)
case *App:
return string(f.Team)
default:
return ""
}
}

func appNameForEnvironmentCompletion(args *naistrix.Arguments) string {
if args.Len() > 0 {
if name := args.Get("name"); name != "" {
return name
}
}

// Some command/flag combinations (for example app restart with parent sticky flags)
// do not expose positional args through naistrix during completion.
if !isRestartCompletionFromCLIArgs() {
return ""
}

return appNameFromCLIArgs(os.Args)
}

func isRestartCompletionFromCLIArgs() bool {
return cliflags.HasSubCommandPathWithValueFlags(
os.Args,
"app",
[]string{"-t", "--team", "-e", "--environment", "--config"},
"restart",
)
}

func appNameFromCLIArgs(argv []string) string {
return cliflags.PositionalArgAfterSubcommand(
argv,
"restart",
[]string{"-t", "--team", "-e", "--environment", "--config"},
)
}

type instances []string
Expand All @@ -40,7 +120,7 @@ func (i *instances) AutoComplete(ctx context.Context, args *naistrix.Arguments,
}

if len(f.Team) == 0 {
return nil, "Please provide team to auto-complete instances. 'nais config team set <team>', or '--team <team>' flag."
return nil, "Please provide team to auto-complete instances. 'nais config set team <team>', or '--team <team>' flag."
}

instances, err := app.GetApplicationInstances(ctx, string(f.Team), args.Get("name"), string(f.Environment))
Expand Down Expand Up @@ -87,13 +167,17 @@ func (a *ActivityTypes) AutoComplete(context.Context, *naistrix.Arguments, strin
type Env string

func (e *Env) AutoComplete(ctx context.Context, args *naistrix.Arguments, str string, flags any) ([]string, string) {
if args.Len() == 0 {
return autoCompleteEnvironments(ctx)
}

f := flags.(*Log)
if len(f.Team) == 0 {
return nil, "Please provide team to auto-complete environments. 'nais config team set <team>', or '--team <team>' flag."
return nil, "Please provide team to auto-complete environments. 'nais config set team <team>', or '--team <team>' flag."
}

if args.Len() == 0 {
envs, err := app.TeamApplicationEnvironments(ctx, f.Team)
if err == nil && len(envs) > 0 {
return envs, "Available environments"
}
return autoCompleteEnvironments(ctx)
}

envs, err := app.ApplicationEnvironments(ctx, f.Team, args.Get("name"))
Expand Down
9 changes: 8 additions & 1 deletion internal/app/command/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package command

import (
"context"
"os"

"github.com/nais/cli/internal/app"
"github.com/nais/cli/internal/app/command/flag"
"github.com/nais/cli/internal/cliflags"
"github.com/nais/naistrix"
"github.com/nais/naistrix/output"
)
Expand Down Expand Up @@ -42,7 +44,12 @@ func issues(parentFlags *flag.App) *naistrix.Command {
if len(flags.Team) == 0 {
return nil, "Please provide team to auto-complete application names. 'nais config set team <team>', or '--team <team>' flag."
}
apps, err := app.GetApplicationNames(ctx, flags.Team, flags.Environment)
environments := []string(flags.Environment)
if len(environments) == 0 {
environments = cliflags.UniqueFlagValues(os.Args, "-e", "--environment")
}

apps, err := app.GetApplicationNames(ctx, flags.Team, environments)
if err != nil {
return nil, "Unable to fetch application names."
}
Expand Down
24 changes: 24 additions & 0 deletions internal/app/command/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package command

import (
"context"
"fmt"
"os"

"github.com/nais/cli/internal/app"
"github.com/nais/cli/internal/app/command/flag"
"github.com/nais/cli/internal/cliflags"
"github.com/nais/naistrix"
)

Expand All @@ -20,6 +23,27 @@ func restart(parentFlags *flag.App) *naistrix.Command {
Args: []naistrix.Argument{
{Name: "name"},
},
AutoCompleteFunc: func(ctx context.Context, args *naistrix.Arguments, _ string) ([]string, string) {
if args.Len() != 0 {
return nil, ""
}
if len(flags.Team) == 0 {
return nil, "Please provide team to auto-complete application names. 'nais config set team <team>', or '--team <team>' flag."
}
envs := []string(flags.Environment)
if len(envs) != 1 {
envs = cliflags.UniqueFlagValues(os.Args, "-e", "--environment")
}
if len(envs) != 1 {
return nil, "Please provide exactly one environment to auto-complete application names. '--environment <environment>' flag."
}

apps, err := app.GetApplicationNames(ctx, flags.Team, envs)
if err != nil {
return nil, fmt.Sprintf("Unable to fetch application names: %v", err)
}
return apps, "Select an application."
},
RunFunc: func(ctx context.Context, args *naistrix.Arguments, out *naistrix.OutputWriter) error {
ret, err := app.RestartApp(ctx, flags.Team, args.Get("name"), flags.Environment)
if err != nil {
Expand Down
Loading
Loading