Skip to content

Commit

Permalink
Feature/service metadata fixes 2 (#90)
Browse files Browse the repository at this point in the history
* Check for available API resources on startup

* Improve field parsing

---------

Co-authored-by: aalexand <aalexand@adobe.com>
  • Loading branch information
aalexandru and aalexand authored Nov 21, 2023
1 parent cd373f2 commit f2f8883
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ docker/

# build artifacts
/cluster-registry*
kubeconfig
kubeconfig*
.dynamodb
.hack*

Expand Down
68 changes: 54 additions & 14 deletions cmd/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ package main
import (
"encoding/base64"
"flag"
"fmt"
registryv1alpha1 "github.com/adobe/cluster-registry/pkg/api/registry/v1alpha1"
"github.com/adobe/cluster-registry/pkg/client/controllers"
"github.com/adobe/cluster-registry/pkg/config"
monitoring "github.com/adobe/cluster-registry/pkg/monitoring/client"
"github.com/adobe/cluster-registry/pkg/sqs"
"github.com/prometheus/client_golang/prometheus/promhttp"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/tools/leaderelection/resourcelock"
"os"

Expand Down Expand Up @@ -148,20 +150,10 @@ func main() {
}

if err = (&controllers.ServiceMetadataWatcherReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("ServiceMetadataWatcher"),
Scheme: mgr.GetScheme(),
WatchedGVKs: func(cfg configv1.ClientConfig) []schema.GroupVersionKind {
var GVKs []schema.GroupVersionKind
for _, gvk := range cfg.ServiceMetadata.WatchedGVKs {
GVKs = append(GVKs, schema.GroupVersionKind{
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
})
}
return GVKs
}(clientConfig),
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("ServiceMetadataWatcher"),
Scheme: mgr.GetScheme(),
WatchedGVKs: loadWatchedGVKs(clientConfig),
ServiceIdAnnotation: clientConfig.ServiceMetadata.ServiceIdAnnotation,
}).SetupWithManager(ctx, mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ServiceMetadataWatcher")
Expand Down Expand Up @@ -204,6 +196,27 @@ func main() {
}
}

func loadWatchedGVKs(cfg configv1.ClientConfig) []schema.GroupVersionKind {
availableGVKs, err := getAvailableGVKs()
if err != nil {
return []schema.GroupVersionKind{}
}
var GVKs []schema.GroupVersionKind
for _, gvk := range cfg.ServiceMetadata.WatchedGVKs {
gvk := schema.GroupVersionKind{
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
}
if _, found := availableGVKs[gvk]; !found {
setupLog.Info("GVK not installed in the cluster", "gvk", gvk)
continue
}
GVKs = append(GVKs, gvk)
}
return GVKs
}

func apply(configFile string, clientConfigDefaults *configv1.ClientConfig) (ctrl.Options, configv1.ClientConfig, error) {
options, cfg, err := configv1.Load(scheme, configFile, clientConfigDefaults)
if err != nil {
Expand All @@ -218,3 +231,30 @@ func apply(configFile string, clientConfigDefaults *configv1.ClientConfig) (ctrl

return options, cfg, nil
}

func getAvailableGVKs() (map[schema.GroupVersionKind]bool, error) {
discoveryClient, err := discovery.NewDiscoveryClientForConfig(ctrl.GetConfigOrDie())
if err != nil {
return nil, fmt.Errorf("unable to create discovery client: %w", err)
}

_, availableResources, err := discoveryClient.ServerGroupsAndResources()
if err != nil {
return nil, fmt.Errorf("unable to get available API resources: %w", err)
}

availableGVKs := make(map[schema.GroupVersionKind]bool)
for _, list := range availableResources {
groupVersion, _ := schema.ParseGroupVersion(list.GroupVersion)
for _, resource := range list.APIResources {
gvk := schema.GroupVersionKind{
Group: groupVersion.Group,
Version: groupVersion.Version,
Kind: resource.Kind,
}
availableGVKs[gvk] = true
}
}

return availableGVKs, nil
}
76 changes: 54 additions & 22 deletions pkg/client/controllers/servicemetadatawatcher_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,14 @@ func (r *ServiceMetadataWatcherReconciler) Reconcile(ctx context.Context, req ct
for _, field := range wso.WatchedFields {
// TODO: validate field Source & Destination somewhere

value, found, err := getNestedString(obj.Object, strings.Split(field.Source, "."))
path, err := parsePath(field.Source)
if err != nil {
// TODO: update status with error
log.Error(err, "cannot parse path", "field", field.Source)
continue
}

value, found, err := getNestedString(obj.Object, path)
if err != nil {
// TODO: update status with error
log.Error(err, "cannot get field", "field", field.Source)
Expand Down Expand Up @@ -165,6 +172,15 @@ func (r *ServiceMetadataWatcherReconciler) Reconcile(ctx context.Context, req ct
return noRequeue()
}

func parsePath(path string) ([]string, error) {
re := regexp.MustCompile(`(?:[^.\[]+|\[.*?\])`)
p := re.FindAllString(path, -1)
if len(p) > 0 {
return p, nil
}
return nil, fmt.Errorf("invalid path")
}

// SetupWithManager sets up the controller with the Manager.
func (r *ServiceMetadataWatcherReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
options := controller.Options{MaxConcurrentReconciles: 10}
Expand Down Expand Up @@ -318,32 +334,40 @@ func createServiceMetadataPatch(serviceId string, namespace string, field string
}

// getNestedString returns the value of a nested field in the provided object
// path is a list of keys separated by dots, e.g. "spec.template.spec.containers[0].image"
// if the field is a slice, the last key must be in the form of "key[index]"
// path is a list of strings separated by dots, e.g. "spec.template.spec.containers[0].image"
// If the field is a slice, the last string must be in the form of "field[index]", where index is an integer
// If the field is a map, the last string must be in the form of "field[key]", where key is a string
func getNestedString(object interface{}, path []string) (string, bool, error) {
re := regexp.MustCompile(`^(.*)\[(\d+|[a-z]+)]$`)
var cpath []string
for i, key := range path {
m := re.FindStringSubmatch(key)
if len(m) > 0 {
cpath = append(cpath, m[1])
slice, found, err := unstructured.NestedSlice(object.(map[string]interface{}), cpath...)
if !found || err != nil {
return "", false, err
}
index, err := strconv.Atoi(m[2])
if err != nil && m[2] != "last" {
return "", false, fmt.Errorf("invalid array index: %s", m[2])
}
if m[2] == "last" {
index = len(slice) - 1
if strings.HasPrefix(key, "[") && strings.HasSuffix(key, "]") {
k := key[1 : len(key)-1]

sliceObj, found, err := unstructured.NestedSlice(object.(map[string]interface{}), cpath...)
if err == nil && found {
index, err := strconv.Atoi(k)
if err != nil {
return "", false, fmt.Errorf("invalid array index: %s", k)
}
if index < 0 {
index = len(sliceObj) + index
}
if index >= len(sliceObj) || index < 0 {
return "", false, fmt.Errorf("index out of range")
}
return getNestedString(sliceObj[index], path[i+1:])
}
if len(slice) <= index {
return "", false, fmt.Errorf("index out of range")

mapObj, found, err := unstructured.NestedMap(object.(map[string]interface{}), cpath...)
if err == nil && found {
if _, ok := mapObj[k]; !ok {
return "", false, fmt.Errorf("key not found: %s", k)
}
return getNestedString(mapObj[k], path[i+1:])
}
return getNestedString(slice[index], path[i+1:])
} else {
cpath = append(cpath, key)
}
cpath = append(cpath, key)
}

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

// TODO: handle additional types?
intVal, found, err := unstructured.NestedInt64(object.(map[string]interface{}), path...)
if found && err == nil {
return strconv.FormatInt(intVal, 10), found, err
}

floatVal, found, err := unstructured.NestedFloat64(object.(map[string]interface{}), path...)
if found && err == nil {
return strconv.FormatFloat(floatVal, 'g', -1, 64), found, err
}

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

0 comments on commit f2f8883

Please sign in to comment.