diff --git a/apply/apply.go b/apply/apply.go index 5cb97e86..52153d02 100644 --- a/apply/apply.go +++ b/apply/apply.go @@ -3,6 +3,6 @@ package apply import "os" type Cmd struct { - Filename *os.File `short:"f" predictor:"file"` + Filename *os.File `short:"f" completion-predictor:"file"` FromFile fromFile `cmd:"" default:"1" name:"-f " help:"Apply any resource from a yaml or json file."` } diff --git a/auth/set_project.go b/auth/set_project.go index 6d13da00..7df361c7 100644 --- a/auth/set_project.go +++ b/auth/set_project.go @@ -13,7 +13,7 @@ import ( ) type SetProjectCmd struct { - Name string `arg:"" help:"Name of the default project to be used." predictor:"project_name"` + Name string `arg:"" help:"Name of the default project to be used." completion-predictor:"project_name"` } func (s *SetProjectCmd) Run(ctx context.Context, client *api.Client) error { diff --git a/copy/copy.go b/copy/copy.go index 82c51609..e39ef042 100644 --- a/copy/copy.go +++ b/copy/copy.go @@ -12,9 +12,9 @@ type Cmd struct { } type resourceCmd struct { - Name string `arg:"" help:"Name of the resource to copy." default:"" predictor:"resource_name"` + Name string `arg:"" help:"Name of the resource to copy." default:"" completion-predictor:"resource_name"` TargetName string `help:"Target name of the new resource. A random name is generated if omitted." default:""` - TargetProject string `help:"Target project of the new resource. The current project is used if omitted." default:"" predictor:"project_name"` + TargetProject string `help:"Target project of the new resource. The current project is used if omitted." default:"" completion-predictor:"project_name"` } func getName(name string) string { diff --git a/create/application.go b/create/application.go index 06024c59..1fca5da0 100644 --- a/create/application.go +++ b/create/application.go @@ -63,7 +63,7 @@ type gitConfig struct { Username *string `help:"Username to use when authenticating to the git repository over HTTPS." env:"GIT_USERNAME"` Password *string `help:"Password to use when authenticating to the git repository over HTTPS. In case of GitHub or GitLab, this can also be an access token." env:"GIT_PASSWORD"` SSHPrivateKey *string `help:"Private key in PEM format to connect to the git repository via SSH." env:"GIT_SSH_PRIVATE_KEY" xor:"SSH_KEY"` - SSHPrivateKeyFromFile *string `help:"Path to a file containing a private key in PEM format to connect to the git repository via SSH." env:"GIT_SSH_PRIVATE_KEY_FROM_FILE" xor:"SSH_KEY" predictor:"file"` + SSHPrivateKeyFromFile *string `help:"Path to a file containing a private key in PEM format to connect to the git repository via SSH." env:"GIT_SSH_PRIVATE_KEY_FROM_FILE" xor:"SSH_KEY" completion-predictor:"file"` } type healthProbe struct { diff --git a/create/cloudvm.go b/create/cloudvm.go index d3a8ed2b..68dfb3e0 100644 --- a/create/cloudvm.go +++ b/create/cloudvm.go @@ -28,9 +28,9 @@ type cloudVMCmd struct { BootDiskSize *resource.Quantity `default:"20Gi" help:"Configures the size of the boot disk."` Disks map[string]resource.Quantity `default:"" help:"Additional disks to mount to the machine."` PublicKeys []string `default:"" help:"SSH public keys to connect to the CloudVM as root. The keys are expected to be in SSH format as defined in RFC4253. Immutable after creation."` - PublicKeysFromFiles []*os.File `default:"" predictor:"file" help:"SSH public key files to connect to the VM as root. The keys are expected to be in SSH format as defined in RFC4253. Immutable after creation."` + PublicKeysFromFiles []*os.File `default:"" completion-predictor:"file" help:"SSH public key files to connect to the VM as root. The keys are expected to be in SSH format as defined in RFC4253. Immutable after creation."` CloudConfig string `default:"" help:"Pass custom cloud config data (https://cloudinit.readthedocs.io/en/latest/topics/format.html#cloud-config-data) to the cloud VM. If a CloudConfig is passed, the PublicKey parameter is ignored. Immutable after creation."` - CloudConfigFromFile *os.File `default:"" predictor:"file" help:"Pass custom cloud config data (https://cloudinit.readthedocs.io/en/latest/topics/format.html#cloud-config-data) from a file. Takes precedence. If a CloudConfig is passed, the PublicKey parameter is ignored. Immutable after creation."` + CloudConfigFromFile *os.File `default:"" completion-predictor:"file" help:"Pass custom cloud config data (https://cloudinit.readthedocs.io/en/latest/topics/format.html#cloud-config-data) from a file. Takes precedence. If a CloudConfig is passed, the PublicKey parameter is ignored. Immutable after creation."` } func (cmd *cloudVMCmd) Run(ctx context.Context, client *api.Client) error { diff --git a/create/create.go b/create/create.go index f8a2ceb8..f84c5e28 100644 --- a/create/create.go +++ b/create/create.go @@ -21,7 +21,7 @@ import ( ) type Cmd struct { - Filename *os.File `short:"f" help:"Create any resource from a yaml or json file." predictor:"file"` + Filename *os.File `short:"f" help:"Create any resource from a yaml or json file." completion-predictor:"file"` FromFile fromFile `cmd:"" default:"1" name:"-f " help:"Create any resource from a yaml or json file."` VCluster vclusterCmd `cmd:"" group:"infrastructure.nine.ch" name:"vcluster" help:"Create a new vcluster."` APIServiceAccount apiServiceAccountCmd `cmd:"" group:"iam.nine.ch" name:"apiserviceaccount" aliases:"asa" help:"Create a new API Service Account."` diff --git a/create/mysql.go b/create/mysql.go index 41c27153..c1dc8b84 100644 --- a/create/mysql.go +++ b/create/mysql.go @@ -25,7 +25,7 @@ type mySQLCmd struct { MachineType string `placeholder:"${mysql_machine_default}" help:"Sizing for a particular MySQL instance. Available types: ${mysql_machine_types}"` AllowedCidrs []meta.IPv4CIDR `placeholder:"203.0.113.1/32" help:"IP addresses allowed to connect to the instance."` SSHKeys []storage.SSHKey `help:"SSH public keys allowed to connect to the database server in order to up-/download and directly restore database backups."` - SSHKeysFile *os.File `predictor:"file" help:"Path to a file containing a list of SSH public keys (see above), separated by newlines. Lines prefixed with # are ignored."` + SSHKeysFile *os.File `completion-predictor:"file" help:"Path to a file containing a list of SSH public keys (see above), separated by newlines. Lines prefixed with # are ignored."` SQLMode *[]storage.MySQLMode `placeholder:"\"MODE1, MODE2, ...\"" help:"Configures the sql_mode setting. Modes affect the SQL syntax MySQL supports and the data validation checks it performs. Defaults to: ${mysql_mode}"` CharacterSetName string `placeholder:"${mysql_charset}" help:"Configures the character_set_server variable."` CharacterSetCollation string `placeholder:"${mysql_collation}" help:"Configures the collation_server variable."` diff --git a/create/postgres.go b/create/postgres.go index 5545b0dc..0f5422ba 100644 --- a/create/postgres.go +++ b/create/postgres.go @@ -24,7 +24,7 @@ type postgresCmd struct { MachineType string `placeholder:"${postgres_machine_default}" help:"Defines the sizing for a particular PostgreSQL instance. Available types: ${postgres_machine_types}"` AllowedCidrs []meta.IPv4CIDR `placeholder:"203.0.113.1/32" help:"IP addresses allowed to connect to the instance."` SSHKeys []storage.SSHKey `help:"SSH public keys allowed to connect to the database server in order to up-/download and directly restore database backups."` - SSHKeysFile *os.File `predictor:"file" help:"Path to a file containing a list of SSH public keys (see above), separated by newlines. Lines prefixed with # are ignored."` + SSHKeysFile *os.File `completion-predictor:"file" help:"Path to a file containing a list of SSH public keys (see above), separated by newlines. Lines prefixed with # are ignored."` PostgresVersion storage.PostgresVersion `placeholder:"${postgres_version_default}" help:"Release version with which the PostgreSQL instance is created. Available versions: ${postgres_versions}"` KeepDailyBackups *int `placeholder:"${postgres_backup_retention_days}" help:"Number of daily database backups to keep. Note that setting this to 0, backup will be disabled and existing dumps deleted immediately."` } diff --git a/delete/delete.go b/delete/delete.go index dbb217f2..1c04ba8a 100644 --- a/delete/delete.go +++ b/delete/delete.go @@ -13,7 +13,7 @@ import ( ) type Cmd struct { - Filename *os.File `short:"f" predictor:"file"` + Filename *os.File `short:"f" completion-predictor:"file"` FromFile fromFile `cmd:"" default:"1" name:"-f " help:"Delete any resource from a yaml or json file."` VCluster vclusterCmd `cmd:"" group:"infrastructure.nine.ch" name:"vcluster" help:"Delete a vcluster."` APIServiceAccount apiServiceAccountCmd `cmd:"" group:"iam.nine.ch" name:"apiserviceaccount" aliases:"asa" help:"Delete an API Service Account."` @@ -33,7 +33,7 @@ type Cmd struct { } type resourceCmd struct { - Name string `arg:"" predictor:"resource_name" help:"Name of the resource to delete."` + Name string `arg:"" completion-predictor:"resource_name" help:"Name of the resource to delete."` Force bool `default:"false" help:"Do not ask for confirmation of deletion."` Wait bool `default:"true" help:"Wait until resource is fully deleted."` WaitTimeout time.Duration `default:"5m" help:"Duration to wait for the deletion. Only relevant if wait is set."` diff --git a/edit/edit.go b/edit/edit.go index d213eccd..b765a611 100644 --- a/edit/edit.go +++ b/edit/edit.go @@ -39,7 +39,7 @@ type Cmd struct { } type resourceCmd struct { - Name string `arg:"" predictor:"resource_name" help:"Name of the resource to edit." required:""` + Name string `arg:"" completion-predictor:"resource_name" help:"Name of the resource to edit." required:""` } const header = `# Please edit the %s below. diff --git a/exec/exec.go b/exec/exec.go index a36bd3a6..ce0cdaf4 100644 --- a/exec/exec.go +++ b/exec/exec.go @@ -5,5 +5,5 @@ type Cmd struct { } type resourceCmd struct { - Name string `arg:"" predictor:"resource_name" help:"Name of the application to exec command/shell in." required:""` + Name string `arg:"" completion-predictor:"resource_name" help:"Name of the application to exec command/shell in." required:""` } diff --git a/get/get.go b/get/get.go index ebc5d34b..87adfb76 100644 --- a/get/get.go +++ b/get/get.go @@ -53,7 +53,7 @@ type output struct { } type resourceCmd struct { - Name string `arg:"" predictor:"resource_name" help:"Name of the resource to get. If omitted all in the project will be listed." default:""` + Name string `arg:"" completion-predictor:"resource_name" help:"Name of the resource to get. If omitted all in the project will be listed." default:""` } type outputFormat string diff --git a/go.mod b/go.mod index 11879b29..899e0edd 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/grafana/loki/v3 v3.6.3 github.com/hashicorp/go-multierror v1.1.1 github.com/int128/kubelogin v1.35.2 - github.com/jotaen/kong-completion v0.0.7 + github.com/jotaen/kong-completion v0.0.11 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de github.com/lucasepe/codename v0.2.1-0.20230220151621-5e31bf1e775f github.com/mattn/go-isatty v0.0.20 diff --git a/go.sum b/go.sum index 065d738c..2dce34bd 100644 --- a/go.sum +++ b/go.sum @@ -481,8 +481,8 @@ github.com/jaegertracing/jaeger-idl v0.6.0 h1:LOVQfVby9ywdMPI9n3hMwKbyLVV3BL1XH2 github.com/jaegertracing/jaeger-idl v0.6.0/go.mod h1:mpW0lZfG907/+o5w5OlnNnig7nHJGT3SfKmRqC42HGQ= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jotaen/kong-completion v0.0.7 h1:l2UrG51q0gWD8Ph0CykBvGOb1YlXDc789AQnWkq+U9M= -github.com/jotaen/kong-completion v0.0.7/go.mod h1:dtitX9zCkffI5AON0IKsqHOFEEaL/S2AudgzJfCVFA4= +github.com/jotaen/kong-completion v0.0.11 h1:ZRyQt+IwjcAObbiyxJZ3YR7r/o/f6HYidTK1+7YNtnE= +github.com/jotaen/kong-completion v0.0.11/go.mod h1:dyIG20e3qq128SUBtF8jzI7YtkfzjWMlgbqkAJd6xHQ= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= diff --git a/logs/logs.go b/logs/logs.go index 9c379756..2f077f9b 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -18,7 +18,7 @@ type Cmd struct { } type resourceCmd struct { - Name string `arg:"" predictor:"resource_name" help:"Name of the resource." default:""` + Name string `arg:"" completion-predictor:"resource_name" help:"Name of the resource." default:""` } type logsCmd struct { diff --git a/main.go b/main.go index d74d4892..65023a6b 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ import ( ) type flags struct { - Project string `help:"Limit commands to a specific project." short:"p" predictor:"project_name"` + Project string `help:"Limit commands to a specific project." short:"p" completion-predictor:"project_name"` APICluster string `help:"Context name of the API cluster." default:"${api_cluster}" env:"NCTL_API_CLUSTER" hidden:""` LogAPIAddress string `help:"Address of the deplo.io logging API server." default:"https://logs.deplo.io" env:"NCTL_LOG_ADDR" hidden:""` LogAPIInsecure bool `help:"Don't verify TLS connection to the logging API server." hidden:"" default:"false" env:"NCTL_LOG_INSECURE"` diff --git a/predictor/predictor.go b/predictor/predictor.go index 96e44a44..03d34dbf 100644 --- a/predictor/predictor.go +++ b/predictor/predictor.go @@ -126,12 +126,45 @@ func findProject(args complete.Args) (string, bool) { if args.LastCompleted == "-p" || args.LastCompleted == "--project" { return "", true } + + // try to find project in args.All first if p := findProjectInSlice(args.All); p != "" { return p, false } + + // Fall back to parsing COMP_LINE if args.All is empty. + // + // When completing positional arguments in nested subcommands like + // "nctl exec application ", the posener/complete library slices + // args.All as it descends through each subcommand (nctl -> exec -> + // application). By the time our predictor is called, args.All is empty + // because all arguments have been "consumed" by the subcommand matching. + // COMP_LINE always contains the full command line, so we use it as a + // fallback to find the --project flag. + if p := findProjectInSlice(argsFromENV()); p != "" { + return p, false + } + return "", false } +// argsFromENV returns all arguments in command line (not including the command itself) +// +// When completing positional arguments in nested subcommands like +// "nctl exec application ", the posener/complete library slices +// args.All as it descends through each subcommand (nctl -> exec -> +// application). By the time our predictor is called, args.All is empty +// because all arguments have been "consumed" by the subcommand matching. +// COMP_LINE always contains the full command line, so we use it as a +// fallback to find the --project flag. +func argsFromENV() []string { + if line := os.Getenv("COMP_LINE"); line != "" { + return strings.Fields(line) + } + + return nil +} + // findProjectInSlice searches for -p or --project flag and returns its value. func findProjectInSlice(args []string) string { for i, arg := range args { diff --git a/predictor/predictor_test.go b/predictor/predictor_test.go index 96f04015..73114292 100644 --- a/predictor/predictor_test.go +++ b/predictor/predictor_test.go @@ -1,6 +1,156 @@ package predictor -import "testing" +import ( + "bytes" + "strconv" + "testing" + + "github.com/posener/complete" +) + +func TestFindProjectWithCompleteLibrary(t *testing.T) { + tests := []struct { + name string + compLine string + wantProject string + }{ + { + name: "project flag before positional arg", + compLine: "nctl exec --project myproject application ", + wantProject: "myproject", + }, + { + name: "short project flag", + compLine: "nctl exec -p myproject application ", + wantProject: "myproject", + }, + { + name: "no project flag", + compLine: "nctl exec application ", + wantProject: "", + }, + { + name: "project flag after subcommand", + compLine: "nctl exec application --project otherproject ", + wantProject: "otherproject", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + capture := &capturePredictor{predictions: []string{"test-result"}} + + // Build a command structure similar to what kong-completion generates + // for "nctl exec application ". + cmd := complete.Command{ + Sub: map[string]complete.Command{ + "exec": { + Flags: map[string]complete.Predictor{ + "--project": complete.PredictAnything, + "-p": complete.PredictAnything, + }, + Sub: map[string]complete.Command{ + "application": { + Args: capture, + }, + }, + }, + }, + } + + // Simulate shell completion + t.Setenv("COMP_LINE", tt.compLine) + t.Setenv("COMP_POINT", strconv.Itoa(len(tt.compLine))) + + cmp := complete.New("nctl", cmd) + cmp.Out = &bytes.Buffer{} // discard output + cmp.Complete() + + if !capture.called { + t.Fatal("predictor was not called") + } + + gotProject, _ := findProject(capture.captured) + if gotProject != tt.wantProject { + t.Errorf("findProject() = %q, want %q", gotProject, tt.wantProject) + } + }) + } +} + +func TestFindProjectIncomplete(t *testing.T) { + tests := []struct { + name string + compLine string + wantIncomplete bool + }{ + { + name: "incomplete --project flag", + compLine: "nctl exec --project ", + wantIncomplete: true, + }, + { + name: "incomplete -p flag", + compLine: "nctl exec -p ", + wantIncomplete: true, + }, + { + name: "complete project flag", + compLine: "nctl exec --project myproject ", + wantIncomplete: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + capture := &capturePredictor{predictions: []string{}} + + // For incomplete flags, the completion happens at the exec level + // (completing the project name), not at the positional arg level. + cmd := complete.Command{ + Sub: map[string]complete.Command{ + "exec": { + Flags: map[string]complete.Predictor{ + "--project": capture, // capture here for incomplete flag tests + "-p": capture, + }, + Sub: map[string]complete.Command{ + "application": { + Args: capture, + }, + }, + }, + }, + } + + t.Setenv("COMP_LINE", tt.compLine) + t.Setenv("COMP_POINT", strconv.Itoa(len(tt.compLine))) + + cmp := complete.New("nctl", cmd) + cmp.Out = &bytes.Buffer{} + cmp.Complete() + + _, gotIncomplete := findProject(capture.captured) + if gotIncomplete != tt.wantIncomplete { + t.Errorf("findProject() incomplete = %v, want %v (LastCompleted=%q)", + gotIncomplete, tt.wantIncomplete, capture.captured.LastCompleted) + } + }) + } +} + +// capturePredictor is a test predictor that captures the args it receives. +type capturePredictor struct { + captured complete.Args + predictions []string + called bool +} + +func (c *capturePredictor) Predict(args complete.Args) []string { + c.captured = args + c.called = true + return c.predictions +} func TestFindProjectInSlice(t *testing.T) { tests := []struct { diff --git a/update/application.go b/update/application.go index ef5f997f..69a78309 100644 --- a/update/application.go +++ b/update/application.go @@ -71,7 +71,7 @@ type gitConfig struct { Username *string `help:"Username to use when authenticating to the git repository over HTTPS." env:"GIT_USERNAME"` Password *string `help:"Password to use when authenticating to the git repository over HTTPS. In case of GitHub or GitLab, this can also be an access token." env:"GIT_PASSWORD"` SSHPrivateKey *string `help:"Private key in x509 format to connect to the git repository via SSH." env:"GIT_SSH_PRIVATE_KEY"` - SSHPrivateKeyFromFile *string `help:"Path to a file containing a private key in PEM format to connect to the git repository via SSH." env:"GIT_SSH_PRIVATE_KEY_FROM_FILE" xor:"SSH_KEY" predictor:"file"` + SSHPrivateKeyFromFile *string `help:"Path to a file containing a private key in PEM format to connect to the git repository via SSH." env:"GIT_SSH_PRIVATE_KEY_FROM_FILE" xor:"SSH_KEY" completion-predictor:"file"` } func (g gitConfig) sshPrivateKey() (*string, error) { diff --git a/update/cloudvm.go b/update/cloudvm.go index dbd5a1e6..5e46aacd 100644 --- a/update/cloudvm.go +++ b/update/cloudvm.go @@ -26,7 +26,7 @@ type cloudVMCmd struct { Shutdown *bool `help:"Shuts down the CloudVM via ACPI."` BootRescue *bool `help:"Boot CloudVM into a live rescue environment."` RescuePublicKeys []string `placeholder:"ssh-ed25519" help:"SSH public keys that can be used to connect to the CloudVM while booted into rescue. The keys are expected to be in SSH format as defined in RFC4253."` - RescuePublicKeysFromFiles []string `placeholder:"~/.ssh/id_ed25519.pub" predictor:"file" help:"SSH public key files that can be used to connect to the CloudVM while booted into rescue. The keys are expected to be in SSH format as defined in RFC4253."` + RescuePublicKeysFromFiles []string `placeholder:"~/.ssh/id_ed25519.pub" completion-predictor:"file" help:"SSH public key files that can be used to connect to the CloudVM while booted into rescue. The keys are expected to be in SSH format as defined in RFC4253."` } func (cmd *cloudVMCmd) Run(ctx context.Context, client *api.Client) error { diff --git a/update/mysql.go b/update/mysql.go index 7406450e..8eebeeab 100644 --- a/update/mysql.go +++ b/update/mysql.go @@ -19,7 +19,7 @@ type mySQLCmd struct { MachineType *string `placeholder:"${mysql_machine_default}" help:"Defines the sizing for a particular MySQL instance. Available types: ${mysql_machine_types}"` AllowedCidrs *[]meta.IPv4CIDR `placeholder:"203.0.113.1/32" help:"Specifies the IP addresses allowed to connect to the instance."` SSHKeys []storage.SSHKey `help:"SSH public keys allowed to connect to the database server in order to up-/download and directly restore database backups."` - SSHKeysFile *os.File `predictor:"file" help:"Path to a file containing a list of SSH public keys (see above), separated by newlines. Lines prefixed with # are ignored."` + SSHKeysFile *os.File `completion-predictor:"file" help:"Path to a file containing a list of SSH public keys (see above), separated by newlines. Lines prefixed with # are ignored."` SQLMode *[]storage.MySQLMode `placeholder:"\"MODE1, MODE2, ...\"" help:"Configures the sql_mode setting. Modes affect the SQL syntax MySQL supports and the data validation checks it performs. Defaults to: ${mysql_mode}"` CharacterSetName *string `placeholder:"${mysql_charset}" help:"Configures the character_set_server variable."` CharacterSetCollation *string `placeholder:"${mysql_collation}" help:"Configures the collation_server variable."` diff --git a/update/postgres.go b/update/postgres.go index a632eddd..ea453d21 100644 --- a/update/postgres.go +++ b/update/postgres.go @@ -19,7 +19,7 @@ type postgresCmd struct { MachineType *string `placeholder:"${postgres_machine_default}" help:"Defines the sizing for a particular PostgreSQL instance. Available types: ${postgres_machine_types}"` AllowedCidrs *[]meta.IPv4CIDR `placeholder:"203.0.113.1/32" help:"Specifies the IP addresses allowed to connect to the instance."` SSHKeys []storage.SSHKey `help:"SSH public keys allowed to connect to the database server in order to up-/download and directly restore database backups."` - SSHKeysFile *os.File `predictor:"file" help:"Path to a file containing a list of SSH public keys (see above), separated by newlines. Lines prefixed with # are ignored."` + SSHKeysFile *os.File `completion-predictor:"file" help:"Path to a file containing a list of SSH public keys (see above), separated by newlines. Lines prefixed with # are ignored."` KeepDailyBackups *int `placeholder:"${postgres_backup_retention_days}" help:"Number of daily database backups to keep. Note that setting this to 0, backup will be disabled and existing dumps deleted immediately."` } diff --git a/update/update.go b/update/update.go index 63d92c90..2546aead 100644 --- a/update/update.go +++ b/update/update.go @@ -26,7 +26,7 @@ type Cmd struct { } type resourceCmd struct { - Name string `arg:"" predictor:"resource_name" help:"Name of the resource to update."` + Name string `arg:"" completion-predictor:"resource_name" help:"Name of the resource to update."` } type updater struct {