Skip to content

Commit 3cefd31

Browse files
authored
{config} Add --config-path into configuration load from aliyun configuration (#1266)
* add config-path into configure load for list cmd * unified error handling * adjust configure related cmds * adjust configure * adjust profile loading * fix tests * fix directory
1 parent 93d237f commit 3cefd31

17 files changed

+533
-320
lines changed

config/configuration.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"encoding/json"
1919
"fmt"
2020
"os"
21+
"path/filepath"
2122
"runtime"
2223

2324
"github.com/aliyun/aliyun-cli/v3/cli"
@@ -91,13 +92,13 @@ func (c *Configuration) PutProfile(profile Profile) {
9192
c.Profiles = append(c.Profiles, profile)
9293
}
9394

94-
func LoadCurrentProfile() (Profile, error) {
95+
func LoadOrCreateDefaultProfile() (Profile, error) {
9596
return LoadProfile(GetConfigPath()+"/"+configFile, "")
9697
}
9798

9899
func LoadProfile(path string, name string) (Profile, error) {
99100
var p Profile
100-
config, err := hookLoadConfiguration(LoadConfiguration)(path)
101+
config, err := hookLoadOrCreateConfiguration(LoadOrCreateConfiguration)(path)
101102
if err != nil {
102103
return p, fmt.Errorf("init config failed %v", err)
103104
}
@@ -153,7 +154,7 @@ func LoadProfileWithContext(ctx *cli.Context) (profile Profile, err error) {
153154
return
154155
}
155156

156-
func LoadConfiguration(path string) (conf *Configuration, err error) {
157+
func LoadOrCreateConfiguration(path string) (conf *Configuration, err error) {
157158
_, statErr := os.Stat(path)
158159
if os.IsNotExist(statErr) {
159160
conf, err = MigrateLegacyConfiguration()
@@ -183,6 +184,34 @@ func LoadConfiguration(path string) (conf *Configuration, err error) {
183184
return
184185
}
185186

187+
func LoadConfigurationFromFile(filePath string) (conf *Configuration, err error) {
188+
bytes, err := os.ReadFile(filePath)
189+
if err != nil {
190+
err = fmt.Errorf("reading config from '%s' failed %v", filePath, err)
191+
return nil, err
192+
}
193+
194+
conf, err = NewConfigFromBytes(bytes)
195+
return
196+
}
197+
198+
func LoadConfigurationWithContext(ctx *cli.Context) (conf *Configuration, err error) {
199+
confPath := hookGetHomePath(GetHomePath)() + configPath + "/" + configFile
200+
if customPath, ok := ConfigurePathFlag(ctx.Flags()).GetValue(); ok {
201+
if _, err := hookFileStat(os.Stat)(customPath); os.IsNotExist(err) {
202+
// invalid config path from user input should be blocked
203+
return nil, fmt.Errorf("config path input does not exist: %s", customPath)
204+
}
205+
confPath = customPath
206+
}
207+
_, statErr := os.Stat(confPath)
208+
if os.IsNotExist(statErr) {
209+
return nil, statErr
210+
}
211+
212+
return LoadConfigurationFromFile(confPath)
213+
}
214+
186215
func SaveConfiguration(config *Configuration) (err error) {
187216
// fmt.Printf("conf %v\n", config)
188217
bytes, err := json.MarshalIndent(config, "", "\t")
@@ -194,6 +223,26 @@ func SaveConfiguration(config *Configuration) (err error) {
194223
return
195224
}
196225

226+
func SaveConfigurationWithContext(ctx *cli.Context, config *Configuration) (err error) {
227+
bytes, err := json.MarshalIndent(config, "", "\t")
228+
if err != nil {
229+
return
230+
}
231+
confFilePath := hookGetHomePath(GetHomePath)() + configPath + "/" + configFile
232+
if customPath, ok := ConfigurePathFlag(ctx.Flags()).GetValue(); ok {
233+
confFilePath = customPath
234+
}
235+
236+
dir := filepath.Dir(confFilePath)
237+
if _, err := os.Stat(dir); os.IsNotExist(err) {
238+
if err := os.MkdirAll(dir, 0755); err != nil {
239+
panic(fmt.Errorf("failed to create config directory %q: %w", dir, err))
240+
}
241+
}
242+
err = os.WriteFile(confFilePath, bytes, 0600)
243+
return
244+
}
245+
197246
func NewConfigFromBytes(bytes []byte) (conf *Configuration, err error) {
198247
conf = NewConfiguration()
199248
err = json.Unmarshal(bytes, conf)

config/configuration_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,12 @@ func TestConfiguration(t *testing.T) {
107107
}
108108

109109
func TestLoadProfile(t *testing.T) {
110-
originhook := hookLoadConfiguration
110+
originhook := hookLoadOrCreateConfiguration
111111
w := new(bytes.Buffer)
112112
defer func() {
113-
hookLoadConfiguration = originhook
113+
hookLoadOrCreateConfiguration = originhook
114114
}()
115-
hookLoadConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) {
115+
hookLoadOrCreateConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) {
116116
return func(path string) (*Configuration, error) {
117117
return &Configuration{CurrentProfile: "default", Profiles: []Profile{{Name: "default", Mode: AK, AccessKeyId: "default_aliyun_access_key_id", AccessKeySecret: "default_aliyun_access_key_secret", OutputFormat: "json"}, {Name: "aaa", Mode: AK, AccessKeyId: "sdf", AccessKeySecret: "ddf", OutputFormat: "json"}}}, nil
118118
}
@@ -129,13 +129,13 @@ func TestLoadProfile(t *testing.T) {
129129

130130
//LoadCurrentProfile testcase
131131
w.Reset()
132-
p, err = LoadCurrentProfile()
132+
p, err = LoadOrCreateDefaultProfile()
133133
assert.Nil(t, err)
134134
p.parent = nil
135135
assert.Equal(t, Profile{Name: "default", Mode: AK, AccessKeyId: "default_aliyun_access_key_id", AccessKeySecret: "default_aliyun_access_key_secret", OutputFormat: "json"}, p)
136136

137137
//testcase 3
138-
hookLoadConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) {
138+
hookLoadOrCreateConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) {
139139
return func(path string) (*Configuration, error) {
140140
return &Configuration{}, errors.New("error")
141141
}
@@ -225,7 +225,7 @@ func TestSaveConfiguration(t *testing.T) {
225225
assert.Equal(t, string(bytes), string(buf[:n]))
226226
}
227227

228-
func TestLoadConfiguration(t *testing.T) {
228+
func TestLoadOrCreateConfiguration(t *testing.T) {
229229
orighookGetHomePath := hookGetHomePath
230230
defer func() {
231231
os.RemoveAll("./.aliyun")
@@ -239,7 +239,7 @@ func TestLoadConfiguration(t *testing.T) {
239239
w := new(bytes.Buffer)
240240

241241
//testcase 1
242-
cf, err := LoadConfiguration(GetConfigPath() + "/" + configFile)
242+
cf, err := LoadOrCreateConfiguration(GetConfigPath() + "/" + configFile)
243243
assert.Nil(t, err)
244244
assert.Equal(t, &Configuration{CurrentProfile: "default", Profiles: []Profile{{Name: "default", Mode: "", OutputFormat: "json", Language: "en"}}}, cf)
245245
conf := &Configuration{Profiles: []Profile{{Language: "en", Name: "default", Mode: "AK", AccessKeyId: "access_key_id", AccessKeySecret: "access_key_secret", RegionId: "cn-hangzhou", OutputFormat: "json"}}}
@@ -248,18 +248,18 @@ func TestLoadConfiguration(t *testing.T) {
248248

249249
//testcase 2
250250
w.Reset()
251-
cf, err = LoadConfiguration(GetConfigPath() + "/" + configFile)
251+
cf, err = LoadOrCreateConfiguration(GetConfigPath() + "/" + configFile)
252252
assert.Equal(t, &Configuration{CurrentProfile: "", Profiles: []Profile{{Name: "default", Mode: "AK", AccessKeyId: "access_key_id", AccessKeySecret: "access_key_secret", RegionId: "cn-hangzhou", OutputFormat: "json", Language: "en"}}}, cf)
253253
assert.Nil(t, err)
254254

255255
}
256256

257257
func TestLoadProfileWithContext(t *testing.T) {
258-
originhook := hookLoadConfiguration
258+
originhook := hookLoadOrCreateConfiguration
259259
defer func() {
260-
hookLoadConfiguration = originhook
260+
hookLoadOrCreateConfiguration = originhook
261261
}()
262-
hookLoadConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) {
262+
hookLoadOrCreateConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) {
263263
return func(path string) (*Configuration, error) {
264264
return &Configuration{CurrentProfile: "default", Profiles: []Profile{{Name: "default", Mode: AK, AccessKeyId: "default_aliyun_access_key_id", AccessKeySecret: "default_aliyun_access_key_secret", OutputFormat: "json"}, {Name: "aaa", Mode: AK, AccessKeyId: "sdf", AccessKeySecret: "ddf", OutputFormat: "json"}}}, nil
265265
}

config/configure.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,19 @@ var signInMap = map[string]string{
5353
"INTL": "https://signin.alibabacloud.com",
5454
}
5555

56-
var hookLoadConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) {
56+
var hookLoadOrCreateConfiguration = func(fn func(path string) (*Configuration, error)) func(path string) (*Configuration, error) {
5757
return fn
5858
}
5959

60-
var hookSaveConfiguration = func(fn func(config *Configuration) error) func(config *Configuration) error {
60+
var hookSaveConfigurationWithContext = func(fn func(ctx *cli.Context, config *Configuration) error) func(ctx *cli.Context, config *Configuration) error {
61+
return fn
62+
}
63+
64+
var hookLoadConfigurationWithContext = func(fn func(ctx *cli.Context) (*Configuration, error)) func(ctx *cli.Context) (*Configuration, error) {
65+
return fn
66+
}
67+
68+
var hookFileStat = func(fn func(name string) (os.FileInfo, error)) func(name string) (os.FileInfo, error) {
6169
return fn
6270
}
6371

@@ -110,8 +118,8 @@ var doConfigureProxy = func(ctx *cli.Context, profileName string, mode string) e
110118
return doConfigure(ctx, profileName, mode)
111119
}
112120

113-
func loadConfiguration() (*Configuration, error) {
114-
return hookLoadConfiguration(LoadConfiguration)(GetConfigPath() + "/" + configFile)
121+
func loadOrCreateConfiguration() (*Configuration, error) {
122+
return hookLoadOrCreateConfiguration(LoadOrCreateConfiguration)(GetConfigPath() + "/" + configFile)
115123
}
116124

117125
func NewConfigureCommand() *cli.Command {
@@ -120,16 +128,28 @@ func NewConfigureCommand() *cli.Command {
120128
Short: i18n.T(
121129
"configure credential and settings",
122130
"配置身份认证和其他信息"),
123-
Usage: "configure --mode {AK|RamRoleArn|EcsRamRole|OIDC|External|CredentialsURI|ChainableRamRoleArn|CloudSSO|OAuth} --profile <profileName>",
131+
Usage: "configure --mode {AK|RamRoleArn|EcsRamRole|OIDC|External|CredentialsURI|ChainableRamRoleArn|CloudSSO|OAuth} --profile <profileName> [--config-path <configPath>]",
124132
Run: func(ctx *cli.Context, args []string) error {
125133
if len(args) > 0 {
126134
return cli.NewInvalidCommandError(args[0], ctx)
127135
}
128136
profileName, _ := ProfileFlag(ctx.Flags()).GetValue()
129137
mode, _ := ModeFlag(ctx.Flags()).GetValue()
130138
if mode == "" {
139+
var err error
140+
var conf *Configuration
141+
if customPath, ok := ConfigurePathFlag(ctx.Flags()).GetValue(); ok {
142+
if _, err := hookFileStat(os.Stat)(customPath); !os.IsNotExist(err) {
143+
conf, _ = LoadConfigurationFromFile(customPath)
144+
if err != nil {
145+
return err
146+
}
147+
}
148+
}
149+
if conf == nil {
150+
conf, err = loadOrCreateConfiguration()
151+
}
131152
// 检查 profileName 是否存在
132-
conf, err := loadConfiguration()
133153
if err == nil {
134154
if profileName == "" {
135155
profileName = conf.CurrentProfile
@@ -157,7 +177,19 @@ func NewConfigureCommand() *cli.Command {
157177
func doConfigure(ctx *cli.Context, profileName string, mode string) error {
158178
w := ctx.Stdout()
159179

160-
conf, err := loadConfiguration()
180+
var err error
181+
var conf *Configuration
182+
if customPath, ok := ConfigurePathFlag(ctx.Flags()).GetValue(); ok {
183+
if _, err := hookFileStat(os.Stat)(customPath); !os.IsNotExist(err) {
184+
conf, err = LoadConfigurationFromFile(customPath)
185+
if err != nil {
186+
return err
187+
}
188+
}
189+
}
190+
if conf == nil {
191+
conf, err = loadOrCreateConfiguration()
192+
}
161193
if err != nil {
162194
return err
163195
}
@@ -277,7 +309,7 @@ func doConfigure(ctx *cli.Context, profileName string, mode string) error {
277309

278310
conf.PutProfile(cp)
279311
conf.CurrentProfile = cp.Name
280-
err = hookSaveConfiguration(SaveConfiguration)(conf)
312+
err = hookSaveConfigurationWithContext(SaveConfigurationWithContext)(ctx, conf)
281313
// cp 要在下文的 DoHello 中使用,所以 需要建立 parent 的关系
282314
cp.parent = conf
283315

config/configure_delete.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,32 @@
1515
package config
1616

1717
import (
18+
"fmt"
19+
1820
"github.com/aliyun/aliyun-cli/v3/cli"
1921
"github.com/aliyun/aliyun-cli/v3/i18n"
2022
)
2123

2224
func NewConfigureDeleteCommand() *cli.Command {
2325
return &cli.Command{
2426
Name: "delete",
25-
Usage: "delete --profile <profileName>",
27+
Usage: "delete --profile <profileName> [--config-path <configPath>]",
2628
Short: i18n.T("delete the specified profile", "删除指定配置"),
2729
Run: func(c *cli.Context, args []string) error {
2830
profileName, ok := ProfileFlag(c.Flags()).GetValue()
2931
if !ok {
30-
cli.Errorf(c.Stderr(), "missing --profile <profileName>\n")
31-
cli.Noticef(c.Stderr(), "\nusage:\n aliyun configure delete --profile <profileName>\n")
32-
return nil
32+
cli.Noticef(c.Stderr(), "\nusage:\n aliyun configure delete --profile <profileName> [--config-path <configPath>]\n")
33+
return fmt.Errorf("missing --profile <profileName>")
3334
}
34-
doConfigureDelete(c, profileName)
35-
return nil
35+
return doConfigureDelete(c, profileName)
3636
},
3737
}
3838
}
3939

40-
func doConfigureDelete(ctx *cli.Context, profileName string) {
41-
conf, err := loadConfiguration()
40+
func doConfigureDelete(ctx *cli.Context, profileName string) error {
41+
conf, err := hookLoadConfigurationWithContext(LoadConfigurationWithContext)(ctx)
4242
if err != nil {
43-
cli.Errorf(ctx.Stderr(), "ERROR: load configure failed: %v\n", err)
43+
return fmt.Errorf("ERROR: load configure failed: %v", err)
4444
}
4545
deleted := false
4646
r := make([]Profile, 0)
@@ -53,8 +53,7 @@ func doConfigureDelete(ctx *cli.Context, profileName string) {
5353
}
5454

5555
if !deleted {
56-
cli.Errorf(ctx.Stderr(), "Error: configuration profile `%s` not found\n", profileName)
57-
return
56+
return fmt.Errorf("error: configuration profile `%s` not found", profileName)
5857
}
5958

6059
conf.Profiles = r
@@ -66,8 +65,9 @@ func doConfigureDelete(ctx *cli.Context, profileName string) {
6665
}
6766
}
6867

69-
err = hookSaveConfiguration(SaveConfiguration)(conf)
68+
err = hookSaveConfigurationWithContext(SaveConfigurationWithContext)(ctx, conf)
7069
if err != nil {
71-
cli.Errorf(ctx.Stderr(), "Error: save configuration failed %s\n", err)
70+
return fmt.Errorf("error: save configuration failed %v", err)
7271
}
72+
return nil
7373
}

0 commit comments

Comments
 (0)