Skip to content

Commit 55ff20e

Browse files
committed
Implement contexts
1 parent 98d28da commit 55ff20e

12 files changed

Lines changed: 307 additions & 83 deletions

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ We do not accept pull requests at the moment.
1616

1717
Configure the `hcloud` program to use your token:
1818

19-
hcloud configure
19+
hcloud context create my-project
20+
hcloud context activate my-project
2021

2122
See `hcloud help` for a list of commands.
2223

cli.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli
22

33
import (
44
"context"
5+
"errors"
56
"io/ioutil"
67
"os"
78
"path/filepath"
@@ -12,11 +13,14 @@ import (
1213
"golang.org/x/crypto/ssh/terminal"
1314
)
1415

16+
var ErrConfigPathUnknown = errors.New("config file path unknown")
17+
1518
type CLI struct {
16-
Token string
17-
Endpoint string
18-
Context context.Context
19-
Config *Config
19+
Token string
20+
Endpoint string
21+
Context context.Context
22+
Config *Config
23+
ConfigPath string
2024

2125
RootCommand *cobra.Command
2226

@@ -25,7 +29,12 @@ type CLI struct {
2529

2630
func NewCLI() *CLI {
2731
cli := &CLI{
28-
Context: context.Background(),
32+
Context: context.Background(),
33+
Config: &Config{},
34+
ConfigPath: DefaultConfigPath,
35+
}
36+
if s := os.Getenv("HCLOUD_CONFIG"); s != "" {
37+
cli.ConfigPath = s
2938
}
3039
cli.RootCommand = NewRootCommand(cli)
3140
return cli
@@ -40,8 +49,12 @@ func (c *CLI) ReadEnv() {
4049
}
4150
}
4251

43-
func (c *CLI) ReadConfig(path string) error {
44-
data, err := ioutil.ReadFile(path)
52+
func (c *CLI) ReadConfig() error {
53+
if c.ConfigPath == "" {
54+
return ErrConfigPathUnknown
55+
}
56+
57+
data, err := ioutil.ReadFile(c.ConfigPath)
4558
if err != nil {
4659
return err
4760
}
@@ -50,10 +63,13 @@ func (c *CLI) ReadConfig(path string) error {
5063
if err != nil {
5164
return err
5265
}
66+
if config == nil {
67+
return nil
68+
}
5369
c.Config = config
5470

55-
if config.Token != "" {
56-
c.Token = config.Token
71+
if config.ActiveContext != nil {
72+
c.Token = config.ActiveContext.Token
5773
}
5874
if config.Endpoint != "" {
5975
c.Endpoint = config.Endpoint
@@ -62,7 +78,10 @@ func (c *CLI) ReadConfig(path string) error {
6278
return nil
6379
}
6480

65-
func (c *CLI) WriteConfig(path string) error {
81+
func (c *CLI) WriteConfig() error {
82+
if c.ConfigPath == "" {
83+
return ErrConfigPathUnknown
84+
}
6685
if c.Config == nil {
6786
return nil
6887
}
@@ -71,10 +90,10 @@ func (c *CLI) WriteConfig(path string) error {
7190
if err != nil {
7291
return err
7392
}
74-
if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
93+
if err := os.MkdirAll(filepath.Dir(c.ConfigPath), 0777); err != nil {
7594
return err
7695
}
77-
if err := ioutil.WriteFile(path, data, 0600); err != nil {
96+
if err := ioutil.WriteFile(c.ConfigPath, data, 0600); err != nil {
7897
return err
7998
}
8099
return nil

cmd/hcloud/main.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ func init() {
1616
func main() {
1717
c := cli.NewCLI()
1818

19-
if cli.DefaultConfigPath != "" {
20-
_, err := os.Stat(cli.DefaultConfigPath)
19+
if c.ConfigPath != "" {
20+
_, err := os.Stat(c.ConfigPath)
2121
switch {
2222
case err == nil:
23-
if err := c.ReadConfig(cli.DefaultConfigPath); err != nil {
24-
log.Fatalf("unable to read config file %q: %s\n", cli.DefaultConfigPath, err)
23+
if err := c.ReadConfig(); err != nil {
24+
log.Fatalf("unable to read config file %q: %s\n", c.ConfigPath, err)
2525
}
2626
case os.IsNotExist(err):
2727
break
2828
default:
29-
log.Fatalf("unable to read config file %q: %s\n", cli.DefaultConfigPath, err)
29+
log.Fatalf("unable to read config file %q: %s\n", c.ConfigPath, err)
3030
}
3131
}
3232

config.go

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"fmt"
45
"os"
56
"path/filepath"
67

@@ -16,13 +17,42 @@ func init() {
1617
}
1718

1819
type Config struct {
19-
Token string
20-
Endpoint string
20+
Endpoint string
21+
ActiveContext *ConfigContext
22+
Contexts []*ConfigContext
23+
}
24+
25+
type ConfigContext struct {
26+
Name string
27+
Token string
28+
}
29+
30+
func (config *Config) ContextByName(name string) *ConfigContext {
31+
for _, c := range config.Contexts {
32+
if c.Name == name {
33+
return c
34+
}
35+
}
36+
return nil
37+
}
38+
39+
func (config *Config) RemoveContext(context *ConfigContext) {
40+
for i, c := range config.Contexts {
41+
if c == context {
42+
config.Contexts = append(config.Contexts[:i], config.Contexts[i+1:]...)
43+
return
44+
}
45+
}
2146
}
2247

2348
type RawConfig struct {
24-
Token string `toml:"token,omitempty"`
25-
Endpoint string `toml:"endpoint,omitempty"`
49+
ActiveContext string `toml:"active_context,omitempty"`
50+
Contexts []RawConfigContext `toml:"contexts"`
51+
}
52+
53+
type RawConfigContext struct {
54+
Name string `toml:"name"`
55+
Token string `toml:"token"`
2656
}
2757

2858
func MarshalConfig(c *Config) ([]byte, error) {
@@ -31,8 +61,15 @@ func MarshalConfig(c *Config) ([]byte, error) {
3161
}
3262

3363
var raw RawConfig
34-
raw.Token = c.Token
35-
raw.Endpoint = c.Endpoint
64+
if c.ActiveContext != nil {
65+
raw.ActiveContext = c.ActiveContext.Name
66+
}
67+
for _, context := range c.Contexts {
68+
raw.Contexts = append(raw.Contexts, RawConfigContext{
69+
Name: context.Name,
70+
Token: context.Token,
71+
})
72+
}
3673
return toml.Marshal(raw)
3774
}
3875

@@ -41,8 +78,23 @@ func UnmarshalConfig(data []byte) (*Config, error) {
4178
if err := toml.Unmarshal(data, &raw); err != nil {
4279
return nil, err
4380
}
44-
return &Config{
45-
Token: raw.Token,
46-
Endpoint: raw.Endpoint,
47-
}, nil
81+
config := &Config{}
82+
for _, rawContext := range raw.Contexts {
83+
config.Contexts = append(config.Contexts, &ConfigContext{
84+
Name: rawContext.Name,
85+
Token: rawContext.Token,
86+
})
87+
}
88+
if raw.ActiveContext != "" {
89+
for _, c := range config.Contexts {
90+
if c.Name == raw.ActiveContext {
91+
config.ActiveContext = c
92+
break
93+
}
94+
}
95+
if config.ActiveContext == nil {
96+
return config, fmt.Errorf("active context %q not found", raw.ActiveContext)
97+
}
98+
}
99+
return config, nil
48100
}

configure.go

Lines changed: 0 additions & 54 deletions
This file was deleted.

context.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cli
2+
3+
import "github.com/spf13/cobra"
4+
5+
func newContextCommand(cli *CLI) *cobra.Command {
6+
cmd := &cobra.Command{
7+
Use: "context [FLAGS]",
8+
Short: "Manage contexts",
9+
Args: cobra.NoArgs,
10+
TraverseChildren: true,
11+
DisableFlagsInUseLine: true,
12+
RunE: cli.wrap(runContext),
13+
}
14+
cmd.AddCommand(
15+
newContextCreateCommand(cli),
16+
newContextActiveCommand(cli),
17+
newContextActivateCommand(cli),
18+
newContextDeleteCommand(cli),
19+
newContextListCommand(cli),
20+
)
21+
return cmd
22+
}
23+
24+
func runContext(cli *CLI, cmd *cobra.Command, args []string) error {
25+
return cmd.Usage()
26+
}

context_activate.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
func newContextActivateCommand(cli *CLI) *cobra.Command {
10+
cmd := &cobra.Command{
11+
Use: "activate [FLAGS] NAME",
12+
Short: "Activate a context",
13+
Args: cobra.ExactArgs(1),
14+
TraverseChildren: true,
15+
DisableFlagsInUseLine: true,
16+
RunE: cli.wrap(runContextActivate),
17+
}
18+
return cmd
19+
}
20+
21+
func runContextActivate(cli *CLI, cmd *cobra.Command, args []string) error {
22+
name := args[0]
23+
context := cli.Config.ContextByName(name)
24+
if context == nil {
25+
return fmt.Errorf("context not found: %v", name)
26+
}
27+
cli.Config.ActiveContext = context
28+
return cli.WriteConfig()
29+
}

context_active.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
func newContextActiveCommand(cli *CLI) *cobra.Command {
10+
cmd := &cobra.Command{
11+
Use: "active [FLAGS]",
12+
Short: "Show active context",
13+
Args: cobra.NoArgs,
14+
TraverseChildren: true,
15+
DisableFlagsInUseLine: true,
16+
RunE: cli.wrap(runContextActive),
17+
}
18+
return cmd
19+
}
20+
21+
func runContextActive(cli *CLI, cmd *cobra.Command, args []string) error {
22+
if cli.Config.ActiveContext != nil {
23+
fmt.Println(cli.Config.ActiveContext.Name)
24+
}
25+
return nil
26+
}

0 commit comments

Comments
 (0)