Skip to content

Commit 3751493

Browse files
Gnoaleremyleone
authored andcommitted
feat(iam): support api-key get option to retrieve the ApiKey principal policies (scaleway#4709)
Co-authored-by: Rémy Léone <rleone@scaleway.com>
1 parent e1ab116 commit 3751493

29 files changed

+9214
-5352
lines changed

cmd/scw/testdata/test-all-usage-iam-api-key-get-usage.golden

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ USAGE:
66
scw iam api-key get <access-key ...> [arg=value ...]
77

88
ARGS:
9-
access-key Access key to search for
9+
access-key Access key to search for
10+
[with-policies=true] Display the set of policies associated with the API key
1011

1112
FLAGS:
1213
-h, --help help for get

core/human/marshal.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ func Marshal(data interface{}, opt *MarshalOpt) (string, error) {
4545
}
4646

4747
rType := rValue.Type()
48-
4948
// safely get the marshalerFunc
5049
marshalerFunc, _ := getMarshalerFunc(rType)
5150
isNil := isInterfaceNil(data)
@@ -84,6 +83,9 @@ func Marshal(data interface{}, opt *MarshalOpt) (string, error) {
8483
case rType.Kind() == reflect.Struct:
8584
return marshalStruct(rValue, opt)
8685

86+
case rType.Kind() == reflect.Map:
87+
return MarshalMap(rValue, opt)
88+
8789
// by default we use defaultMarshalerFunc
8890
default:
8991
return defaultMarshalerFunc(rValue.Interface(), opt)
@@ -481,3 +483,40 @@ func getDefaultFieldsOpt(t reflect.Type) []*MarshalFieldOpt {
481483

482484
return results
483485
}
486+
487+
func MarshalMap(m reflect.Value, opt *MarshalOpt) (string, error) {
488+
buffer := bytes.Buffer{}
489+
490+
w := tabwriter.NewWriter(&buffer, 5, 1, colPadding, ' ', tabwriter.ANSIGraphicsRendition)
491+
492+
mapKeys := m.MapKeys()
493+
sort.Slice(mapKeys, func(i, j int) bool {
494+
return mapKeys[i].String() < mapKeys[j].String()
495+
})
496+
497+
for _, mapKey := range mapKeys {
498+
mapValue := m.MapIndex(mapKey)
499+
500+
if mapValue.Type().Kind() == reflect.Slice {
501+
sort.Slice(mapValue.Interface(), func(i, j int) bool {
502+
return mapValue.Index(i).String() < mapValue.Index(j).String()
503+
})
504+
sliceLen := mapValue.Len()
505+
values := make([]string, sliceLen)
506+
for i := range sliceLen {
507+
values[i] = fmt.Sprint(mapValue.Index(i).Interface())
508+
}
509+
fmt.Fprintf(w, "%s\t%s\n", mapKey.String(), strings.Join(values, " "))
510+
} else {
511+
content, err := Marshal(mapValue.Interface(), opt)
512+
if err != nil {
513+
return "", err
514+
}
515+
fmt.Fprintf(w, "%s\t%s\n", mapKey.String(), content)
516+
}
517+
}
518+
519+
w.Flush()
520+
521+
return strings.TrimSpace(buffer.String()), nil
522+
}

core/human/marshal_test.go

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,20 @@ import (
1414
)
1515

1616
type Struct struct {
17-
String string
18-
Int int
19-
Bool bool
20-
Strings []string
21-
Time time.Time
22-
Struct *Struct
23-
Nil *Struct
24-
Structs []*Struct
25-
Map map[string]string
26-
Stringer Stringer
27-
StringerPtr *Stringer
28-
Size *scw.Size
29-
Bytes []byte
17+
String string
18+
Int int
19+
Bool bool
20+
Strings []string
21+
Time time.Time
22+
Struct *Struct
23+
Nil *Struct
24+
Structs []*Struct
25+
Map map[string]string
26+
Stringer Stringer
27+
StringerPtr *Stringer
28+
Size *scw.Size
29+
Bytes []byte
30+
MapStringList map[string][]string
3031
}
3132

3233
type StructAny struct {
@@ -112,6 +113,77 @@ func TestMarshal(t *testing.T) {
112113
"key1": "v1",
113114
"key2": "v2",
114115
},
116+
MapStringList: map[string][]string{
117+
"key1": {"v1", "v2"},
118+
"key2": {"v3", "v4"},
119+
},
120+
Stringer: Stringer{},
121+
StringerPtr: &Stringer{},
122+
Size: scw.SizePtr(13200),
123+
Bytes: []byte{0, 1},
124+
},
125+
result: `
126+
String This is a string
127+
Int 42
128+
Bool true
129+
Strings.0 s1
130+
Strings.1 s2
131+
Time ` + humanDate + `
132+
Struct.String -
133+
Struct.Int 0
134+
Struct.Bool false
135+
Struct.Time a long while ago
136+
Struct.Stringer a stringer
137+
Structs.0.String Nested string
138+
Structs.0.Int 0
139+
Structs.0.Bool false
140+
Structs.0.Time a long while ago
141+
Structs.0.Stringer a stringer
142+
Map.key1 v1
143+
Map.key2 v2
144+
Stringer a stringer
145+
StringerPtr a stringer
146+
Size 13 kB
147+
Bytes AAE=
148+
MapStringList.key1.0 v1
149+
MapStringList.key1.1 v2
150+
MapStringList.key2.0 v3
151+
MapStringList.key2.1 v4
152+
`,
153+
}))
154+
155+
t.Run("structWithMapsInSection", run(&testCase{
156+
opt: &human.MarshalOpt{
157+
Sections: []*human.MarshalSection{
158+
{
159+
FieldName: "MapStringList",
160+
},
161+
{
162+
FieldName: "Map",
163+
},
164+
},
165+
},
166+
data: &Struct{
167+
String: "This is a string",
168+
Int: 42,
169+
Bool: true,
170+
Strings: []string{"s1", "s2"},
171+
Time: date,
172+
Struct: &Struct{},
173+
Nil: nil,
174+
Structs: []*Struct{
175+
{
176+
String: "Nested string",
177+
},
178+
},
179+
Map: map[string]string{
180+
"key1": "v1",
181+
"key2": "v2",
182+
},
183+
MapStringList: map[string][]string{
184+
"key1": {"v1", "v2"},
185+
"key2": {"v3", "v4"},
186+
},
115187
Stringer: Stringer{},
116188
StringerPtr: &Stringer{},
117189
Size: scw.SizePtr(13200),
@@ -123,7 +195,7 @@ func TestMarshal(t *testing.T) {
123195
Bool true
124196
Strings.0 s1
125197
Strings.1 s2
126-
Time ` + humanDate + `
198+
Time 34 years ago
127199
Struct.String -
128200
Struct.Int 0
129201
Struct.Bool false
@@ -134,12 +206,18 @@ func TestMarshal(t *testing.T) {
134206
Structs.0.Bool false
135207
Structs.0.Time a long while ago
136208
Structs.0.Stringer a stringer
137-
Map.key1 v1
138-
Map.key2 v2
139209
Stringer a stringer
140210
StringerPtr a stringer
141211
Size 13 kB
142212
Bytes AAE=
213+
214+
Map String List:
215+
key1 v1 v2
216+
key2 v3 v4
217+
218+
Map:
219+
key1 v1
220+
key2 v2
143221
`,
144222
}))
145223

@@ -338,7 +416,7 @@ func Test_getStructFieldsIndex(t *testing.T) {
338416
},
339417
),
340418
},
341-
want: [][]int{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}},
419+
want: [][]int{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}},
342420
},
343421
}
344422
for _, tt := range tests {

core/testing.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,19 @@ func ExecStoreBeforeCmd(metaKey, cmd string) BeforeFunc {
590590
}
591591
}
592592

593+
// ExecStoreBeforeCmdWithResulter executes the given before command and register the result
594+
// in the context Meta at metaKey. The result is transformed by the resulter function.
595+
func ExecStoreBeforeCmdWithResulter(metaKey, cmd string, resulter func(any) any) BeforeFunc {
596+
return func(ctx *BeforeFuncCtx) error {
597+
args := cmdToArgs(ctx.Meta, cmd)
598+
ctx.Logger.Debugf("ExecStoreBeforeCmd: metaKey=%s args=%s\n", metaKey, args)
599+
result := ctx.ExecuteCmd(args)
600+
ctx.Meta[metaKey] = resulter(result)
601+
602+
return nil
603+
}
604+
}
605+
593606
func BeforeFuncOsExec(cmd string, args ...string) BeforeFunc {
594607
return func(ctx *BeforeFuncCtx) error {
595608
ctx.Logger.Debugf("BeforeFuncOsExec: cmd=%s args=%s\n", cmd, args)

core/testing_recorder.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,15 @@ import (
1818
func cassetteRequestFilter(i *cassette.Interaction) error {
1919
delete(i.Request.Headers, "x-auth-token")
2020
delete(i.Request.Headers, "X-Auth-Token")
21-
22-
orgIDRegex := regexp.MustCompile(`^organization_id=[0-9a-f-]{36}$`)
21+
orgIDRegex := regexp.MustCompile(`(.+)organization_id=[0-9a-f-]{36}(.+)`)
2322
tokenRegex := regexp.MustCompile(`^https://api\.scaleway\.com/account/v1/tokens/[0-9a-f-]{36}$`)
24-
apiKeyRegex := regexp.MustCompile(
25-
`^https://api\.scaleway\.com/iam/v1alpha1/api-keys/SCW[0-9A-Z]{17}$`,
26-
)
2723

2824
i.URL = orgIDRegex.ReplaceAllString(
2925
i.URL,
30-
"organization_id=11111111-1111-1111-1111-111111111111")
26+
"${1}organization_id=11111111-1111-1111-1111-111111111111${2}")
3127
i.URL = tokenRegex.ReplaceAllString(
3228
i.URL,
3329
"api.scaleway.com/account/v1/tokens/11111111-1111-1111-1111-111111111111")
34-
i.URL = apiKeyRegex.ReplaceAllString(
35-
i.URL,
36-
"api.scaleway.com/iam/v1alpha1/api-keys/SCWXXXXXXXXXXXXXXXXX")
3730

3831
return nil
3932
}

docs/commands/iam.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ scw iam api-key get <access-key ...> [arg=value ...]
135135
| Name | | Description |
136136
|------|---|-------------|
137137
| access-key | Required | Access key to search for |
138+
| with-policies | Default: `true` | Display the set of policies associated with the API key |
138139

139140

140141

internal/namespaces/iam/v1alpha1/custom.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ func GetCommands() *core.Commands {
5252
cmds.MustFind("iam", "policy", "create").Override(iamPolicyCreateBuilder)
5353
cmds.MustFind("iam", "policy", "get").Override(iamPolicyGetBuilder)
5454

55+
iamCmd := cmds.MustFind("iam", "api-key", "get")
56+
iamCmd.ArgsType = iamApiKeyCustomBuilder.argType
57+
iamCmd.ArgSpecs = iamApiKeyCustomBuilder.argSpecs
58+
iamCmd.Run = iamApiKeyCustomBuilder.run
59+
human.RegisterMarshalerFunc(apiKeyResponse{}, apiKeyMarshalerFunc)
60+
5561
return cmds
5662
}
5763

0 commit comments

Comments
 (0)