Skip to content

Commit f895daf

Browse files
committed
wip
1 parent bdcfa39 commit f895daf

26 files changed

+943
-152
lines changed

cmd/hcloud/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func main() {
4141

4242
cfg := config.NewConfig()
4343
if err := config.ReadConfig(cfg); err != nil {
44-
log.Fatalf("unable to read config file %s\n", err)
44+
log.Fatalf("unable to read config file: %s\n", err)
4545
}
4646

4747
s, err := state.New(cfg)

go.mod

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ module github.com/hetznercloud/cli
22

33
go 1.21
44

5+
replace github.com/spf13/viper => github.com/phm07/viper v0.0.0-20240424133512-73ebad00c669
6+
57
require (
8+
github.com/BurntSushi/toml v1.3.2
69
github.com/boumenot/gocover-cobertura v1.2.0
710
github.com/cheggaaa/pb/v3 v3.1.5
811
github.com/dustin/go-humanize v1.0.1
@@ -12,7 +15,6 @@ require (
1215
github.com/google/go-cmp v0.6.0
1316
github.com/guptarohit/asciigraph v0.7.1
1417
github.com/hetznercloud/hcloud-go/v2 v2.7.2
15-
github.com/pelletier/go-toml/v2 v2.2.1
1618
github.com/spf13/cobra v1.8.0
1719
github.com/spf13/pflag v1.0.5
1820
github.com/spf13/viper v1.18.2
@@ -35,6 +37,7 @@ require (
3537
github.com/mattn/go-isatty v0.0.20 // indirect
3638
github.com/mattn/go-runewidth v0.0.15 // indirect
3739
github.com/mitchellh/mapstructure v1.5.0 // indirect
40+
github.com/pelletier/go-toml/v2 v2.2.1 // indirect
3841
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3942
github.com/prometheus/client_golang v1.19.0 // indirect
4043
github.com/prometheus/client_model v0.5.0 // indirect
@@ -56,7 +59,7 @@ require (
5659
golang.org/x/text v0.14.0 // indirect
5760
golang.org/x/tools v0.17.0 // indirect
5861
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
59-
google.golang.org/protobuf v1.32.0 // indirect
62+
google.golang.org/protobuf v1.33.0 // indirect
6063
gopkg.in/ini.v1 v1.67.0 // indirect
6164
gopkg.in/yaml.v3 v3.0.1 // indirect
6265
)

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
2+
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
13
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
24
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
35
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -39,8 +41,6 @@ github.com/guptarohit/asciigraph v0.7.1 h1:K+JWbRc04XEfv8BSZgNuvhCmpbvX4+9NYd/Ux
3941
github.com/guptarohit/asciigraph v0.7.1/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag=
4042
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
4143
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
42-
github.com/hetznercloud/hcloud-go/v2 v2.7.1 h1:D4domwRSLOyBL/bwzd1O7hunBbKmeEHZTa7GmCYrniY=
43-
github.com/hetznercloud/hcloud-go/v2 v2.7.1/go.mod h1:49tIV+pXRJTUC7fbFZ03s45LKqSQdOPP5y91eOnJo/k=
4444
github.com/hetznercloud/hcloud-go/v2 v2.7.2 h1:UlE7n1GQZacCfyjv9tDVUN7HZfOXErPIfM/M039u9A0=
4545
github.com/hetznercloud/hcloud-go/v2 v2.7.2/go.mod h1:49tIV+pXRJTUC7fbFZ03s45LKqSQdOPP5y91eOnJo/k=
4646
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -64,6 +64,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
6464
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
6565
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
6666
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
67+
github.com/phm07/viper v0.0.0-20240424133512-73ebad00c669 h1:/RkERYB9EOE1AkgDmGheEbPkDt8usI0EGryLsQGvG2c=
68+
github.com/phm07/viper v0.0.0-20240424133512-73ebad00c669/go.mod h1:Hqr8J4/Q1O00v/4zIIggDIidAoD4w8Oqtzc+Ew8QO+I=
6769
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6870
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
6971
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -94,8 +96,6 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
9496
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
9597
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
9698
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
97-
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
98-
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
9999
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
100100
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
101101
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -164,8 +164,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
164164
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
165165
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
166166
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
167-
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
168-
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
167+
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
168+
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
169169
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
170170
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
171171
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

internal/cli/root.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ func NewRootCommand(s state.State) *cobra.Command {
2626
var err error
2727
out := os.Stdout
2828
if quiet := config.OptionQuiet.Value(); quiet {
29-
//if quiet := viper.GetBool("quiet"); quiet {
3029
out, err = os.Open(os.DevNull)
3130
if err != nil {
3231
return err

internal/cmd/cmpl/suggestions.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,14 @@ func SuggestArgs(
9393
return f(cmd, args, toComplete)
9494
}
9595
}
96+
97+
// NoFileCompletion returns a function that provides completion suggestions without
98+
// file completion.
99+
func NoFileCompletion(f func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)) func(
100+
*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
101+
102+
return func(command *cobra.Command, i []string, s string) ([]string, cobra.ShellCompDirective) {
103+
candidates, _ := f(command, i, s)
104+
return candidates, cobra.ShellCompDirectiveNoFileComp
105+
}
106+
}

internal/cmd/config/add.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"reflect"
7+
8+
"github.com/spf13/cobra"
9+
10+
"github.com/hetznercloud/cli/internal/cmd/cmpl"
11+
"github.com/hetznercloud/cli/internal/cmd/util"
12+
"github.com/hetznercloud/cli/internal/state"
13+
"github.com/hetznercloud/cli/internal/state/config"
14+
)
15+
16+
func NewAddCommand(s state.State) *cobra.Command {
17+
cmd := &cobra.Command{
18+
Use: "add <key> <value>...",
19+
Short: "Set a configuration value",
20+
Args: util.Validate,
21+
TraverseChildren: true,
22+
DisableFlagsInUseLine: true,
23+
RunE: state.Wrap(s, runAdd),
24+
ValidArgsFunction: cmpl.NoFileCompletion(cmpl.SuggestArgs(
25+
cmpl.SuggestCandidatesF(func() []string {
26+
var keys []string
27+
for key, opt := range config.Options {
28+
if opt.HasFlag(config.OptionFlagPreference) {
29+
keys = append(keys, key)
30+
}
31+
}
32+
return keys
33+
}),
34+
cmpl.SuggestCandidatesCtx(func(_ *cobra.Command, args []string) []string {
35+
var comps []string
36+
if opt, ok := config.Options[args[0]]; ok {
37+
comps = opt.Completions()
38+
}
39+
return comps
40+
}),
41+
)),
42+
}
43+
cmd.Flags().Bool("global", false, "Set the value globally (for all contexts)")
44+
return cmd
45+
}
46+
47+
func runAdd(s state.State, cmd *cobra.Command, args []string) error {
48+
global, _ := cmd.Flags().GetBool("global")
49+
50+
var prefs config.Preferences
51+
52+
if global {
53+
prefs = s.Config().Preferences()
54+
} else {
55+
ctx := s.Config().ActiveContext()
56+
if reflect.ValueOf(ctx).IsNil() {
57+
return fmt.Errorf("no active context (use --global flag to set a global option)")
58+
}
59+
prefs = ctx.Preferences()
60+
}
61+
62+
key, values := args[0], args[1:]
63+
if err := prefs.Add(key, values); err != nil {
64+
return err
65+
}
66+
67+
_ = s.Config().Write(nil)
68+
return s.Config().Write(os.Stdout)
69+
}

internal/cmd/config/config.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ func NewCommand(s state.State) *cobra.Command {
1616
DisableFlagsInUseLine: true,
1717
}
1818
cmd.AddCommand(
19-
newSetCommand(s),
19+
NewSetCommand(s),
20+
NewGetCommand(s),
21+
NewListCommand(s),
22+
NewUnsetCommand(s),
23+
NewAddCommand(s),
24+
NewRemoveCommand(s),
2025
)
2126
return cmd
2227
}

internal/cmd/config/get.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/spf13/viper"
8+
9+
"github.com/hetznercloud/cli/internal/cmd/util"
10+
"github.com/hetznercloud/cli/internal/state"
11+
"github.com/hetznercloud/cli/internal/state/config"
12+
)
13+
14+
func NewGetCommand(s state.State) *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "get <key>",
17+
Short: "Get a configuration value",
18+
Args: util.Validate,
19+
TraverseChildren: true,
20+
DisableFlagsInUseLine: true,
21+
RunE: state.Wrap(s, runGet),
22+
}
23+
cmd.Flags().Bool("global", false, "Get the value globally")
24+
cmd.Flags().Bool("allow-sensitive", false, "Allow showing sensitive values")
25+
return cmd
26+
}
27+
28+
func runGet(s state.State, cmd *cobra.Command, args []string) error {
29+
global, _ := cmd.Flags().GetBool("global")
30+
allowSensitive, _ := cmd.Flags().GetBool("allow-sensitive")
31+
32+
if global {
33+
viper.Reset()
34+
config.ResetFlags()
35+
viper.Set("context", nil)
36+
if err := s.Config().ParseConfig(); err != nil {
37+
return err
38+
}
39+
}
40+
41+
key := args[0]
42+
var opt config.IOption
43+
for name, o := range config.Options {
44+
if name == key {
45+
opt = o
46+
break
47+
}
48+
}
49+
if opt == nil {
50+
return fmt.Errorf("unknown key: %s", key)
51+
}
52+
53+
val := opt.ValueAny()
54+
if opt.HasFlag(config.OptionFlagSensitive) && !allowSensitive {
55+
return fmt.Errorf("'%s' is sensitive. use --allow-sensitive to show the value", key)
56+
}
57+
cmd.Println(val)
58+
return nil
59+
}

internal/cmd/config/get_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package config_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
configCmd "github.com/hetznercloud/cli/internal/cmd/config"
9+
"github.com/hetznercloud/cli/internal/testutil"
10+
)
11+
12+
func TestGet(t *testing.T) {
13+
type testCase struct {
14+
key string
15+
args []string
16+
expOut string
17+
expErr string
18+
}
19+
20+
testCases := []testCase{
21+
{
22+
key: "context",
23+
expOut: "test_context\n",
24+
},
25+
{
26+
key: "debug",
27+
expOut: "true\n",
28+
},
29+
{
30+
key: "endpoint",
31+
expOut: "https://test-endpoint.com\n",
32+
},
33+
{
34+
key: "poll-interval",
35+
expOut: "1.234s\n",
36+
},
37+
}
38+
39+
for _, tt := range testCases {
40+
t.Run(tt.key, func(t *testing.T) {
41+
fx := testutil.NewFixture(t)
42+
defer fx.Finish()
43+
44+
cmd := configCmd.NewGetCommand(fx.State())
45+
46+
setTestValues()
47+
out, errOut, err := fx.Run(cmd, append(tt.args, tt.key))
48+
49+
assert.NoError(t, err)
50+
assert.Equal(t, tt.expErr, errOut)
51+
assert.Equal(t, tt.expOut, out)
52+
})
53+
}
54+
}

0 commit comments

Comments
 (0)