Skip to content

Commit

Permalink
Merge branch 'main' into handle-export-chg
Browse files Browse the repository at this point in the history
  • Loading branch information
cam-garrison authored Aug 26, 2024
2 parents e74c2b0 + 1df2c4a commit ce25276
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 33 deletions.
1 change: 0 additions & 1 deletion controllers/routingctrl/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun
toRemove = append(toRemove, component)

// required labels for the exported service:
// routing.opendatahub.io/exported: "true"
// platform.opendatahub.io/owner-name: test-component
// platform.opendatahub.io/owner-kind: Component
addRoutingRequirementsToSvc(ctx, svc, component)
Expand Down
9 changes: 2 additions & 7 deletions controllers/routingctrl/exported_svc_locator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,16 @@ import (
"errors"
"fmt"

"github.com/opendatahub-io/odh-platform/pkg/metadata/labels"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func getExportedServices(ctx context.Context, cli client.Client, target *unstructured.Unstructured) ([]corev1.Service, error) {
func getExportedServices(ctx context.Context, cli client.Client, labels map[string]string, target *unstructured.Unstructured) ([]corev1.Service, error) {
listOpts := []client.ListOption{
client.InNamespace(target.GetNamespace()),
labels.MatchingLabels(
labels.RoutingExported("true"),
labels.OwnerName(target.GetName()),
labels.OwnerKind(target.GetObjectKind().GroupVersionKind().Kind),
),
client.MatchingLabels(labels),
}

var exportedSvcList *corev1.ServiceList
Expand Down
10 changes: 4 additions & 6 deletions controllers/routingctrl/fixtures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,18 @@ func getClusterDomain(ctx context.Context, cli client.Client) string {
return domain
}

// addRoutingRequirementsToSvc adds routing-related metadata to the Service being exported.
// It adds the "routing.opendatahub.io/exported" label to indicate that the service is exported,
// and it also sets labels for the owner component's name and kind, using
// "platform.opendatahub.io/owner-name" and "platform.opendatahub.io/owner-kind" respectively.
// addRoutingRequirementsToSvc adds routing-related metadata to the Service being exported to match the
// serviceSelector defined in the suite_test.
func addRoutingRequirementsToSvc(ctx context.Context, exportedSvc *corev1.Service, owningComponent *unstructured.Unstructured) {
exportedLabel := labels.RoutingExported("true")
ownerName := labels.OwnerName(owningComponent.GetName())
ownerKind := labels.OwnerKind(owningComponent.GetObjectKind().GroupVersionKind().Kind)

_, errExportSvc := controllerutil.CreateOrUpdate(ctx, envTest.Client, exportedSvc, func() error {
metadata.ApplyMetaOptions(exportedSvc, exportedLabel, ownerName, ownerKind)
metadata.ApplyMetaOptions(exportedSvc, ownerName, ownerKind)

return nil
})

Expect(errExportSvc).ToNot(HaveOccurred())
}

Expand Down
8 changes: 7 additions & 1 deletion controllers/routingctrl/reconcile_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/go-logr/logr"
"github.com/opendatahub-io/odh-platform/pkg/cluster"
"github.com/opendatahub-io/odh-platform/pkg/config"
"github.com/opendatahub-io/odh-platform/pkg/metadata"
"github.com/opendatahub-io/odh-platform/pkg/metadata/annotations"
"github.com/opendatahub-io/odh-platform/pkg/metadata/labels"
Expand All @@ -29,7 +30,12 @@ func (r *Controller) createRoutingResources(ctx context.Context, target *unstruc

r.log.Info("Reconciling resources for target", "target", target)

exportedServices, errSvcGet := getExportedServices(ctx, r.Client, target)
renderedSelectors, errLables := config.ResolveSelectors(r.component.ServiceSelector, target)
if errLables != nil {
return fmt.Errorf("could not render labels for ServiceSelector %v. Error %w", r.component.ServiceSelector, errLables)
}

exportedServices, errSvcGet := getExportedServices(ctx, r.Client, renderedSelectors, target)
if errSvcGet != nil {
if errors.Is(errSvcGet, &ExportedServiceNotFoundError{}) {
r.log.Info("no exported services found for target", "target", target)
Expand Down
5 changes: 5 additions & 0 deletions controllers/routingctrl/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/opendatahub-io/odh-platform/controllers/routingctrl"
"github.com/opendatahub-io/odh-platform/pkg/metadata/labels"
"github.com/opendatahub-io/odh-platform/pkg/platform"
"github.com/opendatahub-io/odh-platform/pkg/spi"
"github.com/opendatahub-io/odh-platform/test"
Expand Down Expand Up @@ -37,6 +38,9 @@ var _ = SynchronizedBeforeSuite(func() {
return
}

ownerName := labels.OwnerName("{{.metadata.name}}")
ownerKind := labels.OwnerKind("{{.kind}}")

routingCtrl := routingctrl.New(
nil,
ctrl.Log.WithName("controllers").WithName("platform"),
Expand All @@ -49,6 +53,7 @@ var _ = SynchronizedBeforeSuite(func() {
Kind: "Component",
},
},
ServiceSelector: labels.MatchingLabels(ownerName, ownerKind),
},
},
routingConfiguration,
Expand Down
70 changes: 70 additions & 0 deletions pkg/config/selectors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package config

import (
"bytes"
"fmt"
"strings"
"text/template"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// ResolveSelectors uses golang template engine to resolve the expressions in the `selectorExpressions` map using
// `source` as a data input. Both the keys and values are resolved against the source data.
//
// Note: expressions are resolved against the source using lowercase keys
//
// Example source:
//
// kind: Service
// metadata:
// name: MyService
//
// Example selectorExpressions:
//
// map[string]{string} {
// "routing.opendatahub.io/{{.kind}}": "{{.metadata.name}}", // > "routing.opendatahub.io/Service": "MyService"
// }
func ResolveSelectors(selectorExpressions map[string]string, source *unstructured.Unstructured) (map[string]string, error) {
resolved := make(map[string]string, len(selectorExpressions))
mainTemplate := template.New("unused_name").Option("missingkey=error")

for key, val := range selectorExpressions {
var err error

resolvedKey := key
if strings.Contains(key, "{{") {
resolvedKey, err = resolve(mainTemplate, key, source)
if err != nil {
return nil, fmt.Errorf("could not resolve key %s: %w", key, err)
}
}

resolvedVal := val
if strings.Contains(val, "{{") {
resolvedVal, err = resolve(mainTemplate, val, source)
if err != nil {
return nil, fmt.Errorf("could not resolve value %s: %w", val, err)
}
}

resolved[resolvedKey] = resolvedVal
}

return resolved, nil
}

func resolve(templ *template.Template, textTemplate string, source *unstructured.Unstructured) (string, error) {
tmpl, err := templ.Parse(textTemplate)
if err != nil {
return "", fmt.Errorf("could not parse template: %w", err)
}

var buff bytes.Buffer

if err := tmpl.Execute(&buff, source.Object); err != nil {
return "", fmt.Errorf("could not execute template: %w", err)
}

return buff.String(), nil
}
51 changes: 51 additions & 0 deletions pkg/config/selectors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package config_test

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/opendatahub-io/odh-platform/pkg/config"
"github.com/opendatahub-io/odh-platform/test"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

var _ = Describe("Templated selectors", test.Unit(), func() {

Context("simple expressions", func() {

It("should resolve simple expressions for both key and value", func() {
labels := map[string]string{
"A.{{.kind}}": "{{.metadata.name}}",
"B": "{{.kind}}",
}

target := unstructured.Unstructured{
Object: map[string]any{},
}
target.SetName("X")
target.SetKind("Y")

renderedLabels, err := config.ResolveSelectors(labels, &target)
Expect(err).ToNot(HaveOccurred())

Expect(renderedLabels["A.Y"]).To(Equal("X"))
Expect(renderedLabels["B"]).To(Equal("Y"))
})

It("should fail on missing expression", func() {
labels := map[string]string{
"A": "{{.metadata.name}}",
}

target := unstructured.Unstructured{
Object: map[string]any{},
}
target.SetKind("Y")

_, err := config.ResolveSelectors(labels, &target)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("could not execute template"))
Expect(err.Error()).To(ContainSubstring("could not resolve value"))
})
})

})
18 changes: 0 additions & 18 deletions pkg/metadata/labels/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,6 @@ func (o OwnerUID) Value() string {
return string(o)
}

// RoutingExported is a Label to mark resources that are exported by the routing capability.
// It is intended to be set by enrolled component to mark resources that should be used to
// configure routing capability by the Platform. This can be a Kubernetes Service or Istio
// VirtualService from which settings like hosts and ports are extracted.
type RoutingExported string

func (r RoutingExported) ApplyToMeta(obj metav1.Object) {
addLabel(r, obj)
}

func (r RoutingExported) Key() string {
return "routing.opendatahub.io/exported"
}

func (r RoutingExported) Value() string {
return string(r)
}

// ExportType is a Label to mark created resources with which export type they were created for.
// this can either be public or external.
type ExportType string
Expand Down
6 changes: 6 additions & 0 deletions pkg/platform/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ type ObjectReference struct {
type RoutingTarget struct {
// ObjectReference provides reference details to the associated object.
ObjectReference `json:"ref,omitempty"`
// ServiceSelector is a LabelSelector definition to locate the Service(s) to expose to Routing for the given ObjectReference.
// All provided label selectors must be present on the Service to find a match.
//
// go expressions are handled in the selector key and value to set dynamic values from the current ObjectReference;
// e.g. "routing.opendatahub.io/{{.kind}}": "{{.metadata.name}}", // > "routing.opendatahub.io/Service": "MyService"
ServiceSelector map[string]string `json:"serviceSelector,omitempty"`
}

// ProtectedResource holds references and configuration details necessary for
Expand Down

0 comments on commit ce25276

Please sign in to comment.