Skip to content

Commit 564c8a7

Browse files
committed
feat: Add support for list of attributes with the same key
Signed-off-by: Douglass Kirkley <doug.kirkley@gmail.com>
1 parent c22c55a commit 564c8a7

21 files changed

+285
-81
lines changed

api/common/attributes.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package common
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
// UserAttributes is a map of user attributes. It supports both string and []string values for attributes.
9+
// +kubebuilder:validation:Type=object
10+
// +kubebuilder:pruning:PreserveUnknownFields
11+
type UserAttributes map[string][]string
12+
13+
// UnmarshalJSON implements json.Unmarshaler.
14+
func (a *UserAttributes) UnmarshalJSON(data []byte) error {
15+
var raw map[string]interface{}
16+
if err := json.Unmarshal(data, &raw); err != nil {
17+
return err
18+
}
19+
20+
if *a == nil {
21+
*a = make(UserAttributes)
22+
}
23+
24+
result := *a
25+
26+
for k, v := range raw {
27+
switch value := v.(type) {
28+
case string:
29+
result[k] = []string{value}
30+
case []interface{}:
31+
var strSlice []string
32+
33+
for _, item := range value {
34+
if str, ok := item.(string); ok {
35+
strSlice = append(strSlice, str)
36+
} else {
37+
return fmt.Errorf("attribute '%s' contains a non-string value in the list", k)
38+
}
39+
}
40+
41+
result[k] = strSlice
42+
default:
43+
return fmt.Errorf("unsupported type for attribute '%s': %T", k, v)
44+
}
45+
}
46+
47+
return nil
48+
}
49+
50+
// MarshalJSON implements json.Marshaler.
51+
func (a *UserAttributes) MarshalJSON() ([]byte, error) {
52+
result := make(map[string]interface{})
53+
54+
for k, v := range *a {
55+
if len(v) == 1 {
56+
result[k] = v[0]
57+
} else {
58+
result[k] = v
59+
}
60+
}
61+
62+
return json.Marshal(result)
63+
}
64+
65+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
66+
func (a *UserAttributes) DeepCopyInto(out *UserAttributes) {
67+
if *a == nil {
68+
*out = nil
69+
return
70+
}
71+
*out = make(UserAttributes, len(*a))
72+
for key, val := range *a {
73+
if val == nil {
74+
(*out)[key] = nil
75+
} else {
76+
// Create a new slice and copy the elements.
77+
inVal := val
78+
outVal := make([]string, len(inVal))
79+
copy(outVal, inVal)
80+
(*out)[key] = outVal
81+
}
82+
}
83+
}
84+
85+
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserAttributes.
86+
func (a *UserAttributes) DeepCopy() *UserAttributes {
87+
if a == nil {
88+
return nil
89+
}
90+
out := new(UserAttributes)
91+
a.DeepCopyInto(out)
92+
return out
93+
}

api/v1/keycloakclient_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ type ServiceAccount struct {
209209
// Attributes is a map of service account attributes.
210210
// +nullable
211211
// +optional
212-
Attributes map[string]string `json:"attributes,omitempty"`
212+
Attributes common.UserAttributes `json:"attributes,omitempty"`
213213

214214
// Groups is a list of groups assigned to service account
215215
// +nullable

api/v1/keycloakrealmuser_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ type KeycloakRealmUserSpec struct {
5858
// Attributes is a map of user attributes.
5959
// +nullable
6060
// +optional
61-
Attributes map[string]string `json:"attributes,omitempty"`
61+
Attributes common.UserAttributes `json:"attributes,omitempty"`
6262

6363
// ReconciliationStrategy is a strategy for reconciliation. Possible values: full, create-only.
6464
// Default value: full. If set to create-only, user will be created only if it does not exist. If user exists, it will not be updated.

api/v1/zz_generated.deepcopy.go

Lines changed: 2 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/v1.edp.epam.com_keycloakclients.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,11 +587,10 @@ spec:
587587
nullable: true
588588
properties:
589589
attributes:
590-
additionalProperties:
591-
type: string
592590
description: Attributes is a map of service account attributes.
593591
nullable: true
594592
type: object
593+
x-kubernetes-preserve-unknown-fields: true
595594
clientRoles:
596595
description: ClientRoles is a list of client roles assigned to
597596
service account.

config/crd/bases/v1.edp.epam.com_keycloakrealmusers.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,10 @@ spec:
4545
description: KeycloakRealmUserSpec defines the desired state of KeycloakRealmUser.
4646
properties:
4747
attributes:
48-
additionalProperties:
49-
type: string
5048
description: Attributes is a map of user attributes.
5149
nullable: true
5250
type: object
51+
x-kubernetes-preserve-unknown-fields: true
5352
clientRoles:
5453
description: ClientRoles is a list of client roles assigned to user.
5554
items:

deploy-templates/crds/v1.edp.epam.com_keycloakclients.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,11 +587,10 @@ spec:
587587
nullable: true
588588
properties:
589589
attributes:
590-
additionalProperties:
591-
type: string
592590
description: Attributes is a map of service account attributes.
593591
nullable: true
594592
type: object
593+
x-kubernetes-preserve-unknown-fields: true
595594
clientRoles:
596595
description: ClientRoles is a list of client roles assigned to
597596
service account.

deploy-templates/crds/v1.edp.epam.com_keycloakrealmusers.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,10 @@ spec:
4545
description: KeycloakRealmUserSpec defines the desired state of KeycloakRealmUser.
4646
properties:
4747
attributes:
48-
additionalProperties:
49-
type: string
5048
description: Attributes is a map of user attributes.
5149
nullable: true
5250
type: object
51+
x-kubernetes-preserve-unknown-fields: true
5352
clientRoles:
5453
description: ClientRoles is a list of client roles assigned to user.
5554
items:

docs/api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3342,7 +3342,7 @@ ServiceAccount is a service account configuration.
33423342
</thead>
33433343
<tbody><tr>
33443344
<td><b>attributes</b></td>
3345-
<td>map[string]string</td>
3345+
<td>object</td>
33463346
<td>
33473347
Attributes is a map of service account attributes.<br/>
33483348
</td>
@@ -6367,7 +6367,7 @@ KeycloakRealmUserSpec defines the desired state of KeycloakRealmUser.
63676367
<td>true</td>
63686368
</tr><tr>
63696369
<td><b>attributes</b></td>
6370-
<td>map[string]string</td>
6370+
<td>object</td>
63716371
<td>
63726372
Attributes is a map of user attributes.<br/>
63736373
</td>

internal/controller/keycloakclient/chain/service_account_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ func TestServiceAccount_Serve(t *testing.T) {
2020
},
2121
ServiceAccount: &keycloakApi.ServiceAccount{
2222
Enabled: true,
23-
Attributes: map[string]string{
24-
"foo": "bar",
23+
Attributes: common.UserAttributes{
24+
"foo": {"bar"},
2525
},
2626
ClientRoles: []keycloakApi.ClientRole{
2727
{

0 commit comments

Comments
 (0)