Skip to content

feat(iam): support api-key get option to retrieve the ApiKey principal policies #4709

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ USAGE:
scw iam api-key get <access-key ...> [arg=value ...]

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

FLAGS:
-h, --help help for get
Expand Down
41 changes: 40 additions & 1 deletion core/human/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ func Marshal(data interface{}, opt *MarshalOpt) (string, error) {
}

rType := rValue.Type()

// safely get the marshalerFunc
marshalerFunc, _ := getMarshalerFunc(rType)
isNil := isInterfaceNil(data)
Expand Down Expand Up @@ -84,6 +83,9 @@ func Marshal(data interface{}, opt *MarshalOpt) (string, error) {
case rType.Kind() == reflect.Struct:
return marshalStruct(rValue, opt)

case rType.Kind() == reflect.Map:
return MarshalMap(rValue, opt)

// by default we use defaultMarshalerFunc
default:
return defaultMarshalerFunc(rValue.Interface(), opt)
Expand Down Expand Up @@ -481,3 +483,40 @@ func getDefaultFieldsOpt(t reflect.Type) []*MarshalFieldOpt {

return results
}

func MarshalMap(m reflect.Value, opt *MarshalOpt) (string, error) {
buffer := bytes.Buffer{}

w := tabwriter.NewWriter(&buffer, 5, 1, colPadding, ' ', tabwriter.ANSIGraphicsRendition)

mapKeys := m.MapKeys()
sort.Slice(mapKeys, func(i, j int) bool {
return mapKeys[i].String() < mapKeys[j].String()
})

for _, mapKey := range mapKeys {
mapValue := m.MapIndex(mapKey)

if mapValue.Type().Kind() == reflect.Slice {
sort.Slice(mapValue.Interface(), func(i, j int) bool {
return mapValue.Index(i).String() < mapValue.Index(j).String()
})
sliceLen := mapValue.Len()
values := make([]string, sliceLen)
for i := range sliceLen {
values[i] = fmt.Sprint(mapValue.Index(i).Interface())
}
fmt.Fprintf(w, "%s\t%s\n", mapKey.String(), strings.Join(values, " "))
} else {
content, err := Marshal(mapValue.Interface(), opt)
if err != nil {
return "", err
}
fmt.Fprintf(w, "%s\t%s\n", mapKey.String(), content)
}
}

w.Flush()

return strings.TrimSpace(buffer.String()), nil
}
112 changes: 95 additions & 17 deletions core/human/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ import (
)

type Struct struct {
String string
Int int
Bool bool
Strings []string
Time time.Time
Struct *Struct
Nil *Struct
Structs []*Struct
Map map[string]string
Stringer Stringer
StringerPtr *Stringer
Size *scw.Size
Bytes []byte
String string
Int int
Bool bool
Strings []string
Time time.Time
Struct *Struct
Nil *Struct
Structs []*Struct
Map map[string]string
Stringer Stringer
StringerPtr *Stringer
Size *scw.Size
Bytes []byte
MapStringList map[string][]string
}

type StructAny struct {
Expand Down Expand Up @@ -112,6 +113,77 @@ func TestMarshal(t *testing.T) {
"key1": "v1",
"key2": "v2",
},
MapStringList: map[string][]string{
"key1": {"v1", "v2"},
"key2": {"v3", "v4"},
},
Stringer: Stringer{},
StringerPtr: &Stringer{},
Size: scw.SizePtr(13200),
Bytes: []byte{0, 1},
},
result: `
String This is a string
Int 42
Bool true
Strings.0 s1
Strings.1 s2
Time ` + humanDate + `
Struct.String -
Struct.Int 0
Struct.Bool false
Struct.Time a long while ago
Struct.Stringer a stringer
Structs.0.String Nested string
Structs.0.Int 0
Structs.0.Bool false
Structs.0.Time a long while ago
Structs.0.Stringer a stringer
Map.key1 v1
Map.key2 v2
Stringer a stringer
StringerPtr a stringer
Size 13 kB
Bytes AAE=
MapStringList.key1.0 v1
MapStringList.key1.1 v2
MapStringList.key2.0 v3
MapStringList.key2.1 v4
`,
}))

t.Run("structWithMapsInSection", run(&testCase{
opt: &human.MarshalOpt{
Sections: []*human.MarshalSection{
{
FieldName: "MapStringList",
},
{
FieldName: "Map",
},
},
},
data: &Struct{
String: "This is a string",
Int: 42,
Bool: true,
Strings: []string{"s1", "s2"},
Time: date,
Struct: &Struct{},
Nil: nil,
Structs: []*Struct{
{
String: "Nested string",
},
},
Map: map[string]string{
"key1": "v1",
"key2": "v2",
},
MapStringList: map[string][]string{
"key1": {"v1", "v2"},
"key2": {"v3", "v4"},
},
Stringer: Stringer{},
StringerPtr: &Stringer{},
Size: scw.SizePtr(13200),
Expand All @@ -123,7 +195,7 @@ func TestMarshal(t *testing.T) {
Bool true
Strings.0 s1
Strings.1 s2
Time ` + humanDate + `
Time 34 years ago
Struct.String -
Struct.Int 0
Struct.Bool false
Expand All @@ -134,12 +206,18 @@ func TestMarshal(t *testing.T) {
Structs.0.Bool false
Structs.0.Time a long while ago
Structs.0.Stringer a stringer
Map.key1 v1
Map.key2 v2
Stringer a stringer
StringerPtr a stringer
Size 13 kB
Bytes AAE=

Map String List:
key1 v1 v2
key2 v3 v4

Map:
key1 v1
key2 v2
`,
}))

Expand Down Expand Up @@ -338,7 +416,7 @@ func Test_getStructFieldsIndex(t *testing.T) {
},
),
},
want: [][]int{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}},
want: [][]int{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}},
},
}
for _, tt := range tests {
Expand Down
13 changes: 13 additions & 0 deletions core/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,19 @@ func ExecStoreBeforeCmd(metaKey, cmd string) BeforeFunc {
}
}

// ExecStoreBeforeCmdWithResulter executes the given before command and register the result
// in the context Meta at metaKey. The result is transformed by the resulter function.
func ExecStoreBeforeCmdWithResulter(metaKey, cmd string, resulter func(any) any) BeforeFunc {
return func(ctx *BeforeFuncCtx) error {
args := cmdToArgs(ctx.Meta, cmd)
ctx.Logger.Debugf("ExecStoreBeforeCmd: metaKey=%s args=%s\n", metaKey, args)
result := ctx.ExecuteCmd(args)
ctx.Meta[metaKey] = resulter(result)

return nil
}
}

func BeforeFuncOsExec(cmd string, args ...string) BeforeFunc {
return func(ctx *BeforeFuncCtx) error {
ctx.Logger.Debugf("BeforeFuncOsExec: cmd=%s args=%s\n", cmd, args)
Expand Down
11 changes: 2 additions & 9 deletions core/testing_recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,15 @@ import (
func cassetteRequestFilter(i *cassette.Interaction) error {
delete(i.Request.Headers, "x-auth-token")
delete(i.Request.Headers, "X-Auth-Token")

orgIDRegex := regexp.MustCompile(`^organization_id=[0-9a-f-]{36}$`)
orgIDRegex := regexp.MustCompile(`(.+)organization_id=[0-9a-f-]{36}(.+)`)
tokenRegex := regexp.MustCompile(`^https://api\.scaleway\.com/account/v1/tokens/[0-9a-f-]{36}$`)
apiKeyRegex := regexp.MustCompile(
`^https://api\.scaleway\.com/iam/v1alpha1/api-keys/SCW[0-9A-Z]{17}$`,
)

i.URL = orgIDRegex.ReplaceAllString(
i.URL,
"organization_id=11111111-1111-1111-1111-111111111111")
"${1}organization_id=11111111-1111-1111-1111-111111111111${2}")
i.URL = tokenRegex.ReplaceAllString(
i.URL,
"api.scaleway.com/account/v1/tokens/11111111-1111-1111-1111-111111111111")
i.URL = apiKeyRegex.ReplaceAllString(
i.URL,
"api.scaleway.com/iam/v1alpha1/api-keys/SCWXXXXXXXXXXXXXXXXX")

return nil
}
Expand Down
1 change: 1 addition & 0 deletions docs/commands/iam.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ scw iam api-key get <access-key ...> [arg=value ...]
| Name | | Description |
|------|---|-------------|
| access-key | Required | Access key to search for |
| with-policies | Default: `true` | Display the set of policies associated with the API key |



Expand Down
6 changes: 6 additions & 0 deletions internal/namespaces/iam/v1alpha1/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ func GetCommands() *core.Commands {
cmds.MustFind("iam", "policy", "create").Override(iamPolicyCreateBuilder)
cmds.MustFind("iam", "policy", "get").Override(iamPolicyGetBuilder)

iamCmd := cmds.MustFind("iam", "api-key", "get")
iamCmd.ArgsType = iamApiKeyCustomBuilder.argType
iamCmd.ArgSpecs = iamApiKeyCustomBuilder.argSpecs
iamCmd.Run = iamApiKeyCustomBuilder.run
human.RegisterMarshalerFunc(apiKeyResponse{}, apiKeyMarshalerFunc)

return cmds
}

Expand Down
Loading
Loading