diff --git a/cmd/hcloud/main.go b/cmd/hcloud/main.go index ddb41d37..2998caed 100644 --- a/cmd/hcloud/main.go +++ b/cmd/hcloud/main.go @@ -5,7 +5,28 @@ import ( "os" "github.com/hetznercloud/cli/internal/cli" + "github.com/hetznercloud/cli/internal/cmd/all" + "github.com/hetznercloud/cli/internal/cmd/certificate" + "github.com/hetznercloud/cli/internal/cmd/completion" + "github.com/hetznercloud/cli/internal/cmd/context" + "github.com/hetznercloud/cli/internal/cmd/datacenter" + "github.com/hetznercloud/cli/internal/cmd/firewall" + "github.com/hetznercloud/cli/internal/cmd/floatingip" + "github.com/hetznercloud/cli/internal/cmd/image" + "github.com/hetznercloud/cli/internal/cmd/iso" + "github.com/hetznercloud/cli/internal/cmd/loadbalancer" + "github.com/hetznercloud/cli/internal/cmd/loadbalancertype" + "github.com/hetznercloud/cli/internal/cmd/location" + "github.com/hetznercloud/cli/internal/cmd/network" + "github.com/hetznercloud/cli/internal/cmd/placementgroup" + "github.com/hetznercloud/cli/internal/cmd/primaryip" + "github.com/hetznercloud/cli/internal/cmd/server" + "github.com/hetznercloud/cli/internal/cmd/servertype" + "github.com/hetznercloud/cli/internal/cmd/sshkey" + "github.com/hetznercloud/cli/internal/cmd/version" + "github.com/hetznercloud/cli/internal/cmd/volume" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" ) func init() { @@ -17,10 +38,10 @@ func init() { func main() { configPath := os.Getenv("HCLOUD_CONFIG") if configPath == "" { - configPath = state.DefaultConfigPath() + configPath = config.DefaultConfigPath() } - cfg, err := state.ReadConfig(configPath) + cfg, err := config.ReadConfig(configPath) if err != nil { log.Fatalf("unable to read config file %q: %s\n", configPath, err) } @@ -31,6 +52,29 @@ func main() { } rootCommand := cli.NewRootCommand(s) + rootCommand.AddCommand( + all.NewCommand(s), + floatingip.NewCommand(s), + image.NewCommand(s), + server.NewCommand(s), + sshkey.NewCommand(s), + version.NewCommand(s), + completion.NewCommand(s), + servertype.NewCommand(s), + context.NewCommand(s), + datacenter.NewCommand(s), + location.NewCommand(s), + iso.NewCommand(s), + volume.NewCommand(s), + network.NewCommand(s), + loadbalancer.NewCommand(s), + loadbalancertype.NewCommand(s), + certificate.NewCommand(s), + firewall.NewCommand(s), + placementgroup.NewCommand(s), + primaryip.NewCommand(s), + ) + if err := rootCommand.Execute(); err != nil { log.Fatalln(err) } diff --git a/internal/cli/root.go b/internal/cli/root.go index 0c2995a4..34731808 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -6,26 +6,6 @@ import ( "github.com/spf13/cobra" - "github.com/hetznercloud/cli/internal/cmd/all" - "github.com/hetznercloud/cli/internal/cmd/certificate" - "github.com/hetznercloud/cli/internal/cmd/completion" - "github.com/hetznercloud/cli/internal/cmd/context" - "github.com/hetznercloud/cli/internal/cmd/datacenter" - "github.com/hetznercloud/cli/internal/cmd/firewall" - "github.com/hetznercloud/cli/internal/cmd/floatingip" - "github.com/hetznercloud/cli/internal/cmd/image" - "github.com/hetznercloud/cli/internal/cmd/iso" - "github.com/hetznercloud/cli/internal/cmd/loadbalancer" - "github.com/hetznercloud/cli/internal/cmd/loadbalancertype" - "github.com/hetznercloud/cli/internal/cmd/location" - "github.com/hetznercloud/cli/internal/cmd/network" - "github.com/hetznercloud/cli/internal/cmd/placementgroup" - "github.com/hetznercloud/cli/internal/cmd/primaryip" - "github.com/hetznercloud/cli/internal/cmd/server" - "github.com/hetznercloud/cli/internal/cmd/servertype" - "github.com/hetznercloud/cli/internal/cmd/sshkey" - "github.com/hetznercloud/cli/internal/cmd/version" - "github.com/hetznercloud/cli/internal/cmd/volume" "github.com/hetznercloud/cli/internal/state" "github.com/hetznercloud/hcloud-go/v2/hcloud" ) @@ -40,28 +20,6 @@ func NewRootCommand(s state.State) *cobra.Command { SilenceErrors: true, DisableFlagsInUseLine: true, } - cmd.AddCommand( - all.NewCommand(s), - floatingip.NewCommand(s), - image.NewCommand(s), - server.NewCommand(s), - sshkey.NewCommand(s), - version.NewCommand(s), - completion.NewCommand(s), - servertype.NewCommand(s), - context.NewCommand(s), - datacenter.NewCommand(s), - location.NewCommand(s), - iso.NewCommand(s), - volume.NewCommand(s), - network.NewCommand(s), - loadbalancer.NewCommand(s), - loadbalancertype.NewCommand(s), - certificate.NewCommand(s), - firewall.NewCommand(s), - placementgroup.NewCommand(s), - primaryip.NewCommand(s), - ) cmd.PersistentFlags().Duration("poll-interval", 500*time.Millisecond, "Interval at which to poll information, for example action progress") cmd.PersistentFlags().Bool("quiet", false, "Only print error messages") diff --git a/internal/cmd/context/active.go b/internal/cmd/context/active.go index 44608f45..fdedeccd 100644 --- a/internal/cmd/context/active.go +++ b/internal/cmd/context/active.go @@ -25,8 +25,8 @@ func runActive(s state.State, cmd *cobra.Command, _ []string) error { if os.Getenv("HCLOUD_TOKEN") != "" { _, _ = fmt.Fprintln(os.Stderr, "Warning: HCLOUD_TOKEN is set. The active context will have no effect.") } - if cfg := s.Config(); cfg.ActiveContext != nil { - cmd.Println(cfg.ActiveContext.Name) + if ctx := s.Config().ActiveContext(); ctx != nil { + cmd.Println(ctx.Name) } return nil } diff --git a/internal/cmd/context/create.go b/internal/cmd/context/create.go index 92b6db5c..99bb8b93 100644 --- a/internal/cmd/context/create.go +++ b/internal/cmd/context/create.go @@ -12,6 +12,7 @@ import ( "golang.org/x/term" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" ) func newCreateCommand(s state.State) *cobra.Command { @@ -40,7 +41,7 @@ func runCreate(s state.State, cmd *cobra.Command, args []string) error { return errors.New("name already used") } - context := &state.ConfigContext{Name: name} + context := &config.Context{Name: name} var token string @@ -82,8 +83,8 @@ func runCreate(s state.State, cmd *cobra.Command, args []string) error { context.Token = token - cfg.Contexts = append(cfg.Contexts, context) - cfg.ActiveContext = context + cfg.SetContexts(append(cfg.Contexts(), context)) + cfg.SetActiveContext(context) if err := cfg.Write(); err != nil { return err diff --git a/internal/cmd/context/delete.go b/internal/cmd/context/delete.go index 89eca4be..4f4a0e95 100644 --- a/internal/cmd/context/delete.go +++ b/internal/cmd/context/delete.go @@ -30,9 +30,9 @@ func runDelete(s state.State, _ *cobra.Command, args []string) error { if context == nil { return fmt.Errorf("context not found: %v", name) } - if cfg.ActiveContext == context { + if cfg.ActiveContext() == context { _, _ = fmt.Fprintln(os.Stderr, "Warning: You are deleting the currently active context. Please select a new active context.") - cfg.ActiveContext = nil + cfg.SetActiveContext(nil) } cfg.RemoveContext(context) return cfg.Write() diff --git a/internal/cmd/context/list.go b/internal/cmd/context/list.go index 4d1ce092..49ccd824 100644 --- a/internal/cmd/context/list.go +++ b/internal/cmd/context/list.go @@ -56,13 +56,13 @@ func runList(s state.State, cmd *cobra.Command, _ []string) error { tw.WriteHeader(cols) } cfg := s.Config() - for _, context := range cfg.Contexts { + for _, context := range cfg.Contexts() { presentation := ContextPresentation{ Name: context.Name, Token: context.Token, Active: " ", } - if ctx := cfg.ActiveContext; ctx != nil && ctx.Name == context.Name { + if ctx := cfg.ActiveContext(); ctx != nil && ctx.Name == context.Name { presentation.Active = "*" } diff --git a/internal/cmd/context/use.go b/internal/cmd/context/use.go index f5d89f1d..b6a3ff7c 100644 --- a/internal/cmd/context/use.go +++ b/internal/cmd/context/use.go @@ -33,6 +33,6 @@ func runUse(s state.State, _ *cobra.Command, args []string) error { if context == nil { return fmt.Errorf("context not found: %v", name) } - cfg.ActiveContext = context + cfg.SetActiveContext(context) return cfg.Write() } diff --git a/internal/hcapi2/mock/mock_gen.go b/internal/hcapi2/mock/mock_gen.go deleted file mode 100644 index cbf54914..00000000 --- a/internal/hcapi2/mock/mock_gen.go +++ /dev/null @@ -1,20 +0,0 @@ -package hcapi2_mock - -//go:generate mockgen -package hcapi2_mock -destination zz_action_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 ActionClient -//go:generate mockgen -package hcapi2_mock -destination zz_certificate_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 CertificateClient -//go:generate mockgen -package hcapi2_mock -destination zz_datacenter_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 DatacenterClient -//go:generate mockgen -package hcapi2_mock -destination zz_image_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 ImageClient -//go:generate mockgen -package hcapi2_mock -destination zz_iso_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 ISOClient -//go:generate mockgen -package hcapi2_mock -destination zz_firewall_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 FirewallClient -//go:generate mockgen -package hcapi2_mock -destination zz_floating_ip_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 FloatingIPClient -//go:generate mockgen -package hcapi2_mock -destination zz_primary_ip_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 PrimaryIPClient -//go:generate mockgen -package hcapi2_mock -destination zz_location_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 LocationClient -//go:generate mockgen -package hcapi2_mock -destination zz_loadbalancer_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 LoadBalancerClient -//go:generate mockgen -package hcapi2_mock -destination zz_loadbalancer_type_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 LoadBalancerTypeClient -//go:generate mockgen -package hcapi2_mock -destination zz_network_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 NetworkClient -//go:generate mockgen -package hcapi2_mock -destination zz_server_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 ServerClient -//go:generate mockgen -package hcapi2_mock -destination zz_server_type_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 ServerTypeClient -//go:generate mockgen -package hcapi2_mock -destination zz_ssh_key_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 SSHKeyClient -//go:generate mockgen -package hcapi2_mock -destination zz_volume_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 VolumeClient -//go:generate mockgen -package hcapi2_mock -destination zz_placement_group_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 PlacementGroupClient -//go:generate mockgen -package hcapi2_mock -destination zz_rdns_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 RDNSClient diff --git a/internal/state/config.go b/internal/state/config.go deleted file mode 100644 index 09b42fac..00000000 --- a/internal/state/config.go +++ /dev/null @@ -1,117 +0,0 @@ -package state - -import ( - "fmt" - "os" - "path/filepath" - - toml "github.com/pelletier/go-toml/v2" -) - -type Config struct { - Path string - Endpoint string - ActiveContext *ConfigContext - Contexts []*ConfigContext -} - -type ConfigContext struct { - Name string - Token string -} - -func (config *Config) Write() error { - data, err := MarshalConfig(config) - if err != nil { - return err - } - if err := os.MkdirAll(filepath.Dir(config.Path), 0777); err != nil { - return err - } - if err := os.WriteFile(config.Path, data, 0600); err != nil { - return err - } - return nil -} - -func (config *Config) ContextNames() []string { - if len(config.Contexts) == 0 { - return nil - } - names := make([]string, len(config.Contexts)) - for i, ctx := range config.Contexts { - names[i] = ctx.Name - } - return names -} - -func (config *Config) ContextByName(name string) *ConfigContext { - for _, c := range config.Contexts { - if c.Name == name { - return c - } - } - return nil -} - -func (config *Config) RemoveContext(context *ConfigContext) { - for i, c := range config.Contexts { - if c == context { - config.Contexts = append(config.Contexts[:i], config.Contexts[i+1:]...) - return - } - } -} - -type RawConfig struct { - ActiveContext string `toml:"active_context,omitempty"` - Contexts []RawConfigContext `toml:"contexts"` -} - -type RawConfigContext struct { - Name string `toml:"name"` - Token string `toml:"token"` -} - -func MarshalConfig(c *Config) ([]byte, error) { - if c == nil { - return []byte{}, nil - } - - var raw RawConfig - if c.ActiveContext != nil { - raw.ActiveContext = c.ActiveContext.Name - } - for _, context := range c.Contexts { - raw.Contexts = append(raw.Contexts, RawConfigContext{ - Name: context.Name, - Token: context.Token, - }) - } - return toml.Marshal(raw) -} - -func UnmarshalConfig(config *Config, data []byte) error { - var raw RawConfig - if err := toml.Unmarshal(data, &raw); err != nil { - return err - } - for _, rawContext := range raw.Contexts { - config.Contexts = append(config.Contexts, &ConfigContext{ - Name: rawContext.Name, - Token: rawContext.Token, - }) - } - if raw.ActiveContext != "" { - for _, c := range config.Contexts { - if c.Name == raw.ActiveContext { - config.ActiveContext = c - break - } - } - if config.ActiveContext == nil { - return fmt.Errorf("active context %s not found", raw.ActiveContext) - } - } - return nil -} diff --git a/internal/state/config/config.go b/internal/state/config/config.go new file mode 100644 index 00000000..f204aa5f --- /dev/null +++ b/internal/state/config/config.go @@ -0,0 +1,179 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + + toml "github.com/pelletier/go-toml/v2" +) + +//go:generate mockgen -package config -destination zz_config_mock.go github.com/hetznercloud/cli/internal/state/config Config + +type Config interface { + Write() error + Marshal() ([]byte, error) + + ActiveContext() *Context + SetActiveContext(*Context) + Contexts() []*Context + SetContexts([]*Context) + Endpoint() string + SetEndpoint(string) + + ContextNames() []string + ContextByName(name string) *Context + RemoveContext(context *Context) +} + +type Context struct { + Name string + Token string +} + +type config struct { + path string + endpoint string + activeContext *Context `toml:"active_context,omitempty"` + contexts []*Context `toml:"contexts"` +} + +func ReadConfig(path string) (Config, error) { + cfg := &config{path: path} + + _, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return cfg, nil + } + return cfg, err + } + + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + if err = cfg.unmarshal(data); err != nil { + return nil, err + } + + return cfg, nil +} + +func (cfg *config) Write() error { + data, err := cfg.Marshal() + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(cfg.path), 0777); err != nil { + return err + } + if err := os.WriteFile(cfg.path, data, 0600); err != nil { + return err + } + return nil +} + +type rawConfig struct { + activeContext string + contexts []rawConfigContext +} + +type rawConfigContext struct { + Name string `toml:"name"` + Token string `toml:"token"` +} + +func (cfg *config) Marshal() ([]byte, error) { + + var raw rawConfig + if cfg.activeContext != nil { + raw.activeContext = cfg.activeContext.Name + } + for _, context := range cfg.contexts { + raw.contexts = append(raw.contexts, rawConfigContext{ + Name: context.Name, + Token: context.Token, + }) + } + return toml.Marshal(raw) +} + +func (cfg *config) ActiveContext() *Context { + return cfg.activeContext +} + +func (cfg *config) SetActiveContext(context *Context) { + cfg.activeContext = context +} + +func (cfg *config) Contexts() []*Context { + return cfg.contexts +} + +func (cfg *config) SetContexts(contexts []*Context) { + cfg.contexts = contexts +} + +func (cfg *config) Endpoint() string { + return cfg.endpoint +} + +func (cfg *config) SetEndpoint(endpoint string) { + cfg.endpoint = endpoint +} + +func (cfg *config) ContextNames() []string { + if len(cfg.contexts) == 0 { + return nil + } + names := make([]string, len(cfg.contexts)) + for i, ctx := range cfg.contexts { + names[i] = ctx.Name + } + return names +} + +func (cfg *config) ContextByName(name string) *Context { + for _, c := range cfg.contexts { + if c.Name == name { + return c + } + } + return nil +} + +func (cfg *config) RemoveContext(context *Context) { + for i, c := range cfg.contexts { + if c == context { + cfg.contexts = append(cfg.contexts[:i], cfg.contexts[i+1:]...) + return + } + } +} + +func (cfg *config) unmarshal(data []byte) error { + var raw rawConfig + if err := toml.Unmarshal(data, &raw); err != nil { + return err + } + for _, rawContext := range raw.contexts { + cfg.contexts = append(cfg.contexts, &Context{ + Name: rawContext.Name, + Token: rawContext.Token, + }) + } + if raw.activeContext != "" { + for _, c := range cfg.contexts { + if c.Name == raw.activeContext { + cfg.activeContext = c + break + } + } + if cfg.activeContext == nil { + return fmt.Errorf("active context %s not found", raw.activeContext) + } + } + return nil +} diff --git a/internal/state/config_unix.go b/internal/state/config/config_unix.go similarity index 94% rename from internal/state/config_unix.go rename to internal/state/config/config_unix.go index 291cc725..d6ce3a19 100644 --- a/internal/state/config_unix.go +++ b/internal/state/config/config_unix.go @@ -1,6 +1,6 @@ //go:build !windows -package state +package config import ( "os/user" diff --git a/internal/state/config_windows.go b/internal/state/config/config_windows.go similarity index 93% rename from internal/state/config_windows.go rename to internal/state/config/config_windows.go index 6dc7b2c6..fecd6abb 100644 --- a/internal/state/config_windows.go +++ b/internal/state/config/config_windows.go @@ -1,6 +1,6 @@ //go:build windows -package state +package config import ( "os" diff --git a/internal/state/config/zz_config_mock.go b/internal/state/config/zz_config_mock.go new file mode 100644 index 00000000..f662c024 --- /dev/null +++ b/internal/state/config/zz_config_mock.go @@ -0,0 +1,181 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/hetznercloud/cli/internal/state/config (interfaces: Config) + +// Package config is a generated GoMock package. +package config + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockConfig is a mock of Config interface. +type MockConfig struct { + ctrl *gomock.Controller + recorder *MockConfigMockRecorder +} + +// MockConfigMockRecorder is the mock recorder for MockConfig. +type MockConfigMockRecorder struct { + mock *MockConfig +} + +// NewMockConfig creates a new mock instance. +func NewMockConfig(ctrl *gomock.Controller) *MockConfig { + mock := &MockConfig{ctrl: ctrl} + mock.recorder = &MockConfigMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConfig) EXPECT() *MockConfigMockRecorder { + return m.recorder +} + +// ActiveContext mocks base method. +func (m *MockConfig) ActiveContext() *Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ActiveContext") + ret0, _ := ret[0].(*Context) + return ret0 +} + +// ActiveContext indicates an expected call of ActiveContext. +func (mr *MockConfigMockRecorder) ActiveContext() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActiveContext", reflect.TypeOf((*MockConfig)(nil).ActiveContext)) +} + +// ContextByName mocks base method. +func (m *MockConfig) ContextByName(arg0 string) *Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContextByName", arg0) + ret0, _ := ret[0].(*Context) + return ret0 +} + +// ContextByName indicates an expected call of ContextByName. +func (mr *MockConfigMockRecorder) ContextByName(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContextByName", reflect.TypeOf((*MockConfig)(nil).ContextByName), arg0) +} + +// ContextNames mocks base method. +func (m *MockConfig) ContextNames() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContextNames") + ret0, _ := ret[0].([]string) + return ret0 +} + +// ContextNames indicates an expected call of ContextNames. +func (mr *MockConfigMockRecorder) ContextNames() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContextNames", reflect.TypeOf((*MockConfig)(nil).ContextNames)) +} + +// Contexts mocks base method. +func (m *MockConfig) Contexts() []*Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Contexts") + ret0, _ := ret[0].([]*Context) + return ret0 +} + +// Contexts indicates an expected call of Contexts. +func (mr *MockConfigMockRecorder) Contexts() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Contexts", reflect.TypeOf((*MockConfig)(nil).Contexts)) +} + +// Endpoint mocks base method. +func (m *MockConfig) Endpoint() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Endpoint") + ret0, _ := ret[0].(string) + return ret0 +} + +// Endpoint indicates an expected call of Endpoint. +func (mr *MockConfigMockRecorder) Endpoint() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Endpoint", reflect.TypeOf((*MockConfig)(nil).Endpoint)) +} + +// Marshal mocks base method. +func (m *MockConfig) Marshal() ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Marshal") + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Marshal indicates an expected call of Marshal. +func (mr *MockConfigMockRecorder) Marshal() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marshal", reflect.TypeOf((*MockConfig)(nil).Marshal)) +} + +// RemoveContext mocks base method. +func (m *MockConfig) RemoveContext(arg0 *Context) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RemoveContext", arg0) +} + +// RemoveContext indicates an expected call of RemoveContext. +func (mr *MockConfigMockRecorder) RemoveContext(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveContext", reflect.TypeOf((*MockConfig)(nil).RemoveContext), arg0) +} + +// SetActiveContext mocks base method. +func (m *MockConfig) SetActiveContext(arg0 *Context) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetActiveContext", arg0) +} + +// SetActiveContext indicates an expected call of SetActiveContext. +func (mr *MockConfigMockRecorder) SetActiveContext(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetActiveContext", reflect.TypeOf((*MockConfig)(nil).SetActiveContext), arg0) +} + +// SetContexts mocks base method. +func (m *MockConfig) SetContexts(arg0 []*Context) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetContexts", arg0) +} + +// SetContexts indicates an expected call of SetContexts. +func (mr *MockConfigMockRecorder) SetContexts(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetContexts", reflect.TypeOf((*MockConfig)(nil).SetContexts), arg0) +} + +// SetEndpoint mocks base method. +func (m *MockConfig) SetEndpoint(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetEndpoint", arg0) +} + +// SetEndpoint indicates an expected call of SetEndpoint. +func (mr *MockConfigMockRecorder) SetEndpoint(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetEndpoint", reflect.TypeOf((*MockConfig)(nil).SetEndpoint), arg0) +} + +// Write mocks base method. +func (m *MockConfig) Write() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write") + ret0, _ := ret[0].(error) + return ret0 +} + +// Write indicates an expected call of Write. +func (mr *MockConfigMockRecorder) Write() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockConfig)(nil).Write)) +} diff --git a/internal/state/state.go b/internal/state/state.go index 3bcbaac3..8a21cde7 100644 --- a/internal/state/state.go +++ b/internal/state/state.go @@ -6,6 +6,7 @@ import ( "os" "github.com/hetznercloud/cli/internal/hcapi2" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/cli/internal/version" "github.com/hetznercloud/hcloud-go/v2/hcloud" ) @@ -17,7 +18,7 @@ type State interface { ActionWaiter Client() hcapi2.Client - Config() *Config + Config() config.Config } type state struct { @@ -28,19 +29,19 @@ type state struct { debug bool debugFilePath string client hcapi2.Client - config *Config + config config.Config } -func New(cfg *Config) (State, error) { +func New(cfg config.Config) (State, error) { var ( token string endpoint string ) - if cfg.ActiveContext != nil { - token = cfg.ActiveContext.Token + if ctx := cfg.ActiveContext(); ctx != nil { + token = ctx.Token } - if cfg.Endpoint != "" { - endpoint = cfg.Endpoint + if ep := cfg.Endpoint(); ep != "" { + endpoint = ep } s := &state{ @@ -55,34 +56,11 @@ func New(cfg *Config) (State, error) { return s, nil } -func ReadConfig(path string) (*Config, error) { - cfg := &Config{Path: path} - - _, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - return cfg, nil - } - return cfg, err - } - - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - - if err = UnmarshalConfig(cfg, data); err != nil { - return nil, err - } - - return cfg, nil -} - func (c *state) Client() hcapi2.Client { return c.client } -func (c *state) Config() *Config { +func (c *state) Config() config.Config { return c.config } @@ -101,7 +79,7 @@ func (c *state) readEnv() { } if s := os.Getenv("HCLOUD_CONTEXT"); s != "" && c.config != nil { if cfgCtx := c.config.ContextByName(s); cfgCtx != nil { - c.config.ActiveContext = cfgCtx + c.config.SetActiveContext(cfgCtx) c.token = cfgCtx.Token } else { log.Printf("warning: context %q specified in HCLOUD_CONTEXT does not exist\n", s) diff --git a/internal/testutil/fixture.go b/internal/testutil/fixture.go index f21b66e1..fb5d5310 100644 --- a/internal/testutil/fixture.go +++ b/internal/testutil/fixture.go @@ -12,6 +12,7 @@ import ( "github.com/hetznercloud/cli/internal/hcapi2" hcapi2_mock "github.com/hetznercloud/cli/internal/hcapi2/mock" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" ) // Fixture provides affordances for testing CLI commands. @@ -20,6 +21,7 @@ type Fixture struct { Client *hcapi2_mock.MockClient ActionWaiter *state.MockActionWaiter TokenEnsurer *state.MockTokenEnsurer + Config *config.MockConfig } // NewFixture creates a new Fixture. @@ -31,6 +33,7 @@ func NewFixture(t *testing.T) *Fixture { Client: hcapi2_mock.NewMockClient(ctrl), ActionWaiter: state.NewMockActionWaiter(ctrl), TokenEnsurer: state.NewMockTokenEnsurer(ctrl), + Config: config.NewMockConfig(ctrl), } } @@ -64,6 +67,7 @@ type fixtureState struct { state.ActionWaiter client hcapi2.Client + config config.Config } func (*fixtureState) WriteConfig() error { @@ -74,8 +78,8 @@ func (s *fixtureState) Client() hcapi2.Client { return s.client } -func (*fixtureState) Config() *state.Config { - return nil +func (s *fixtureState) Config() config.Config { + return s.config } // State returns a state.State implementation for testing purposes. @@ -85,5 +89,6 @@ func (f *Fixture) State() state.State { TokenEnsurer: f.TokenEnsurer, ActionWaiter: f.ActionWaiter, client: f.Client, + config: f.Config, } }