Skip to content

Commit 22b3ef2

Browse files
fix: fallback to IONOS_CONFIG_FILE if not found at --config location (#560)
* fix: fallback to IONOS_CONFIG_FILE env, and ~/.ionos/config SDK Default * doc: changelog * test: add test * test: fix windows
1 parent 727ad38 commit 22b3ef2

5 files changed

Lines changed: 254 additions & 103 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Changed config generation product name from 'compute' to 'cloud' for CloudAPI, though existing configurations using 'compute' key will continue to work.
77

88
### Fixed
9+
- Fixed a bug where the fallback to IONOS_CONFIG_FILE and ~/.ionos/config was not working correctly
910
- Fixed 'ionosctl mongo cluster update' sending a payload containing certain nil/empty values to the API, which would cause a 500 Internal Server Error.
1011
- Fixed a bug where config overrides were ignored for certain CloudAPI commands
1112

commands/cfg/location.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ func LocationCmd() *core.Command {
1818
PreCmdRun: core.NoPreRun,
1919
CmdRun: func(c *core.CommandConfig) error {
2020
cl, authErr := client.Get()
21-
path := config.GetConfigFilePath()
22-
if authErr == nil && cl != nil && cl.Config == nil {
21+
var path string
22+
if authErr == nil && cl != nil && cl.ConfigPath != "" {
2323
path = cl.ConfigPath
24+
} else {
25+
path = config.GetConfigFilePath()
2426
}
2527

2628
_, err := fmt.Fprintf(c.Command.Command.OutOrStdout(), path)

internal/client/client.go

Lines changed: 2 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,16 @@ import (
44
"context"
55
"fmt"
66
"os"
7-
"path/filepath"
8-
"strings"
97
"sync"
108

11-
cfg "github.com/ionos-cloud/ionosctl/v6/internal/config"
129
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
1310
"github.com/ionos-cloud/ionosctl/v6/pkg/die"
14-
"github.com/ionos-cloud/sdk-go-bundle/shared"
15-
"github.com/ionos-cloud/sdk-go-bundle/shared/fileconfiguration"
1611
"github.com/spf13/viper"
17-
"gopkg.in/yaml.v3"
1812
)
1913

2014
var once sync.Once
2115
var instance *Client
2216

23-
func retrieveConfigFile() (*fileconfiguration.FileConfig, string, error) {
24-
// 1) --config flag
25-
if cfg, path, err := loadFromFlag(); cfg != nil || err != nil {
26-
return cfg, path, err
27-
}
28-
29-
// 2) SDK env var
30-
if cfg, path, err := loadFromEnvVar(); cfg != nil || err != nil {
31-
return cfg, path, err
32-
}
33-
34-
// 3) migrate old JSON if applicable
35-
if cfg, path, err := loadFromJSONMigration(); cfg != nil || err != nil {
36-
return cfg, path, err
37-
}
38-
39-
// 4) default SDK path
40-
if cfg, path, err := loadFromSDKDefault(); cfg != nil || err != nil {
41-
return cfg, path, err
42-
}
43-
44-
// note: if we reach this point, no config file was found
45-
// though old CLI behaviour was to return the default config path
46-
return nil, viper.GetString(constants.ArgConfig), nil
47-
}
48-
4917
func Get() (*Client, error) {
5018
var getClientErr error
5119

@@ -54,11 +22,12 @@ func Get() (*Client, error) {
5422

5523
once.Do(
5624
func() {
57-
config, path, err := retrieveConfigFile()
25+
src, err := retrieveConfigFile()
5826
if err != nil {
5927
getClientErr = fmt.Errorf("failed to retrieve config file: %w", err)
6028
return
6129
}
30+
config, path := src.Config, src.Path
6231

6332
if instance == nil && os.Getenv(constants.EnvToken) != "" {
6433
instance = newClient("", "", os.Getenv(constants.EnvToken), desiredURL)
@@ -166,71 +135,3 @@ func (c *Client) TestCreds() error {
166135
func EnforceClient(user, pass, token, hostUrl string) {
167136
instance = newClient(user, pass, token, hostUrl)
168137
}
169-
170-
func loadFromFlag() (*fileconfiguration.FileConfig, string, error) {
171-
path := viper.GetString(constants.ArgConfig)
172-
if path == "" {
173-
return nil, "", nil
174-
}
175-
cfg, err := fileconfiguration.New(path)
176-
if err != nil && !strings.Contains(err.Error(), "does not exist") {
177-
return nil, path, fmt.Errorf("failed to load config from --config '%s': %w", path, err)
178-
}
179-
return cfg, path, nil
180-
}
181-
182-
func loadFromEnvVar() (*fileconfiguration.FileConfig, string, error) {
183-
path := os.Getenv(shared.IonosFilePathEnvVar)
184-
if path == "" {
185-
return nil, "", nil
186-
}
187-
cfg, err := fileconfiguration.New(path)
188-
if err != nil && !strings.Contains(err.Error(), "does not exist") {
189-
return nil, path, fmt.Errorf(
190-
"failed to load config from env var %s='%s': %w",
191-
shared.IonosFilePathEnvVar, path, err,
192-
)
193-
}
194-
return cfg, path, nil
195-
}
196-
197-
func loadFromJSONMigration() (*fileconfiguration.FileConfig, string, error) {
198-
yamlPath := viper.GetString(constants.ArgConfig)
199-
if yamlPath == "" {
200-
return nil, "", nil
201-
}
202-
203-
jsonPath := filepath.Join(filepath.Dir(yamlPath), "config.json")
204-
if _, err := os.Stat(jsonPath); err != nil {
205-
return nil, "", nil // no JSON to migrate
206-
}
207-
208-
migrated, err := cfg.MigrateFromJSON(jsonPath)
209-
if err != nil {
210-
return nil, "", fmt.Errorf("failed migrating %s → YAML: %w", jsonPath, err)
211-
}
212-
if migrated == nil {
213-
return nil, "", nil
214-
}
215-
216-
out, _ := yaml.Marshal(migrated)
217-
if err := os.WriteFile(yamlPath, out, 0o600); err != nil {
218-
fmt.Fprintf(os.Stderr,
219-
"Warning: could not write migrated config to %s: %v\n",
220-
yamlPath, err,
221-
)
222-
}
223-
return migrated, yamlPath, nil
224-
}
225-
226-
func loadFromSDKDefault() (*fileconfiguration.FileConfig, string, error) {
227-
defaultPath, err := fileconfiguration.DefaultConfigFileName()
228-
if err != nil {
229-
return nil, defaultPath, fmt.Errorf("failed to get default config path: %w", err)
230-
}
231-
cfg, err := fileconfiguration.New(defaultPath)
232-
if err != nil && !strings.Contains(err.Error(), "does not exist") {
233-
return nil, defaultPath, fmt.Errorf("failed to load default config '%s': %w", defaultPath, err)
234-
}
235-
return cfg, defaultPath, nil
236-
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package client
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
cfg "github.com/ionos-cloud/ionosctl/v6/internal/config"
10+
"github.com/ionos-cloud/ionosctl/v6/internal/constants"
11+
"github.com/ionos-cloud/sdk-go-bundle/shared"
12+
"github.com/ionos-cloud/sdk-go-bundle/shared/fileconfiguration"
13+
"github.com/spf13/viper"
14+
"gopkg.in/yaml.v3"
15+
)
16+
17+
// ConfigSource holds a loaded FileConfig (or nil) plus the path it came from.
18+
type ConfigSource struct {
19+
Config *fileconfiguration.FileConfig
20+
Path string
21+
}
22+
23+
// retrieveConfigFile returns the first successful config or a fatal error.
24+
// If no config is found, ConfigSource.Config will be nil and Path will be the
25+
// default flag value (for backwards-compat).
26+
func retrieveConfigFile() (ConfigSource, error) {
27+
loaders := []func() (ConfigSource, error){
28+
loadFromFlag,
29+
loadFromEnvVar,
30+
loadFromJSONMigration,
31+
loadFromSDKDefault,
32+
}
33+
34+
for _, load := range loaders {
35+
src, err := load()
36+
if err != nil {
37+
// I/O or parse error: stop immediately
38+
return ConfigSource{}, err
39+
}
40+
if src.Config != nil {
41+
// found a valid config; return it
42+
return src, nil
43+
}
44+
// not found -> try next
45+
}
46+
47+
// none found -> return nil config, default flag path
48+
return ConfigSource{nil, viper.GetString(constants.ArgConfig)}, nil
49+
}
50+
51+
// loadFromFlag tries --config; if empty or missing -> (nil, ""), nil;
52+
// on I/O/parse error -> err; on success -> (*FileConfig, path), nil.
53+
func loadFromFlag() (ConfigSource, error) {
54+
path := viper.GetString(constants.ArgConfig)
55+
if path == "" {
56+
return ConfigSource{}, nil
57+
}
58+
return tryLoad(path, "--config")
59+
}
60+
61+
// loadFromEnvVar tries IONOS_CONFIG_FILE; same semantics as loadFromFlag.
62+
func loadFromEnvVar() (ConfigSource, error) {
63+
path := os.Getenv(shared.IonosFilePathEnvVar)
64+
if path == "" {
65+
return ConfigSource{}, nil
66+
}
67+
return tryLoad(path, fmt.Sprintf("env %s", shared.IonosFilePathEnvVar))
68+
}
69+
70+
// loadFromJSONMigration migrates legacy config.json (if present next to --config path).
71+
// On success returns the new YAML config; on any error returns err.
72+
func loadFromJSONMigration() (ConfigSource, error) {
73+
yamlPath := viper.GetString(constants.ArgConfig)
74+
if yamlPath == "" {
75+
return ConfigSource{}, nil
76+
}
77+
jsonPath := filepath.Join(filepath.Dir(yamlPath), "config.json")
78+
if _, err := os.Stat(jsonPath); err != nil {
79+
return ConfigSource{}, nil
80+
}
81+
82+
migrated, err := cfg.MigrateFromJSON(jsonPath)
83+
if err != nil {
84+
return ConfigSource{}, fmt.Errorf("failed migrating %q to YAML: %w", jsonPath, err)
85+
}
86+
if migrated == nil {
87+
return ConfigSource{}, nil
88+
}
89+
90+
out, _ := yaml.Marshal(migrated)
91+
if err := os.WriteFile(yamlPath, out, 0o600); err != nil {
92+
fmt.Fprintf(os.Stderr,
93+
"Warning: could not write migrated config to %s: %v\n",
94+
yamlPath, err)
95+
}
96+
return ConfigSource{migrated, yamlPath}, nil
97+
}
98+
99+
// loadFromSDKDefault tries the SDK default path (~/.ionos/config).
100+
func loadFromSDKDefault() (ConfigSource, error) {
101+
defaultPath, err := fileconfiguration.DefaultConfigFileName()
102+
if err != nil {
103+
return ConfigSource{}, fmt.Errorf("could not determine default config path: %w", err)
104+
}
105+
return tryLoad(defaultPath, "SDK default")
106+
}
107+
108+
// tryLoad loads a FileConfig from the given path.
109+
// If the file does not exist, it returns a nil FileConfig and the path.
110+
// If the file exists but cannot be loaded, it returns an error.
111+
// If the file is loaded successfully, it returns the FileConfig and the path.
112+
func tryLoad(path, sourceDesc string) (ConfigSource, error) {
113+
cfg, err := fileconfiguration.New(path)
114+
if err != nil {
115+
if strings.Contains(err.Error(), "does not exist") {
116+
return ConfigSource{nil, path}, nil
117+
}
118+
return ConfigSource{}, fmt.Errorf("%s: failed loading %q: %w", sourceDesc, path, err)
119+
}
120+
return ConfigSource{cfg, path}, nil
121+
}

0 commit comments

Comments
 (0)