Skip to content

Commit f2f8883

Browse files
aalexandruaalexand
andauthored
Feature/service metadata fixes 2 (#90)
* Check for available API resources on startup * Improve field parsing --------- Co-authored-by: aalexand <[email protected]>
1 parent cd373f2 commit f2f8883

File tree

3 files changed

+109
-37
lines changed

3 files changed

+109
-37
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ docker/
1010

1111
# build artifacts
1212
/cluster-registry*
13-
kubeconfig
13+
kubeconfig*
1414
.dynamodb
1515
.hack*
1616

cmd/client/client.go

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ package main
1515
import (
1616
"encoding/base64"
1717
"flag"
18+
"fmt"
1819
registryv1alpha1 "github.com/adobe/cluster-registry/pkg/api/registry/v1alpha1"
1920
"github.com/adobe/cluster-registry/pkg/client/controllers"
2021
"github.com/adobe/cluster-registry/pkg/config"
2122
monitoring "github.com/adobe/cluster-registry/pkg/monitoring/client"
2223
"github.com/adobe/cluster-registry/pkg/sqs"
2324
"github.com/prometheus/client_golang/prometheus/promhttp"
2425
"k8s.io/apimachinery/pkg/runtime/schema"
26+
"k8s.io/client-go/discovery"
2527
"k8s.io/client-go/tools/leaderelection/resourcelock"
2628
"os"
2729

@@ -148,20 +150,10 @@ func main() {
148150
}
149151

150152
if err = (&controllers.ServiceMetadataWatcherReconciler{
151-
Client: mgr.GetClient(),
152-
Log: ctrl.Log.WithName("controllers").WithName("ServiceMetadataWatcher"),
153-
Scheme: mgr.GetScheme(),
154-
WatchedGVKs: func(cfg configv1.ClientConfig) []schema.GroupVersionKind {
155-
var GVKs []schema.GroupVersionKind
156-
for _, gvk := range cfg.ServiceMetadata.WatchedGVKs {
157-
GVKs = append(GVKs, schema.GroupVersionKind{
158-
Group: gvk.Group,
159-
Version: gvk.Version,
160-
Kind: gvk.Kind,
161-
})
162-
}
163-
return GVKs
164-
}(clientConfig),
153+
Client: mgr.GetClient(),
154+
Log: ctrl.Log.WithName("controllers").WithName("ServiceMetadataWatcher"),
155+
Scheme: mgr.GetScheme(),
156+
WatchedGVKs: loadWatchedGVKs(clientConfig),
165157
ServiceIdAnnotation: clientConfig.ServiceMetadata.ServiceIdAnnotation,
166158
}).SetupWithManager(ctx, mgr); err != nil {
167159
setupLog.Error(err, "unable to create controller", "controller", "ServiceMetadataWatcher")
@@ -204,6 +196,27 @@ func main() {
204196
}
205197
}
206198

199+
func loadWatchedGVKs(cfg configv1.ClientConfig) []schema.GroupVersionKind {
200+
availableGVKs, err := getAvailableGVKs()
201+
if err != nil {
202+
return []schema.GroupVersionKind{}
203+
}
204+
var GVKs []schema.GroupVersionKind
205+
for _, gvk := range cfg.ServiceMetadata.WatchedGVKs {
206+
gvk := schema.GroupVersionKind{
207+
Group: gvk.Group,
208+
Version: gvk.Version,
209+
Kind: gvk.Kind,
210+
}
211+
if _, found := availableGVKs[gvk]; !found {
212+
setupLog.Info("GVK not installed in the cluster", "gvk", gvk)
213+
continue
214+
}
215+
GVKs = append(GVKs, gvk)
216+
}
217+
return GVKs
218+
}
219+
207220
func apply(configFile string, clientConfigDefaults *configv1.ClientConfig) (ctrl.Options, configv1.ClientConfig, error) {
208221
options, cfg, err := configv1.Load(scheme, configFile, clientConfigDefaults)
209222
if err != nil {
@@ -218,3 +231,30 @@ func apply(configFile string, clientConfigDefaults *configv1.ClientConfig) (ctrl
218231

219232
return options, cfg, nil
220233
}
234+
235+
func getAvailableGVKs() (map[schema.GroupVersionKind]bool, error) {
236+
discoveryClient, err := discovery.NewDiscoveryClientForConfig(ctrl.GetConfigOrDie())
237+
if err != nil {
238+
return nil, fmt.Errorf("unable to create discovery client: %w", err)
239+
}
240+
241+
_, availableResources, err := discoveryClient.ServerGroupsAndResources()
242+
if err != nil {
243+
return nil, fmt.Errorf("unable to get available API resources: %w", err)
244+
}
245+
246+
availableGVKs := make(map[schema.GroupVersionKind]bool)
247+
for _, list := range availableResources {
248+
groupVersion, _ := schema.ParseGroupVersion(list.GroupVersion)
249+
for _, resource := range list.APIResources {
250+
gvk := schema.GroupVersionKind{
251+
Group: groupVersion.Group,
252+
Version: groupVersion.Version,
253+
Kind: resource.Kind,
254+
}
255+
availableGVKs[gvk] = true
256+
}
257+
}
258+
259+
return availableGVKs, nil
260+
}

pkg/client/controllers/servicemetadatawatcher_controller.go

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,14 @@ func (r *ServiceMetadataWatcherReconciler) Reconcile(ctx context.Context, req ct
131131
for _, field := range wso.WatchedFields {
132132
// TODO: validate field Source & Destination somewhere
133133

134-
value, found, err := getNestedString(obj.Object, strings.Split(field.Source, "."))
134+
path, err := parsePath(field.Source)
135+
if err != nil {
136+
// TODO: update status with error
137+
log.Error(err, "cannot parse path", "field", field.Source)
138+
continue
139+
}
140+
141+
value, found, err := getNestedString(obj.Object, path)
135142
if err != nil {
136143
// TODO: update status with error
137144
log.Error(err, "cannot get field", "field", field.Source)
@@ -165,6 +172,15 @@ func (r *ServiceMetadataWatcherReconciler) Reconcile(ctx context.Context, req ct
165172
return noRequeue()
166173
}
167174

175+
func parsePath(path string) ([]string, error) {
176+
re := regexp.MustCompile(`(?:[^.\[]+|\[.*?\])`)
177+
p := re.FindAllString(path, -1)
178+
if len(p) > 0 {
179+
return p, nil
180+
}
181+
return nil, fmt.Errorf("invalid path")
182+
}
183+
168184
// SetupWithManager sets up the controller with the Manager.
169185
func (r *ServiceMetadataWatcherReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
170186
options := controller.Options{MaxConcurrentReconciles: 10}
@@ -318,32 +334,40 @@ func createServiceMetadataPatch(serviceId string, namespace string, field string
318334
}
319335

320336
// getNestedString returns the value of a nested field in the provided object
321-
// path is a list of keys separated by dots, e.g. "spec.template.spec.containers[0].image"
322-
// if the field is a slice, the last key must be in the form of "key[index]"
337+
// path is a list of strings separated by dots, e.g. "spec.template.spec.containers[0].image"
338+
// If the field is a slice, the last string must be in the form of "field[index]", where index is an integer
339+
// If the field is a map, the last string must be in the form of "field[key]", where key is a string
323340
func getNestedString(object interface{}, path []string) (string, bool, error) {
324-
re := regexp.MustCompile(`^(.*)\[(\d+|[a-z]+)]$`)
325341
var cpath []string
326342
for i, key := range path {
327-
m := re.FindStringSubmatch(key)
328-
if len(m) > 0 {
329-
cpath = append(cpath, m[1])
330-
slice, found, err := unstructured.NestedSlice(object.(map[string]interface{}), cpath...)
331-
if !found || err != nil {
332-
return "", false, err
333-
}
334-
index, err := strconv.Atoi(m[2])
335-
if err != nil && m[2] != "last" {
336-
return "", false, fmt.Errorf("invalid array index: %s", m[2])
337-
}
338-
if m[2] == "last" {
339-
index = len(slice) - 1
343+
if strings.HasPrefix(key, "[") && strings.HasSuffix(key, "]") {
344+
k := key[1 : len(key)-1]
345+
346+
sliceObj, found, err := unstructured.NestedSlice(object.(map[string]interface{}), cpath...)
347+
if err == nil && found {
348+
index, err := strconv.Atoi(k)
349+
if err != nil {
350+
return "", false, fmt.Errorf("invalid array index: %s", k)
351+
}
352+
if index < 0 {
353+
index = len(sliceObj) + index
354+
}
355+
if index >= len(sliceObj) || index < 0 {
356+
return "", false, fmt.Errorf("index out of range")
357+
}
358+
return getNestedString(sliceObj[index], path[i+1:])
340359
}
341-
if len(slice) <= index {
342-
return "", false, fmt.Errorf("index out of range")
360+
361+
mapObj, found, err := unstructured.NestedMap(object.(map[string]interface{}), cpath...)
362+
if err == nil && found {
363+
if _, ok := mapObj[k]; !ok {
364+
return "", false, fmt.Errorf("key not found: %s", k)
365+
}
366+
return getNestedString(mapObj[k], path[i+1:])
343367
}
344-
return getNestedString(slice[index], path[i+1:])
368+
} else {
369+
cpath = append(cpath, key)
345370
}
346-
cpath = append(cpath, key)
347371
}
348372

349373
if reflect.TypeOf(object).String() == "string" {
@@ -364,7 +388,15 @@ func getNestedString(object interface{}, path []string) (string, bool, error) {
364388
return strconv.FormatBool(boolVal), found, err
365389
}
366390

367-
// TODO: handle additional types?
391+
intVal, found, err := unstructured.NestedInt64(object.(map[string]interface{}), path...)
392+
if found && err == nil {
393+
return strconv.FormatInt(intVal, 10), found, err
394+
}
395+
396+
floatVal, found, err := unstructured.NestedFloat64(object.(map[string]interface{}), path...)
397+
if found && err == nil {
398+
return strconv.FormatFloat(floatVal, 'g', -1, 64), found, err
399+
}
368400

369401
return "", found, fmt.Errorf("invalid field type")
370402
}

0 commit comments

Comments
 (0)