Skip to content

Commit 45ed78c

Browse files
committed
labels wip
On-behalf-of: @SAP [email protected] Signed-off-by: Artem Shcherbatiuk <[email protected]>
1 parent 64e5552 commit 45ed78c

File tree

3 files changed

+170
-9
lines changed

3 files changed

+170
-9
lines changed

gateway/resolver/resolver.go

Lines changed: 114 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,100 @@ import (
2525
"github.com/openmfp/golang-commons/logger"
2626
)
2727

28+
// convertLabels converts between map[string]string and []Label format
29+
// toArray=true: converts maps to arrays (for GraphQL output)
30+
// toArray=false: converts arrays to maps (for Kubernetes input)
31+
func convertLabels(obj any, toArray bool) any {
32+
switch v := obj.(type) {
33+
case map[string]interface{}:
34+
return convertObject(v, toArray)
35+
case []interface{}:
36+
result := make([]any, len(v))
37+
for i, item := range v {
38+
result[i] = convertLabels(item, toArray)
39+
}
40+
return result
41+
default:
42+
return obj
43+
}
44+
}
45+
46+
// convertObject handles object conversion with direction control
47+
func convertObject(objMap map[string]interface{}, toArray bool) map[string]interface{} {
48+
result := make(map[string]interface{})
49+
for key, value := range objMap {
50+
if key == "metadata" {
51+
result[key] = convertMetadata(value, toArray)
52+
} else {
53+
result[key] = convertLabels(value, toArray)
54+
}
55+
}
56+
return result
57+
}
58+
59+
// convertMetadata handles metadata field conversion
60+
func convertMetadata(metadataValue any, toArray bool) any {
61+
metadata, ok := metadataValue.(map[string]interface{})
62+
if !ok {
63+
return convertLabels(metadataValue, toArray)
64+
}
65+
66+
result := make(map[string]interface{})
67+
for key, value := range metadata {
68+
if isLabelField(key) && value != nil {
69+
result[key] = convertLabelField(value, toArray)
70+
} else {
71+
result[key] = convertLabels(value, toArray)
72+
}
73+
}
74+
return result
75+
}
76+
77+
// convertLabelField converts between label map and array formats
78+
func convertLabelField(value any, toArray bool) any {
79+
if toArray {
80+
// Convert map to array for GraphQL output
81+
labelMap, ok := value.(map[string]interface{})
82+
if !ok {
83+
return convertLabels(value, toArray)
84+
}
85+
86+
var labelArray []map[string]interface{}
87+
for k, v := range labelMap {
88+
if strValue, ok := v.(string); ok {
89+
labelArray = append(labelArray, map[string]interface{}{
90+
"key": k,
91+
"value": strValue,
92+
})
93+
}
94+
}
95+
return labelArray
96+
} else {
97+
// Convert array to map for Kubernetes input
98+
labelArray, ok := value.([]interface{})
99+
if !ok {
100+
return convertLabels(value, toArray)
101+
}
102+
103+
labelMap := make(map[string]string)
104+
for _, item := range labelArray {
105+
if labelObj, ok := item.(map[string]interface{}); ok {
106+
if key, keyOk := labelObj["key"].(string); keyOk {
107+
if val, valOk := labelObj["value"].(string); valOk {
108+
labelMap[key] = val
109+
}
110+
}
111+
}
112+
}
113+
return labelMap
114+
}
115+
}
116+
117+
// isLabelField checks if a field is labels or annotations
118+
func isLabelField(fieldName string) bool {
119+
return fieldName == "labels" || fieldName == "annotations"
120+
}
121+
28122
type Provider interface {
29123
CrudProvider
30124
CustomQueriesProvider
@@ -129,7 +223,9 @@ func (r *Service) ListItems(gvk schema.GroupVersionKind, scope v1.ResourceScope)
129223

130224
items := make([]map[string]any, len(list.Items))
131225
for i, item := range list.Items {
132-
items[i] = item.Object
226+
// Convert maps back to label arrays for GraphQL response
227+
convertedItem := convertLabels(item.Object, true).(map[string]interface{})
228+
items[i] = convertedItem
133229
}
134230

135231
return items, nil
@@ -185,7 +281,9 @@ func (r *Service) GetItem(gvk schema.GroupVersionKind, scope v1.ResourceScope) g
185281
return nil, err
186282
}
187283

188-
return obj.Object, nil
284+
// Convert maps back to label arrays for GraphQL response
285+
convertedResponse := convertLabels(obj.Object, true)
286+
return convertedResponse, nil
189287
}
190288
}
191289

@@ -220,8 +318,11 @@ func (r *Service) CreateItem(gvk schema.GroupVersionKind, scope v1.ResourceScope
220318

221319
objectInput := p.Args["object"].(map[string]interface{})
222320

321+
// Convert label arrays back to maps for Kubernetes compatibility
322+
convertedInput := convertLabels(objectInput, false).(map[string]interface{})
323+
223324
obj := &unstructured.Unstructured{
224-
Object: objectInput,
325+
Object: convertedInput,
225326
}
226327
obj.SetGroupVersionKind(gvk)
227328

@@ -251,7 +352,9 @@ func (r *Service) CreateItem(gvk schema.GroupVersionKind, scope v1.ResourceScope
251352
return nil, err
252353
}
253354

254-
return obj.Object, nil
355+
// Convert maps back to label arrays for GraphQL response
356+
convertedResponse := convertLabels(obj.Object, true)
357+
return convertedResponse, nil
255358
}
256359
}
257360

@@ -270,8 +373,10 @@ func (r *Service) UpdateItem(gvk schema.GroupVersionKind, scope v1.ResourceScope
270373
}
271374

272375
objectInput := p.Args["object"].(map[string]interface{})
273-
// Marshal the input object to JSON to create the patch data
274-
patchData, err := json.Marshal(objectInput)
376+
// Convert label arrays back to maps for Kubernetes compatibility
377+
convertedInput := convertLabels(objectInput, false).(map[string]interface{})
378+
// Marshal the converted input object to JSON to create the patch data
379+
patchData, err := json.Marshal(convertedInput)
275380
if err != nil {
276381
return nil, fmt.Errorf("failed to marshal object input: %v", err)
277382
}
@@ -312,7 +417,9 @@ func (r *Service) UpdateItem(gvk schema.GroupVersionKind, scope v1.ResourceScope
312417
return nil, err
313418
}
314419

315-
return existingObj.Object, nil
420+
// Convert maps back to label arrays for GraphQL response
421+
convertedResponse := convertLabels(existingObj.Object, true)
422+
return convertedResponse, nil
316423
}
317424
}
318425

gateway/schema/scalars.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,41 @@ var jsonStringScalar = graphql.NewScalar(graphql.ScalarConfig{
7272
return nil
7373
},
7474
})
75+
76+
// Label represents a single key-value label pair
77+
type Label struct {
78+
Key string `json:"key"`
79+
Value string `json:"value"`
80+
}
81+
82+
// LabelType defines the GraphQL object type for a single label
83+
var LabelType = graphql.NewObject(graphql.ObjectConfig{
84+
Name: "Label",
85+
Description: "A key-value label pair that supports keys with dots and special characters",
86+
Fields: graphql.Fields{
87+
"key": &graphql.Field{
88+
Type: graphql.NewNonNull(graphql.String),
89+
Description: "The label key (can contain dots and special characters)",
90+
},
91+
"value": &graphql.Field{
92+
Type: graphql.NewNonNull(graphql.String),
93+
Description: "The label value",
94+
},
95+
},
96+
})
97+
98+
// LabelInputType defines the GraphQL input type for a single label
99+
var LabelInputType = graphql.NewInputObject(graphql.InputObjectConfig{
100+
Name: "LabelInput",
101+
Description: "Input type for a key-value label pair",
102+
Fields: graphql.InputObjectConfigFieldMap{
103+
"key": &graphql.InputObjectFieldConfig{
104+
Type: graphql.NewNonNull(graphql.String),
105+
Description: "The label key (can contain dots and special characters)",
106+
},
107+
"value": &graphql.InputObjectFieldConfig{
108+
Type: graphql.NewNonNull(graphql.String),
109+
Description: "The label value",
110+
},
111+
},
112+
})

gateway/schema/schema.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,9 +446,25 @@ func (g *Gateway) handleObjectFieldSpecType(fieldSpec spec.Schema, typePrefix st
446446

447447
return newType, newInputType, nil
448448
} else if fieldSpec.AdditionalProperties != nil && fieldSpec.AdditionalProperties.Schema != nil {
449-
// Hagndle map types
449+
// Handle map types
450450
if len(fieldSpec.AdditionalProperties.Schema.Type) == 1 && fieldSpec.AdditionalProperties.Schema.Type[0] == "string" {
451-
// This is a map[string]string
451+
// Check if this is a labels or annotations field
452+
currentFieldName := ""
453+
if len(fieldPath) > 0 {
454+
currentFieldName = fieldPath[len(fieldPath)-1]
455+
}
456+
457+
// Check if we're in a metadata context or processing a known ObjectMeta type
458+
isInMetadata := len(fieldPath) >= 2 && fieldPath[len(fieldPath)-2] == "metadata"
459+
isObjectMetaType := strings.Contains(typePrefix, "ObjectMeta") || strings.Contains(typePrefix, "meta_v1")
460+
461+
if (isInMetadata || isObjectMetaType) && (currentFieldName == "labels" || currentFieldName == "annotations") {
462+
// This is a labels or annotations field
463+
// Use List(LabelType) for output (allows querying key/value fields)
464+
// Use List(LabelInputType) for input (supports array syntax)
465+
return graphql.NewList(LabelType), graphql.NewList(LabelInputType), nil
466+
}
467+
// This is a regular map[string]string
452468
return stringMapScalar, stringMapScalar, nil
453469
}
454470
}

0 commit comments

Comments
 (0)