Skip to content

Commit 2151cb4

Browse files
authored
feat(config): add info command (#2741)
1 parent 8dfd62f commit 2151cb4

File tree

9 files changed

+317
-0
lines changed

9 files changed

+317
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
2+
πŸŸ₯πŸŸ₯πŸŸ₯ STDERR️️ πŸŸ₯πŸŸ₯πŸŸ₯️
3+
Get config values from the config file for the current profile
4+
5+
USAGE:
6+
scw config info
7+
8+
EXAMPLES:
9+
Get the default config values
10+
scw config info
11+
12+
Get the config values of the profile 'prod'
13+
scw -p prod config info
14+
15+
FLAGS:
16+
-h, --help help for info
17+
18+
GLOBAL FLAGS:
19+
-c, --config string The path to the config file
20+
-D, --debug Enable debug mode
21+
-o, --output string Output format: json or human, see 'scw help output' for more info (default "human")
22+
-p, --profile string The config profile to use
23+
24+
SEE ALSO:
25+
# Config management help
26+
scw config --help

β€Ždocs/commands/config.mdβ€Ž

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Read more about the config management engine at https://github.com/scaleway/scal
2929
- [Destroy the config file](#destroy-the-config-file)
3030
- [Dump the config file](#dump-the-config-file)
3131
- [Get a value from the config file](#get-a-value-from-the-config-file)
32+
- [Get config values from the config file for the current profile](#get-config-values-from-the-config-file-for-the-current-profile)
3233
- [Allows the deletion of a profile from the config file](#allows-the-deletion-of-a-profile-from-the-config-file)
3334
- [Mark a profile as active in the config file](#mark-a-profile-as-active-in-the-config-file)
3435
- [Delete a profile from the config file](#delete-a-profile-from-the-config-file)
@@ -101,6 +102,35 @@ scw -p prod config get default_region
101102

102103

103104

105+
## Get config values from the config file for the current profile
106+
107+
108+
109+
110+
111+
**Usage:**
112+
113+
```
114+
scw config info
115+
```
116+
117+
118+
**Examples:**
119+
120+
121+
Get the default config values
122+
```
123+
scw config info
124+
```
125+
126+
Get the config values of the profile 'prod'
127+
```
128+
scw -p prod config info
129+
```
130+
131+
132+
133+
104134
## Allows the deletion of a profile from the config file
105135

106136

β€Žinternal/core/testing.goβ€Ž

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,58 @@ func TestCheckExitCode(expectedCode int) TestCheck {
573573
}
574574
}
575575

576+
// GoldenReplacement describe patterns to be replaced in goldens
577+
type GoldenReplacement struct {
578+
// Line will be matched using this regex
579+
Pattern *regexp.Regexp
580+
// Content that will replace the matched regex
581+
// This is the format for repl in (*regexp.Regexp).ReplaceAll
582+
// You can use $ to represent groups $1, $2...
583+
Replacement string
584+
}
585+
586+
// goldenReplacePatterns replace the list of patterns with their given replacement
587+
func goldenReplacePatterns(golden string, replacements ...GoldenReplacement) (string, error) {
588+
var matchFailed []string
589+
var changedGolden = golden
590+
591+
for _, replacement := range replacements {
592+
if !replacement.Pattern.MatchString(changedGolden) {
593+
matchFailed = append(matchFailed, replacement.Pattern.String())
594+
continue
595+
}
596+
changedGolden = replacement.Pattern.ReplaceAllString(changedGolden, replacement.Replacement)
597+
}
598+
599+
if len(matchFailed) > 0 {
600+
return changedGolden, fmt.Errorf("failed to match regex in golden: %#q", matchFailed)
601+
}
602+
return changedGolden, nil
603+
}
604+
605+
// TestCheckGoldenAndReplacePatterns assert stderr and stdout using golden,
606+
// golden are matched against given regex and edited with replacements
607+
func TestCheckGoldenAndReplacePatterns(replacements ...GoldenReplacement) TestCheck {
608+
return func(t *testing.T, ctx *CheckFuncCtx) {
609+
actual := marshalGolden(t, ctx)
610+
actual, actualReplaceErr := goldenReplacePatterns(actual, replacements...)
611+
612+
goldenPath := getTestFilePath(t, ".golden")
613+
// In order to avoid diff in goldens we set all timestamp to the same date
614+
if *UpdateGoldens {
615+
require.NoError(t, os.MkdirAll(path.Dir(goldenPath), 0755))
616+
require.NoError(t, os.WriteFile(goldenPath, []byte(actual), 0644)) //nolint:gosec
617+
}
618+
619+
expected, err := os.ReadFile(goldenPath)
620+
require.NoError(t, err, "expected to find golden file %s", goldenPath)
621+
expectedString, expectedReplaceErr := goldenReplacePatterns(string(expected), replacements...)
622+
assert.Equal(t, expectedString, actual)
623+
assert.Nil(t, actualReplaceErr, "failed to match test output with regexes")
624+
assert.Nil(t, expectedReplaceErr, "failed to match stored golden with regexes")
625+
}
626+
}
627+
576628
// TestCheckGolden assert stderr and stdout using golden
577629
func TestCheckGolden() TestCheck {
578630
return func(t *testing.T, ctx *CheckFuncCtx) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package core
2+
3+
import (
4+
"regexp"
5+
"testing"
6+
7+
"github.com/alecthomas/assert"
8+
)
9+
10+
func TestGoldenIgnoreLines(t *testing.T) {
11+
original := `
12+
Line1
13+
Line2
14+
Line3
15+
Line4`
16+
expected := `
17+
Line1
18+
Line4`
19+
actual, err := goldenReplacePatterns(original, GoldenReplacement{
20+
Pattern: regexp.MustCompile("Line2\nLine3\n"),
21+
Replacement: "",
22+
})
23+
assert.Nil(t, err)
24+
assert.Equal(t, expected, actual)
25+
26+
expected2 := `
27+
Line4
28+
Line3
29+
Line2
30+
Line1`
31+
actual2, err := goldenReplacePatterns(original,
32+
GoldenReplacement{
33+
Pattern: regexp.MustCompile("(?s)(Line1).*(Line2).*(Line3).*(Line4)"),
34+
Replacement: "$4\n$3\n$2\n$1",
35+
})
36+
assert.Nil(t, err)
37+
assert.Equal(t, expected2, actual2)
38+
}

β€Žinternal/namespaces/config/commands.goβ€Ž

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func GetCommands() *core.Commands {
3030
configActivateProfileCommand(),
3131
configResetCommand(),
3232
configDestroyCommand(),
33+
configInfoCommand(),
3334
)
3435
}
3536

@@ -535,6 +536,67 @@ func configDestroyCommand() *core.Command {
535536
}
536537
}
537538

539+
// configInfoCommand values from the scaleway config for the current profile
540+
func configInfoCommand() *core.Command {
541+
type configInfoArgs struct{}
542+
543+
return &core.Command{
544+
Short: `Get config values from the config file for the current profile`,
545+
Namespace: "config",
546+
Resource: "info",
547+
AllowAnonymousClient: true,
548+
ArgsType: reflect.TypeOf(configInfoArgs{}),
549+
ArgSpecs: core.ArgSpecs{},
550+
Examples: []*core.Example{
551+
{
552+
Short: "Get the default config values",
553+
Raw: "scw config info",
554+
},
555+
{
556+
Short: "Get the config values of the profile 'prod'",
557+
Raw: "scw -p prod config info",
558+
},
559+
},
560+
SeeAlsos: []*core.SeeAlso{
561+
{
562+
Short: "Config management help",
563+
Command: "scw config --help",
564+
},
565+
},
566+
Run: func(ctx context.Context, argsI interface{}) (i interface{}, e error) {
567+
config, err := scw.LoadConfigFromPath(core.ExtractConfigPath(ctx))
568+
if err != nil {
569+
return nil, err
570+
}
571+
572+
profileName := core.ExtractProfileName(ctx)
573+
// use config.GetProfile instead of getProfile as we want the profile merged with the default
574+
profile, err := config.GetProfile(profileName)
575+
if err != nil {
576+
return nil, err
577+
}
578+
579+
values := map[string]any{}
580+
for _, key := range getProfileKeys() {
581+
value, err := getProfileValue(profile, key)
582+
if err == nil && value != nil {
583+
values[key] = value
584+
}
585+
}
586+
587+
return struct {
588+
ConfigPath string
589+
ProfileName string
590+
Profile map[string]any
591+
}{
592+
ConfigPath: core.ExtractConfigPath(ctx),
593+
ProfileName: core.ExtractProfileName(ctx),
594+
Profile: values,
595+
}, nil
596+
},
597+
}
598+
}
599+
538600
// Helper functions
539601
func getProfileValue(profile *scw.Profile, fieldName string) (interface{}, error) {
540602
field, err := getProfileField(profile, fieldName)

β€Žinternal/namespaces/config/commands_test.goβ€Ž

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
import (
44
"os"
55
"path"
6+
"regexp"
67
"testing"
78

89
"github.com/alecthomas/assert"
@@ -255,6 +256,53 @@ func Test_ConfigDestroyCommand(t *testing.T) {
255256
}))
256257
}
257258

259+
func Test_ConfigInfoCommand(t *testing.T) {
260+
// replace ConfigPath lines with "/tmp/scw/.config/scw/config.yaml"
261+
configPathReplacements := []core.GoldenReplacement{
262+
{
263+
Pattern: regexp.MustCompile(`(ConfigPath\s).*`),
264+
Replacement: "$1/tmp/scw/.config/scw/config.yaml",
265+
},
266+
{
267+
Pattern: regexp.MustCompile(`(?m)^(\s*"ConfigPath":\s*").*(",)`),
268+
Replacement: "$1/tmp/scw/.config/scw/config.yaml$2",
269+
},
270+
}
271+
272+
t.Run("Simple", core.Test(&core.TestConfig{
273+
Commands: GetCommands(),
274+
BeforeFunc: beforeFuncCreateFullConfig(),
275+
Cmd: "scw config info",
276+
Check: core.TestCheckCombine(
277+
core.TestCheckExitCode(0),
278+
core.TestCheckGoldenAndReplacePatterns(configPathReplacements...),
279+
),
280+
TmpHomeDir: true,
281+
}))
282+
283+
t.Run("Profile", core.Test(&core.TestConfig{
284+
Commands: GetCommands(),
285+
BeforeFunc: beforeFuncCreateFullConfig(),
286+
Cmd: "scw -p p1 config info",
287+
Check: core.TestCheckCombine(
288+
core.TestCheckExitCode(0),
289+
core.TestCheckGoldenAndReplacePatterns(configPathReplacements...),
290+
),
291+
TmpHomeDir: true,
292+
}))
293+
294+
t.Run("Unknown Profile", core.Test(&core.TestConfig{
295+
Commands: GetCommands(),
296+
BeforeFunc: beforeFuncCreateFullConfig(),
297+
Cmd: "scw -p test config info",
298+
Check: core.TestCheckCombine(
299+
core.TestCheckExitCode(1),
300+
core.TestCheckGolden(),
301+
),
302+
TmpHomeDir: true,
303+
}))
304+
}
305+
258306
func checkConfig(f func(t *testing.T, config *scw.Config)) core.TestCheck {
259307
return func(t *testing.T, ctx *core.CheckFuncCtx) {
260308
homeDir := ctx.OverrideEnv["HOME"]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
2+
🟩🟩🟩 STDOUT️ 🟩🟩🟩️
3+
ConfigPath /tmp/scw3107607218/.config/scw/config.yaml
4+
ProfileName p1
5+
Profile.access-key SCWP1XXXXXXXXXXXXXXX
6+
Profile.api-url https://p1-mock-api-url.com
7+
Profile.default-organization-id 11111111-1111-1111-1111-111111111111
8+
Profile.default-region fr-par
9+
Profile.default-zone fr-par-1
10+
Profile.insecure true
11+
Profile.secret-key 11111111-1111-1111-1111-111111111111
12+
🟩🟩🟩 JSON STDOUT 🟩🟩🟩
13+
{
14+
"ConfigPath": "/tmp/scw3107607218/.config/scw/config.yaml",
15+
"ProfileName": "p1",
16+
"Profile": {
17+
"access-key": "SCWP1XXXXXXXXXXXXXXX",
18+
"api-url": "https://p1-mock-api-url.com",
19+
"default-organization-id": "11111111-1111-1111-1111-111111111111",
20+
"default-project-id": null,
21+
"default-region": "fr-par",
22+
"default-zone": "fr-par-1",
23+
"insecure": true,
24+
"secret-key": "11111111-1111-1111-1111-111111111111",
25+
"send-telemetry": null
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
2+
🟩🟩🟩 STDOUT️ 🟩🟩🟩️
3+
ConfigPath /tmp/scw1635042996/.config/scw/config.yaml
4+
ProfileName default
5+
Profile.access-key SCWXXXXXXXXXXXXXXXXX
6+
Profile.default-organization-id 11111111-1111-1111-1111-111111111111
7+
Profile.default-region fr-par
8+
Profile.default-zone fr-par-1
9+
Profile.insecure true
10+
Profile.secret-key 11111111-1111-1111-1111-111111111111
11+
Profile.send-telemetry true
12+
🟩🟩🟩 JSON STDOUT 🟩🟩🟩
13+
{
14+
"ConfigPath": "/tmp/scw1635042996/.config/scw/config.yaml",
15+
"ProfileName": "default",
16+
"Profile": {
17+
"access-key": "SCWXXXXXXXXXXXXXXXXX",
18+
"api-url": null,
19+
"default-organization-id": "11111111-1111-1111-1111-111111111111",
20+
"default-project-id": null,
21+
"default-region": "fr-par",
22+
"default-zone": "fr-par-1",
23+
"insecure": true,
24+
"secret-key": "11111111-1111-1111-1111-111111111111",
25+
"send-telemetry": true
26+
}
27+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
🎲🎲🎲 EXIT CODE: 1 🎲🎲🎲
2+
πŸŸ₯πŸŸ₯πŸŸ₯ STDERR️️ πŸŸ₯πŸŸ₯πŸŸ₯️
3+
scaleway-sdk-go: given profile test does not exist
4+
πŸŸ₯πŸŸ₯πŸŸ₯ JSON STDERR πŸŸ₯πŸŸ₯πŸŸ₯
5+
{
6+
"error": "scaleway-sdk-go: given profile test does not exist"
7+
}

0 commit comments

Comments
Β (0)