Skip to content

Commit 6d2e563

Browse files
authored
Merge pull request #110 from fosrl/dev
0.14.0
2 parents 5ca974c + 2f97dd0 commit 6d2e563

16 files changed

Lines changed: 521 additions & 16 deletions

cmd/config/config.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package configcmd
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/fosrl/cli/internal/config"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func ConfigCmd() *cobra.Command {
13+
cmd := &cobra.Command{
14+
Use: "config",
15+
Short: "View and edit CLI configuration",
16+
Long: `View and edit persistent CLI configuration without manually editing the config file.`,
17+
}
18+
19+
cmd.AddCommand(configPathCmd())
20+
cmd.AddCommand(configShowCmd())
21+
cmd.AddCommand(configListCmd())
22+
cmd.AddCommand(configGetCmd())
23+
cmd.AddCommand(configSetCmd())
24+
25+
return cmd
26+
}
27+
28+
func configPathCmd() *cobra.Command {
29+
return &cobra.Command{
30+
Use: "path",
31+
Short: "Print the config file path",
32+
RunE: func(cmd *cobra.Command, args []string) error {
33+
path, err := config.ConfigFilePath()
34+
if err != nil {
35+
return err
36+
}
37+
fmt.Println(path)
38+
return nil
39+
},
40+
}
41+
}
42+
43+
func configShowCmd() *cobra.Command {
44+
return &cobra.Command{
45+
Use: "show",
46+
Short: "Print the current config contents",
47+
RunE: func(cmd *cobra.Command, args []string) error {
48+
return dumpConfig(config.ConfigFromContext(cmd.Context()))
49+
},
50+
}
51+
}
52+
53+
func configListCmd() *cobra.Command {
54+
return &cobra.Command{
55+
Use: "list",
56+
Short: "List settable config keys",
57+
RunE: func(cmd *cobra.Command, args []string) error {
58+
for _, key := range config.ConfigOptions {
59+
fmt.Fprintln(cmd.OutOrStdout(), key)
60+
}
61+
return nil
62+
},
63+
}
64+
}
65+
66+
func configGetCmd() *cobra.Command {
67+
return &cobra.Command{
68+
Use: "get <key>",
69+
Short: "Get a config value",
70+
Args: cobra.ExactArgs(1),
71+
RunE: func(cmd *cobra.Command, args []string) error {
72+
cfg := config.ConfigFromContext(cmd.Context())
73+
74+
value, err := cfg.GetKey(args[0])
75+
if err != nil {
76+
return err
77+
}
78+
fmt.Println(value)
79+
return nil
80+
},
81+
}
82+
}
83+
84+
func configSetCmd() *cobra.Command {
85+
return &cobra.Command{
86+
Use: "set <key> <value>",
87+
Short: "Set a config value",
88+
Long: `Set a config value and write it to the config file.
89+
90+
Supported keys:
91+
` + strings.Join(config.SupportedConfigKeys(), "\n ") + `
92+
93+
Examples:
94+
pangolin config set up.tunnel_dns true
95+
pangolin config set up.upstream_dns 10.0.0.53
96+
pangolin config set up.upstream_dns 10.0.0.53,10.0.0.54
97+
`,
98+
Args: cobra.ExactArgs(2),
99+
RunE: func(cmd *cobra.Command, args []string) error {
100+
cfg := config.ConfigFromContext(cmd.Context())
101+
102+
if err := cfg.SetKey(args[0], args[1]); err != nil {
103+
return err
104+
}
105+
if err := cfg.Save(); err != nil {
106+
return err
107+
}
108+
109+
value, err := cfg.GetKey(args[0])
110+
if err != nil {
111+
return err
112+
}
113+
fmt.Printf("%s = %s\n", args[0], value)
114+
return nil
115+
},
116+
}
117+
}
118+
119+
func dumpConfig(cfg *config.Config) error {
120+
out := map[string]any{
121+
"log_level": cfg.LogLevel,
122+
"log_file": cfg.LogFile,
123+
"disable_update_check": cfg.DisableUpdateCheck,
124+
"disable_companion_mode": cfg.DisableCompanionMode,
125+
}
126+
127+
up := map[string]any{}
128+
if cfg.IsSet("up.tunnel_dns") {
129+
up["tunnel_dns"] = cfg.GetBool("up.tunnel_dns")
130+
}
131+
if cfg.IsSet("up.override_dns") {
132+
up["override_dns"] = cfg.GetBool("up.override_dns")
133+
}
134+
if cfg.IsSet("up.upstream_dns") {
135+
up["upstream_dns"] = cfg.GetStringSlice("up.upstream_dns")
136+
}
137+
if len(up) > 0 {
138+
out["up"] = up
139+
}
140+
141+
data, err := json.MarshalIndent(out, "", " ")
142+
if err != nil {
143+
return err
144+
}
145+
fmt.Println(string(data))
146+
return nil
147+
}

cmd/list/aliases.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const aliasesPageSize = 1000
1717
type aliasesFetchOptions struct {
1818
includeLabels bool
1919
labelFilter []string
20+
status string
2021
}
2122

2223
func aliasesCmd() *cobra.Command {
@@ -43,6 +44,7 @@ func aliasesCmd() *cobra.Command {
4344
requested := aliasesFetchOptions{
4445
includeLabels: withLabels,
4546
labelFilter: labelFilter,
47+
status: "approved",
4648
}
4749

4850
data, err := fetchAllAliases(apiClient, orgID, requested)
@@ -71,10 +73,7 @@ func fetchAllAliases(apiClient *api.Client, orgID string, requested aliasesFetch
7173

7274
var combined api.ListUserResourceAliasesData
7375
for page := 1; ; page++ {
74-
pageData, err := apiClient.ListUserResourceAliases(orgID, page, aliasesPageSize, api.ListUserResourceAliasesOptions{
75-
IncludeLabels: effective.includeLabels,
76-
LabelFilter: effective.labelFilter,
77-
})
76+
pageData, err := apiClient.ListUserResourceAliases(orgID, page, aliasesPageSize, listAliasesAPIOptions(effective))
7877
if err != nil {
7978
return nil, err
8079
}
@@ -99,6 +98,18 @@ func fetchAllAliases(apiClient *api.Client, orgID string, requested aliasesFetch
9998
}
10099

101100
func resolveAliasesFetchOptions(apiClient *api.Client, orgID string, requested aliasesFetchOptions) (aliasesFetchOptions, bool, error) {
101+
effective, clientSideFilter, err := resolveAliasesFetchOptionsWithStatus(apiClient, orgID, requested)
102+
if err == nil || !isBadRequest(err) || requested.status == "" {
103+
return effective, clientSideFilter, err
104+
}
105+
106+
// Older servers reject unknown query params such as status; retry without it.
107+
withoutStatus := requested
108+
withoutStatus.status = ""
109+
return resolveAliasesFetchOptionsWithStatus(apiClient, orgID, withoutStatus)
110+
}
111+
112+
func resolveAliasesFetchOptionsWithStatus(apiClient *api.Client, orgID string, requested aliasesFetchOptions) (aliasesFetchOptions, bool, error) {
102113
effective := requested
103114

104115
if err := probeAliasesPage(apiClient, orgID, effective); err == nil {
@@ -111,6 +122,7 @@ func resolveAliasesFetchOptions(apiClient *api.Client, orgID string, requested a
111122
effective = aliasesFetchOptions{
112123
includeLabels: true,
113124
labelFilter: nil,
125+
status: requested.status,
114126
}
115127
if err := probeAliasesPage(apiClient, orgID, effective); err == nil {
116128
return effective, true, nil
@@ -119,19 +131,24 @@ func resolveAliasesFetchOptions(apiClient *api.Client, orgID string, requested a
119131
}
120132
}
121133

122-
effective = aliasesFetchOptions{}
134+
effective = aliasesFetchOptions{status: requested.status}
123135
if err := probeAliasesPage(apiClient, orgID, effective); err != nil {
124136
return effective, false, err
125137
}
126138

127139
return effective, false, nil
128140
}
129141

130-
func probeAliasesPage(apiClient *api.Client, orgID string, opts aliasesFetchOptions) error {
131-
_, err := apiClient.ListUserResourceAliases(orgID, 1, 1, api.ListUserResourceAliasesOptions{
142+
func listAliasesAPIOptions(opts aliasesFetchOptions) api.ListUserResourceAliasesOptions {
143+
return api.ListUserResourceAliasesOptions{
132144
IncludeLabels: opts.includeLabels,
133145
LabelFilter: opts.labelFilter,
134-
})
146+
Status: opts.status,
147+
}
148+
}
149+
150+
func probeAliasesPage(apiClient *api.Client, orgID string, opts aliasesFetchOptions) error {
151+
_, err := apiClient.ListUserResourceAliases(orgID, 1, 1, listAliasesAPIOptions(opts))
135152
return err
136153
}
137154

cmd/root.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/fosrl/cli/cmd/auth/logout"
1313
"github.com/fosrl/cli/cmd/authdaemon"
1414
companioncmd "github.com/fosrl/cli/cmd/companion"
15+
configcmd "github.com/fosrl/cli/cmd/config"
1516
"github.com/fosrl/cli/cmd/down"
1617
"github.com/fosrl/cli/cmd/list"
1718
"github.com/fosrl/cli/cmd/logs"
@@ -60,6 +61,7 @@ func RootCommand(initResources bool) (*cobra.Command, error) {
6061
}
6162
cmd.AddCommand(selectcmd.SelectCmd())
6263
cmd.AddCommand(list.ListCmd())
64+
cmd.AddCommand(configcmd.ConfigCmd())
6365

6466
// Platform-specific commands - nil on unsupported platforms
6567
if upCmd := up.UpCmd(); upCmd != nil {
@@ -112,7 +114,7 @@ func RootCommand(initResources bool) (*cobra.Command, error) {
112114

113115
func commandNeedsAuthInit(cmd *cobra.Command) bool {
114116
for c := cmd; c != nil; c = c.Parent() {
115-
if c.Name() == "companion" {
117+
if c.Name() == "companion" || c.Name() == "config" {
116118
return false
117119
}
118120
}

cmd/up/client/client.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func ClientUpCmd() *cobra.Command {
8282
cmd := &cobra.Command{
8383
Use: "client",
8484
Short: "Start a client connection",
85-
Long: "Bring up a client tunneled connection",
85+
Long: `Bring up a client tunneled connection.`,
8686
PreRunE: func(cmd *cobra.Command, args []string) error {
8787
// `--id` and `--secret` must be specified together
8888
if (opts.ID == "") != (opts.Secret == "") {
@@ -93,6 +93,9 @@ func ClientUpCmd() *cobra.Command {
9393
return errors.New("--silent and --attached options conflict")
9494
}
9595

96+
cfg := config.ConfigFromContext(cmd.Context())
97+
applyUpDefaults(cmd, &opts, cfg)
98+
9699
if err := validateDNSIP(opts.DNS, "netstack-dns"); err != nil {
97100
return err
98101
}
@@ -136,6 +139,23 @@ func ClientUpCmd() *cobra.Command {
136139
return cmd
137140
}
138141

142+
// Precedence: flags > env/config > built-in defaults.
143+
func applyUpDefaults(cmd *cobra.Command, opts *ClientUpCmdOpts, cfg *config.Config) {
144+
if cfg == nil {
145+
return
146+
}
147+
148+
if !cmd.Flags().Changed("tunnel-dns") && cfg.IsSet("up.tunnel_dns") {
149+
opts.TunnelDNS = cfg.GetBool("up.tunnel_dns")
150+
}
151+
if !cmd.Flags().Changed("override-dns") && cfg.IsSet("up.override_dns") {
152+
opts.OverrideDNS = cfg.GetBool("up.override_dns")
153+
}
154+
if !cmd.Flags().Changed("upstream-dns") && cfg.IsSet("up.upstream_dns") {
155+
opts.UpstreamDNS = cfg.GetStringSlice("up.upstream_dns")
156+
}
157+
}
158+
139159
func clientUpMain(cmd *cobra.Command, opts *ClientUpCmdOpts, extraArgs []string) error {
140160
apiClient := api.FromContext(cmd.Context())
141161
accountStore := config.AccountStoreFromContext(cmd.Context())

cmd/up/up_unix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ If ran with no subcommand, 'client' is passed.
2222
cmd.AddCommand(client.ClientUpCmd())
2323

2424
return cmd
25-
}
25+
}

docs/pangolin.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Pangolin CLI
1212

1313
* [pangolin apply](pangolin_apply.md) - Apply commands
1414
* [pangolin auth](pangolin_auth.md) - Authentication commands
15+
* [pangolin config](pangolin_config.md) - View and edit CLI configuration
1516
* [pangolin down](pangolin_down.md) - Stop a connection
1617
* [pangolin list](pangolin_list.md) - List resources and other items from the server
1718
* [pangolin login](pangolin_login.md) - Login to Pangolin

docs/pangolin_config.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## pangolin config
2+
3+
View and edit CLI configuration
4+
5+
### Synopsis
6+
7+
View and edit persistent CLI configuration without manually editing the config file.
8+
9+
### Options
10+
11+
```
12+
-h, --help help for config
13+
```
14+
15+
### SEE ALSO
16+
17+
* [pangolin](pangolin.md) - Pangolin CLI
18+
* [pangolin config get](pangolin_config_get.md) - Get a config value
19+
* [pangolin config list](pangolin_config_list.md) - List settable config keys
20+
* [pangolin config path](pangolin_config_path.md) - Print the config file path
21+
* [pangolin config set](pangolin_config_set.md) - Set a config value
22+
* [pangolin config show](pangolin_config_show.md) - Print the current config contents
23+

docs/pangolin_config_get.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
## pangolin config get
2+
3+
Get a config value
4+
5+
```
6+
pangolin config get <key> [flags]
7+
```
8+
9+
### Options
10+
11+
```
12+
-h, --help help for get
13+
```
14+
15+
### SEE ALSO
16+
17+
* [pangolin config](pangolin_config.md) - View and edit CLI configuration
18+

docs/pangolin_config_list.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
## pangolin config list
2+
3+
List settable config keys
4+
5+
```
6+
pangolin config list [flags]
7+
```
8+
9+
### Options
10+
11+
```
12+
-h, --help help for list
13+
```
14+
15+
### SEE ALSO
16+
17+
* [pangolin config](pangolin_config.md) - View and edit CLI configuration
18+

docs/pangolin_config_path.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
## pangolin config path
2+
3+
Print the config file path
4+
5+
```
6+
pangolin config path [flags]
7+
```
8+
9+
### Options
10+
11+
```
12+
-h, --help help for path
13+
```
14+
15+
### SEE ALSO
16+
17+
* [pangolin config](pangolin_config.md) - View and edit CLI configuration
18+

0 commit comments

Comments
 (0)