diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4ce8c08..9cce420 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,4 +9,4 @@ jobs: - uses: actions/setup-go@v5 with: go-version-file: go.mod - - run: make \ No newline at end of file + - run: make test \ No newline at end of file diff --git a/controllers/authzctrl/controller.go b/controllers/authzctrl/controller.go index df0a9ef..61df496 100644 --- a/controllers/authzctrl/controller.go +++ b/controllers/authzctrl/controller.go @@ -41,7 +41,7 @@ func New(cli client.Client, log logr.Logger, hostExtractor: spi.UnifiedHostExtractor( spi.NewPathExpressionExtractor(protectedResource.HostPaths), spi.NewAnnotationHostExtractor(";", metadata.Keys(annotations.RoutingAddressesExternal(""), annotations.RoutingAddressesPublic(""))...)), - templateLoader: authorization.NewConfigMapTemplateLoader(cli, authorization.NewStaticTemplateLoader(config.Audiences)), + templateLoader: authorization.NewConfigMapTemplateLoader(cli, authorization.NewStaticTemplateLoader()), } } @@ -120,8 +120,11 @@ func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func (r *Controller) Activate() { +var _ platformctrl.Activable[authorization.ProviderConfig] = &Controller{} + +func (r *Controller) Activate(config authorization.ProviderConfig) { r.active = true + r.config = config } func (r *Controller) Deactivate() { diff --git a/controllers/authzctrl/reconcile_authconfig.go b/controllers/authzctrl/reconcile_authconfig.go index 5fb6260..bac4e4b 100644 --- a/controllers/authzctrl/reconcile_authconfig.go +++ b/controllers/authzctrl/reconcile_authconfig.go @@ -118,7 +118,12 @@ func (r *Controller) createAuthConfigTemplate(ctx context.Context, target *unstr return authorinov1beta2.AuthConfig{}, fmt.Errorf("could not detect authtype: %w", err) } - templ, err := r.templateLoader.Load(ctx, authType, types.NamespacedName{Namespace: target.GetNamespace(), Name: target.GetName()}) + templateData := map[string]any{ + "Namespace": target.GetNamespace(), + "Audiences": r.config.Audiences, + } + + templ, err := r.templateLoader.Load(ctx, authType, types.NamespacedName{Namespace: target.GetNamespace(), Name: target.GetName()}, templateData) if err != nil { return authorinov1beta2.AuthConfig{}, fmt.Errorf("could not load template %s: %w", authType, err) } diff --git a/controllers/routingctrl/controller.go b/controllers/routingctrl/controller.go index 9d96549..b7e7b6d 100644 --- a/controllers/routingctrl/controller.go +++ b/controllers/routingctrl/controller.go @@ -130,8 +130,11 @@ func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func (r *Controller) Activate() { +var _ platformctrl.Activable[routing.IngressConfig] = &Controller{} + +func (r *Controller) Activate(config routing.IngressConfig) { r.active = true + r.config = config } func (r *Controller) Deactivate() { diff --git a/controllers/routingctrl/controller_test.go b/controllers/routingctrl/controller_test.go index ac3dfdb..f5da5e0 100644 --- a/controllers/routingctrl/controller_test.go +++ b/controllers/routingctrl/controller_test.go @@ -6,7 +6,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/opendatahub-io/odh-platform/pkg/metadata" "github.com/opendatahub-io/odh-platform/pkg/metadata/annotations" "github.com/opendatahub-io/odh-platform/test" . "github.com/opendatahub-io/odh-platform/test/matchers" @@ -88,8 +87,8 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun It("should have external routing resources created", func(ctx context.Context) { // given // required annotation for watched custom resource: - // routing.opendatahub.io/export-mode: "external" - component, createErr := createComponentRequiringPlatformRouting(ctx, "exported-test-component", "external", appNs.Name) + // routing.opendatahub.io/export-mode-external: "true" + component, createErr := createComponentRequiringPlatformRouting(ctx, "exported-test-component", appNs.Name, annotations.ExternalMode()) Expect(createErr).ToNot(HaveOccurred()) toRemove = append(toRemove, component) @@ -105,8 +104,8 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun It("should have new hosts propagated back to watched resource", func(ctx context.Context) { // given // required annotation for watched custom resource: - // routing.opendatahub.io/export-mode: "external" - component, createErr := createComponentRequiringPlatformRouting(ctx, "exported-test-component", "external", appNs.Name) + // routing.opendatahub.io/export-mode-external: "true" + component, createErr := createComponentRequiringPlatformRouting(ctx, "exported-test-component", appNs.Name, annotations.ExternalMode()) Expect(createErr).ToNot(HaveOccurred()) toRemove = append(toRemove, component) @@ -127,7 +126,8 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun annotations.RoutingAddressesPublic("").Key(), ), "public services are not expected to be defined in this mode") - externalAddressesAnnotation := annotations.RoutingAddressesExternal(fmt.Sprintf("%s-%s.%s", svc.Name, svc.Namespace, domain)) + externalAddressesAnnotation := annotations.RoutingAddressesExternal( + fmt.Sprintf("%[1]s-http-%[2]s.%[3]s"+";"+"%[1]s-grpc-%[2]s.%[3]s", svc.Name, svc.Namespace, domain)) g.Expect(updatedComponent.GetAnnotations()).To(HaveKeyWithValue( externalAddressesAnnotation.Key(), externalAddressesAnnotation.Value(), @@ -148,8 +148,8 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun It("should have routing resources for out-of-mesh access created", func(ctx context.Context) { // given // required annotation for watched custom resource: - // routing.opendatahub.io/export-mode: "public" - component, createErr := createComponentRequiringPlatformRouting(ctx, "public-test-component", "public", appNs.Name) + // routing.opendatahub.io/export-mode-public: "true" + component, createErr := createComponentRequiringPlatformRouting(ctx, "public-test-component", appNs.Name, annotations.PublicMode()) Expect(createErr).ToNot(HaveOccurred()) toRemove = append(toRemove, component) @@ -167,8 +167,8 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun It("should have new hosts propagated back to watched resource by the controller", func(ctx context.Context) { // given // required annotation for watched custom resource: - // routing.opendatahub.io/export-mode: "public" - component, createErr := createComponentRequiringPlatformRouting(ctx, "public-test-component", "public", appNs.Name) + // routing.opendatahub.io/export-mode-public: "true" + component, createErr := createComponentRequiringPlatformRouting(ctx, "public-test-component", appNs.Name, annotations.PublicMode()) Expect(createErr).ToNot(HaveOccurred()) toRemove = append(toRemove, component) @@ -191,7 +191,8 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun ), "public services are not expected to be defined in this mode") publicAddressAnnotation := annotations.RoutingAddressesPublic( - fmt.Sprintf("%[1]s-%[2]s.%[3]s;%[1]s-%[2]s.%[3]s.svc;%[1]s-%[2]s.%[3]s.svc.cluster.local", + fmt.Sprintf("%[1]s-http-%[2]s.%[3]s;%[1]s-http-%[2]s.%[3]s.svc;%[1]s-http-%[2]s.%[3]s.svc.cluster.local;"+ + "%[1]s-grpc-%[2]s.%[3]s;%[1]s-grpc-%[2]s.%[3]s.svc;%[1]s-grpc-%[2]s.%[3]s.svc.cluster.local", svc.Name, svc.Namespace, routingConfiguration.GatewayNamespace), ) @@ -214,8 +215,10 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun It("should have both external and cluster-local resources created", func(ctx context.Context) { // given // required annotation for watched custom resource: - // routing.opendatahub.io/export-mode: "public;external" - component, createErr := createComponentRequiringPlatformRouting(ctx, "public-and-external-test-component", "public;external", appNs.Name) + // routing.opendatahub.io/export-mode-external: "true" + // routing.opendatahub.io/export-mode-public: "true" + component, createErr := createComponentRequiringPlatformRouting(ctx, "public-and-external-test-component", + appNs.Name, annotations.ExternalMode(), annotations.PublicMode()) Expect(createErr).ToNot(HaveOccurred()) toRemove = append(toRemove, component) @@ -235,10 +238,12 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun return errGet } - externalAddressAnnotation := annotations.RoutingAddressesExternal(fmt.Sprintf("%s-%s.%s", svc.Name, svc.Namespace, domain)) + externalAddressAnnotation := annotations.RoutingAddressesExternal( + fmt.Sprintf("%[1]s-http-%[2]s.%[3]s"+";"+"%[1]s-grpc-%[2]s.%[3]s", svc.Name, svc.Namespace, domain)) publicAddrAnnotation := annotations.RoutingAddressesPublic( - fmt.Sprintf("%[1]s-%[2]s.%[3]s;%[1]s-%[2]s.%[3]s.svc;%[1]s-%[2]s.%[3]s.svc.cluster.local", + fmt.Sprintf("%[1]s-http-%[2]s.%[3]s;%[1]s-http-%[2]s.%[3]s.svc;%[1]s-http-%[2]s.%[3]s.svc.cluster.local;"+ + "%[1]s-grpc-%[2]s.%[3]s;%[1]s-grpc-%[2]s.%[3]s.svc;%[1]s-grpc-%[2]s.%[3]s.svc.cluster.local", svc.Name, svc.Namespace, routingConfiguration.GatewayNamespace, ), ) @@ -270,9 +275,10 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun It("should remove the routing resources when both public;external", func(ctx context.Context) { // given // required annotation for watched custom resource: - // routing.opendatahub.io/export-mode: "public;external" + // routing.opendatahub.io/export-mode-external: "true" + // routing.opendatahub.io/export-mode-public: "true" component, createErr := createComponentRequiringPlatformRouting(ctx, "public-and-external-test-component", - "public;external", appNs.Name) + appNs.Name, annotations.ExternalMode(), annotations.PublicMode()) Expect(createErr).ToNot(HaveOccurred()) toRemove = append(toRemove, component) @@ -311,9 +317,10 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun It("should remove all created routing resources", func(ctx context.Context) { // given // required annotation for watched custom resource: - // routing.opendatahub.io/export-mode: "public;external" + // routing.opendatahub.io/export-mode-external: "true" + // routing.opendatahub.io/export-mode-public: "true" component, createErr := createComponentRequiringPlatformRouting(ctx, "public-and-external-remove-annotation", - "public;external", appNs.Name) + appNs.Name, annotations.ExternalMode(), annotations.PublicMode()) Expect(createErr).ToNot(HaveOccurred()) // required labels for the exported service: @@ -327,8 +334,8 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun publicResourcesShouldExist(ctx, svc) // when - By("removing the export annotation", func() { - setExportMode(ctx, component, remove) + By("removing the export annotations", func() { + setExportModes(ctx, component, removeExternal, removePublic) }) // then @@ -348,9 +355,10 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun It("should remove all routing resources from removed", func(ctx context.Context) { // given // required annotation for watched custom resource: - // routing.opendatahub.io/export-mode: "public;external" + // routing.opendatahub.io/export-mode-external: "true" + // routing.opendatahub.io/export-mode-public: "true" component, createErr := createComponentRequiringPlatformRouting(ctx, "public-and-external-change-annotation", - "public;external", appNs.Name) + appNs.Name, annotations.ExternalMode(), annotations.PublicMode()) Expect(createErr).ToNot(HaveOccurred()) // required labels for the exported service: @@ -364,7 +372,7 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun publicResourcesShouldExist(ctx, svc) By("removing external from the export modes", func() { - setExportMode(ctx, component, "public") + setExportModes(ctx, component, enablePublic, disableExternal) }) // then @@ -383,7 +391,8 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun ), "public services are not expected to be defined in this mode") publicAddressAnnotation := annotations.RoutingAddressesPublic( - fmt.Sprintf("%[1]s-%[2]s.%[3]s;%[1]s-%[2]s.%[3]s.svc;%[1]s-%[2]s.%[3]s.svc.cluster.local", + fmt.Sprintf("%[1]s-http-%[2]s.%[3]s;%[1]s-http-%[2]s.%[3]s.svc;%[1]s-http-%[2]s.%[3]s.svc.cluster.local;"+ + "%[1]s-grpc-%[2]s.%[3]s;%[1]s-grpc-%[2]s.%[3]s.svc;%[1]s-grpc-%[2]s.%[3]s.svc.cluster.local", svc.Name, svc.Namespace, routingConfiguration.GatewayNamespace), ) @@ -402,9 +411,10 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun It("should remove all routing resources when the unsupported mode is used", func(ctx context.Context) { // given // required annotation for watched custom resource: - // routing.opendatahub.io/export-mode: "public;external" + // routing.opendatahub.io/export-mode-external: "true" + // routing.opendatahub.io/export-mode-public: "true" component, createErr := createComponentRequiringPlatformRouting(ctx, "public-and-external-changed-to-non-existing", - "public;external", appNs.Name) + appNs.Name, annotations.ExternalMode(), annotations.PublicMode()) Expect(createErr).ToNot(HaveOccurred()) // required labels for the exported service: @@ -417,8 +427,8 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun externalResourcesShouldExist(ctx, svc) publicResourcesShouldExist(ctx, svc) - By("removing external from the export modes", func() { - setExportMode(ctx, component, "not-supported-mode") + By("setting a non-supported mode", func() { + setExportModes(ctx, component, enableNonSupportedMode, removePublic, removeExternal) }) // then @@ -435,36 +445,6 @@ var _ = Describe("Platform routing setup for the component", test.EnvTest(), fun }) -type exportMode string - -var remove = exportMode("--blank--") - -func (m exportMode) ApplyToMeta(obj metav1.Object) { - annos := obj.GetAnnotations() - key := annotations.RoutingExportMode("").Key() - - switch m { - default: - annos[key] = string(m) - case remove: - delete(annos, key) - } - - obj.SetAnnotations(annos) -} - -func setExportMode(ctx context.Context, component *unstructured.Unstructured, mode exportMode) { - errGetComponent := envTest.Client.Get(ctx, client.ObjectKey{ - Namespace: component.GetNamespace(), - Name: component.GetName(), - }, component) - Expect(errGetComponent).ToNot(HaveOccurred()) - - metadata.ApplyMetaOptions(component, mode) - - Expect(envTest.Client.Update(ctx, component)).To(Succeed()) -} - func externalResourcesShouldExist(ctx context.Context, svc *corev1.Service) { Eventually(routeExistsFor(svc)). WithContext(ctx). @@ -564,68 +544,80 @@ func hasNoAddressAnnotations(component *unstructured.Unstructured) func(g Gomega } } -func routeExistsFor(exportedSvc *corev1.Service) func(g Gomega, ctx context.Context) error { +func routeExistsFor(exposedSvc *corev1.Service) func(g Gomega, ctx context.Context) error { return func(g Gomega, ctx context.Context) error { - svcRoute := &openshiftroutev1.Route{} - if errGet := envTest.Get(ctx, types.NamespacedName{ - Name: exportedSvc.Name + "-" + exportedSvc.Namespace + "-route", - Namespace: routingConfiguration.GatewayNamespace, - }, svcRoute); errGet != nil { - return errGet + g.Expect(exposedSvc.Spec.Ports).ToNot(BeEmpty()) + + for _, exposedPort := range exposedSvc.Spec.Ports { + svcRoute := &openshiftroutev1.Route{} + if errGet := envTest.Get(ctx, types.NamespacedName{ + Name: exposedSvc.Name + "-" + exposedPort.Name + "-" + exposedSvc.Namespace + "-route", + Namespace: routingConfiguration.GatewayNamespace, + }, svcRoute); errGet != nil { + return errGet + } + + g.Expect(svcRoute).To(BeAttachedToService(routingConfiguration.IngressService)) + g.Expect(svcRoute).To(HaveHost(exposedSvc.Name + "-" + exposedPort.Name + "-" + exposedSvc.Namespace + "." + domain)) } - g.Expect(svcRoute).To(BeAttachedToService(routingConfiguration.IngressService)) - g.Expect(svcRoute).To(HaveHost(exportedSvc.Name + "-" + exportedSvc.Namespace + "." + domain)) - return nil } } func publicSvcExistsFor(exposedSvc *corev1.Service) func(g Gomega, ctx context.Context) error { return func(g Gomega, ctx context.Context) error { - publicSvc := &corev1.Service{} - if errGet := envTest.Get(ctx, types.NamespacedName{ - Name: exposedSvc.Name + "-" + exposedSvc.Namespace, - Namespace: routingConfiguration.GatewayNamespace, - }, publicSvc); errGet != nil { - return errGet + g.Expect(exposedSvc.Spec.Ports).ToNot(BeEmpty()) + + for _, exposedPort := range exposedSvc.Spec.Ports { + publicSvc := &corev1.Service{} + if errGet := envTest.Get(ctx, types.NamespacedName{ + Name: exposedSvc.Name + "-" + exposedPort.Name + "-" + exposedSvc.Namespace, + Namespace: routingConfiguration.GatewayNamespace, + }, publicSvc); errGet != nil { + return errGet + } + + g.Expect(publicSvc.GetAnnotations()).To( + HaveKeyWithValue( + "service.beta.openshift.io/serving-cert-secret-name", + exposedSvc.Name+"-"+exposedPort.Name+"-"+exposedSvc.Namespace+"-certs", + ), + ) + + g.Expect(publicSvc.Spec.Selector).To( + HaveKeyWithValue(routingConfiguration.IngressSelectorLabel, routingConfiguration.IngressSelectorValue), + ) } - g.Expect(publicSvc.GetAnnotations()).To( - HaveKeyWithValue( - "service.beta.openshift.io/serving-cert-secret-name", - exposedSvc.Name+"-"+exposedSvc.Namespace+"-certs", - ), - ) - - g.Expect(publicSvc.Spec.Selector).To( - HaveKeyWithValue(routingConfiguration.IngressSelectorLabel, routingConfiguration.IngressSelectorValue), - ) - return nil } } func publicGatewayExistsFor(exposedSvc *corev1.Service) func(g Gomega, ctx context.Context) error { return func(g Gomega, ctx context.Context) error { - publicGateway := &v1beta1.Gateway{} - if errGet := envTest.Get(ctx, types.NamespacedName{ - Name: exposedSvc.Name + "-" + exposedSvc.Namespace, - Namespace: routingConfiguration.GatewayNamespace, - }, publicGateway); errGet != nil { - return errGet + g.Expect(exposedSvc.Spec.Ports).ToNot(BeEmpty()) + + for _, exposedPort := range exposedSvc.Spec.Ports { + publicGateway := &v1beta1.Gateway{} + if errGet := envTest.Get(ctx, types.NamespacedName{ + Name: exposedSvc.Name + "-" + exposedPort.Name + "-" + exposedSvc.Namespace, + Namespace: routingConfiguration.GatewayNamespace, + }, publicGateway); errGet != nil { + return errGet + } + + g.Expect(publicGateway.Spec.GetSelector()).To(HaveKeyWithValue(routingConfiguration.IngressSelectorLabel, routingConfiguration.IngressSelectorValue)) + // limitation: only checks first element of []*Server slice + g.Expect(publicGateway).To( + HaveHosts( + exposedSvc.Name+"-"+exposedPort.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace, + exposedSvc.Name+"-"+exposedPort.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace+".svc", + exposedSvc.Name+"-"+exposedPort.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace+".svc.cluster.local", + ), + ) } - g.Expect(publicGateway.Spec.GetSelector()).To(HaveKeyWithValue(routingConfiguration.IngressSelectorLabel, routingConfiguration.IngressSelectorValue)) - // limitation: only checks first element of []*Server slice - g.Expect(publicGateway).To( - HaveHosts( - exposedSvc.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace, - exposedSvc.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace+".svc", - exposedSvc.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace+".svc.cluster.local", - ), - ) - return nil } } @@ -633,22 +625,27 @@ func publicGatewayExistsFor(exposedSvc *corev1.Service) func(g Gomega, ctx conte func publicVirtualSvcExistsFor(exposedSvc *corev1.Service) func(g Gomega, ctx context.Context) error { return func(g Gomega, ctx context.Context) error { publicVS := &v1beta1.VirtualService{} - if errGet := envTest.Get(ctx, types.NamespacedName{ - Name: exposedSvc.Name + "-" + exposedSvc.Namespace, - Namespace: routingConfiguration.GatewayNamespace, - }, publicVS); errGet != nil { - return errGet - } - g.Expect(publicVS).To( - HaveHosts( - exposedSvc.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace, - exposedSvc.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace+".svc", - exposedSvc.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace+".svc.cluster.local", - ), - ) - g.Expect(publicVS).To(BeAttachedToGateways("mesh", exposedSvc.Name+"-"+exposedSvc.Namespace)) - g.Expect(publicVS).To(RouteToHost(exposedSvc.Name+"."+exposedSvc.Namespace+".svc.cluster.local", 8000)) + g.Expect(exposedSvc.Spec.Ports).ToNot(BeEmpty()) + + for _, exposedPort := range exposedSvc.Spec.Ports { + if errGet := envTest.Get(ctx, types.NamespacedName{ + Name: exposedSvc.Name + "-" + exposedPort.Name + "-" + exposedSvc.Namespace, + Namespace: routingConfiguration.GatewayNamespace, + }, publicVS); errGet != nil { + return errGet + } + + g.Expect(publicVS).To( + HaveHosts( + exposedSvc.Name+"-"+exposedPort.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace, + exposedSvc.Name+"-"+exposedPort.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace+".svc", + exposedSvc.Name+"-"+exposedPort.Name+"-"+exposedSvc.Namespace+"."+routingConfiguration.GatewayNamespace+".svc.cluster.local", + ), + ) + g.Expect(publicVS).To(BeAttachedToGateways("mesh", exposedSvc.Name+"-"+exposedPort.Name+"-"+exposedSvc.Namespace)) + g.Expect(publicVS).To(RouteToHost(exposedSvc.Name+"."+exposedSvc.Namespace+".svc.cluster.local", uint32(exposedPort.TargetPort.IntVal))) + } return nil } @@ -656,39 +653,47 @@ func publicVirtualSvcExistsFor(exposedSvc *corev1.Service) func(g Gomega, ctx co func destinationRuleExistsFor(exposedSvc *corev1.Service) func(g Gomega, ctx context.Context) error { return func(g Gomega, ctx context.Context) error { - destinationRule := &v1beta1.DestinationRule{} - if errGet := envTest.Get(ctx, types.NamespacedName{ - Name: exposedSvc.Name + "-" + exposedSvc.Namespace, - Namespace: routingConfiguration.GatewayNamespace, - }, destinationRule); errGet != nil { - return errGet + g.Expect(exposedSvc.Spec.Ports).ToNot(BeEmpty()) + + for _, exposedPort := range exposedSvc.Spec.Ports { + destinationRule := &v1beta1.DestinationRule{} + if errGet := envTest.Get(ctx, types.NamespacedName{ + Name: exposedSvc.Name + "-" + exposedPort.Name + "-" + exposedSvc.Namespace, + Namespace: routingConfiguration.GatewayNamespace, + }, destinationRule); errGet != nil { + return errGet + } + + g.Expect(destinationRule).To( + HaveHost( + exposedSvc.Name + "-" + exposedPort.Name + "-" + exposedSvc.Namespace + "." + routingConfiguration.GatewayNamespace + ".svc.cluster.local", + ), + ) + g.Expect(destinationRule.Spec.GetTrafficPolicy().GetTls().GetMode()).To(Equal(istionetworkingv1beta1.ClientTLSSettings_DISABLE)) } - g.Expect(destinationRule).To( - HaveHost( - exposedSvc.Name + "-" + exposedSvc.Namespace + "." + routingConfiguration.GatewayNamespace + ".svc.cluster.local", - ), - ) - g.Expect(destinationRule.Spec.GetTrafficPolicy().GetTls().GetMode()).To(Equal(istionetworkingv1beta1.ClientTLSSettings_DISABLE)) - return nil } } -func ingressVirtualServiceExistsFor(exportedSvc *corev1.Service) func(g Gomega, ctx context.Context) error { +func ingressVirtualServiceExistsFor(exposedSvc *corev1.Service) func(g Gomega, ctx context.Context) error { return func(g Gomega, ctx context.Context) error { - routerVS := &v1beta1.VirtualService{} - if errGet := envTest.Get(ctx, types.NamespacedName{ - Name: exportedSvc.Name + "-" + exportedSvc.Namespace + "-ingress", - Namespace: routingConfiguration.GatewayNamespace, - }, routerVS); errGet != nil { - return errGet + g.Expect(exposedSvc.Spec.Ports).ToNot(BeEmpty()) + + for _, exposedPort := range exposedSvc.Spec.Ports { + routerVS := &v1beta1.VirtualService{} + if errGet := envTest.Get(ctx, types.NamespacedName{ + Name: exposedSvc.Name + "-" + exposedPort.Name + "-" + exposedSvc.Namespace + "-ingress", + Namespace: routingConfiguration.GatewayNamespace, + }, routerVS); errGet != nil { + return errGet + } + + g.Expect(routerVS).To(HaveHost(exposedSvc.Name + "-" + exposedPort.Name + "-" + exposedSvc.Namespace + "." + domain)) + g.Expect(routerVS).To(BeAttachedToGateways(routingConfiguration.IngressService)) + g.Expect(routerVS).To(RouteToHost(exposedSvc.Name+"."+exposedSvc.Namespace+".svc.cluster.local", uint32(exposedPort.TargetPort.IntValue()))) } - g.Expect(routerVS).To(HaveHost(exportedSvc.Name + "-" + exportedSvc.Namespace + "." + domain)) - g.Expect(routerVS).To(BeAttachedToGateways(routingConfiguration.IngressService)) - g.Expect(routerVS).To(RouteToHost(exportedSvc.Name+"."+exportedSvc.Namespace+".svc.cluster.local", 8000)) - return nil } } @@ -717,6 +722,11 @@ func simpleSvcDeployment(ctx context.Context, nsName, svcName string) (*appsv1.D Port: 8080, TargetPort: intstr.FromInt32(8000), }, + { + Name: "grpc", + Port: 9080, + TargetPort: intstr.FromInt32(9000), + }, }, }, } diff --git a/controllers/routingctrl/fixtures_test.go b/controllers/routingctrl/fixtures_test.go index 0fbb30d..a787da8 100644 --- a/controllers/routingctrl/fixtures_test.go +++ b/controllers/routingctrl/fixtures_test.go @@ -10,6 +10,7 @@ import ( "github.com/opendatahub-io/odh-platform/pkg/metadata/labels" "github.com/opendatahub-io/odh-platform/test" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -46,12 +47,61 @@ func addRoutingRequirementsToSvc(ctx context.Context, exportedSvc *corev1.Servic Expect(errExportSvc).ToNot(HaveOccurred()) } -func createComponentRequiringPlatformRouting(ctx context.Context, componentName, mode, appNs string) (*unstructured.Unstructured, error) { +// createComponentRequiringPlatformRouting creates a new component with the specified routing modes. +func createComponentRequiringPlatformRouting(ctx context.Context, componentName, appNs string, modes ...annotations.RoutingExportMode) (*unstructured.Unstructured, error) { component, errCreate := test.CreateUnstructured(componentResource(componentName, appNs)) Expect(errCreate).ToNot(HaveOccurred()) - // set component's "routing.opendatahub.io/export-mode" annotation to the specified mode. - metadata.ApplyMetaOptions(component, annotations.RoutingExportMode(mode)) + for _, mode := range modes { + metadata.ApplyMetaOptions(component, mode) + } return component, envTest.Client.Create(ctx, component) } + +type exportModeAction struct { + mode string + value string +} + +var ( + enablePublic = exportModeAction{mode: annotations.PublicMode().Key(), value: "true"} + disableExternal = exportModeAction{mode: annotations.ExternalMode().Key(), value: "false"} + removeExternal = exportModeAction{mode: annotations.ExternalMode().Key(), value: ""} + removePublic = exportModeAction{mode: annotations.PublicMode().Key(), value: ""} + enableNonSupportedMode = exportModeAction{ + mode: annotations.RoutingExportModePrefix + "notsupported", + value: "true", + } +) + +func (m exportModeAction) ApplyToMeta(obj metav1.Object) { + annos := obj.GetAnnotations() + if annos == nil { + annos = make(map[string]string) + } + + key := m.mode + + if m.value == "" { + delete(annos, key) + } else { + annos[key] = m.value + } + + obj.SetAnnotations(annos) +} + +func setExportModes(ctx context.Context, component *unstructured.Unstructured, actions ...exportModeAction) { + errGetComponent := envTest.Client.Get(ctx, client.ObjectKey{ + Namespace: component.GetNamespace(), + Name: component.GetName(), + }, component) + Expect(errGetComponent).ToNot(HaveOccurred()) + + for _, action := range actions { + metadata.ApplyMetaOptions(component, action) + } + + Expect(envTest.Client.Update(ctx, component)).To(Succeed()) +} diff --git a/controllers/routingctrl/reconcile_resources.go b/controllers/routingctrl/reconcile_resources.go index e0dfb17..aca918a 100644 --- a/controllers/routingctrl/reconcile_resources.go +++ b/controllers/routingctrl/reconcile_resources.go @@ -35,7 +35,7 @@ func (r *Controller) createRoutingResources(ctx context.Context, target *unstruc 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) + return fmt.Errorf("could not render labels for ServiceSelector %v: %w", r.component.ServiceSelector, errLables) } exportedServices, errSvcGet := getExportedServices(ctx, r.Client, renderedSelectors, target) @@ -68,45 +68,51 @@ func (r *Controller) createRoutingResources(ctx context.Context, target *unstruc func (r *Controller) exportService(ctx context.Context, target *unstructured.Unstructured, exportedSvc *corev1.Service, domain string) error { exportModes := r.extractExportModes(target) - templateData := routing.NewExposedServiceConfig(exportedSvc, r.config, domain) + externalHosts := []string{} + publicHosts := []string{} // To establish ownership for watched component ownershipLabels := append(labels.AsOwner(target), labels.AppManagedBy("odh-routing-controller")) - for _, exportMode := range exportModes { - resources, err := r.templateLoader.Load(templateData, exportMode) - if err != nil { - return fmt.Errorf("could not load templates for type %s: %w", exportMode, err) - } - - ownershipLabels = append(ownershipLabels, labels.ExportType(exportMode)) - if errApply := unstruct.Apply(ctx, r.Client, resources, ownershipLabels...); errApply != nil { - return fmt.Errorf("could not apply routing resources for type %s: %w", exportMode, errApply) + for _, exportedSvcPort := range exportedSvc.Spec.Ports { + templateData := routing.NewExposedServiceConfig(exportedSvc, exportedSvcPort, r.config, domain) + + for _, exportMode := range exportModes { + resources, err := r.templateLoader.Load(templateData, exportMode) + if err != nil { + return fmt.Errorf("could not load templates for type %s: %w", exportMode, err) + } + + ownershipLabels = append(ownershipLabels, labels.ExportType(exportMode)) + if errApply := unstruct.Apply(ctx, r.Client, resources, ownershipLabels...); errApply != nil { + return fmt.Errorf("could not apply routing resources for type %s: %w", exportMode, errApply) + } + + switch exportMode { + case routing.ExternalRoute: + externalHosts = append(externalHosts, templateData.ExternalHost()) + case routing.PublicRoute: + publicHosts = append(publicHosts, templateData.PublicHosts()...) + } } } - return r.propagateHostsToWatchedCR(target, templateData) + return r.propagateHostsToWatchedCR(target, publicHosts, externalHosts) } -func (r *Controller) propagateHostsToWatchedCR(target *unstructured.Unstructured, data *routing.ExposedServiceConfig) error { - exportModes := r.extractExportModes(target) - +func (r *Controller) propagateHostsToWatchedCR(target *unstructured.Unstructured, publicHosts, externalHosts []string) error { // Remove all existing routing addresses metaOptions := []metadata.Option{ annotations.Remove(annotations.RoutingAddressesExternal("")), annotations.Remove(annotations.RoutingAddressesPublic("")), } - // TODO(mvp): put the logic of creating host names into a single place - for _, exportMode := range exportModes { - switch exportMode { - case routing.ExternalRoute: - externalAddress := annotations.RoutingAddressesExternal(fmt.Sprintf("%s-%s.%s", data.ServiceName, data.ServiceNamespace, data.Domain)) - metaOptions = append(metaOptions, externalAddress) - case routing.PublicRoute: - publicAddresses := annotations.RoutingAddressesPublic(fmt.Sprintf("%[1]s.%[2]s;%[1]s.%[2]s.svc;%[1]s.%[2]s.svc.cluster.local", data.PublicServiceName, data.GatewayNamespace)) - metaOptions = append(metaOptions, publicAddresses) - } + if len(publicHosts) > 0 { + metaOptions = append(metaOptions, annotations.RoutingAddressesPublic(strings.Join(publicHosts, ";"))) + } + + if len(externalHosts) > 0 { + metaOptions = append(metaOptions, annotations.RoutingAddressesExternal(strings.Join(externalHosts, ";"))) } metadata.ApplyMetaOptions(target, metaOptions...) @@ -124,24 +130,26 @@ func (r *Controller) ensureResourceHasFinalizer(ctx context.Context, target *uns return nil } +// extractExportModes retrieves the enabled export modes from the target's annotations. func (r *Controller) extractExportModes(target *unstructured.Unstructured) []routing.RouteType { - exportModes, exportModeFound := target.GetAnnotations()[annotations.RoutingExportMode("").Key()] - if !exportModeFound { + targetAnnotations := target.GetAnnotations() + if targetAnnotations == nil { return nil } - exportModesSplit := strings.Split(exportModes, ";") - validRouteTypes := make([]routing.RouteType, 0, len(exportModesSplit)) - - for _, exportMode := range exportModesSplit { - routeType := routing.RouteType(strings.TrimSpace(exportMode)) - if routing.IsValidRouteType(routeType) { - validRouteTypes = append(validRouteTypes, routeType) - } else { - r.log.Info("Invalid route type found", - "invalidRouteType", routeType, - "resourceName", target.GetName(), - "resourceNamespace", target.GetNamespace()) + validRouteTypes := make([]routing.RouteType, 0) + + for key, value := range targetAnnotations { + if value == "true" { + routeType, valid := routing.IsValidRouteType(key) + if valid { + validRouteTypes = append(validRouteTypes, routeType) + } else { + r.log.Info("Invalid route type found", + "invalidRouteType", routeType, + "resourceName", target.GetName(), + "resourceNamespace", target.GetNamespace()) + } } } diff --git a/controllers/types.go b/controllers/types.go index 932f371..c34ce42 100644 --- a/controllers/types.go +++ b/controllers/types.go @@ -7,8 +7,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" ) -type Activable interface { - Activate() +type Activable[T any] interface { + Activate(config T) Deactivate() } diff --git a/pkg/authorization/authconfig.go b/pkg/authorization/authconfig.go index e20e408..ed9455e 100644 --- a/pkg/authorization/authconfig.go +++ b/pkg/authorization/authconfig.go @@ -36,23 +36,17 @@ var authConfigTemplateAnonymous []byte var authConfigTemplateUserDefined []byte type staticTemplateLoader struct { - audience []string } var _ AuthConfigTemplateLoader = (*staticTemplateLoader)(nil) -func NewStaticTemplateLoader(audience []string) *staticTemplateLoader { - return &staticTemplateLoader{audience: audience} +func NewStaticTemplateLoader() *staticTemplateLoader { + return &staticTemplateLoader{} } -func (s *staticTemplateLoader) Load(_ context.Context, authType AuthType, key types.NamespacedName) (authorinov1beta2.AuthConfig, error) { +func (s *staticTemplateLoader) Load(_ context.Context, authType AuthType, key types.NamespacedName, templateData map[string]any) (authorinov1beta2.AuthConfig, error) { authConfig := authorinov1beta2.AuthConfig{} - templateData := map[string]interface{}{ - "Namespace": key.Namespace, - "Audiences": s.audience, - } - templateContent := authConfigTemplateAnonymous if authType == UserDefined { templateContent = authConfigTemplateUserDefined @@ -71,7 +65,7 @@ func (s *staticTemplateLoader) Load(_ context.Context, authType AuthType, key ty return authConfig, nil } -func (s *staticTemplateLoader) resolveTemplate(tmpl []byte, data map[string]interface{}) ([]byte, error) { +func (s *staticTemplateLoader) resolveTemplate(tmpl []byte, data map[string]any) ([]byte, error) { engine, err := template.New("authconfig").Parse(string(tmpl)) if err != nil { return []byte{}, fmt.Errorf("could not create template engine: %w", err) @@ -103,9 +97,9 @@ func NewConfigMapTemplateLoader(cli client.Client, fallback AuthConfigTemplateLo // TODO: check "authconfig-template" CM in key.Namespace to see if there is a "spec" to use, construct a AuthConfig object // https://issues.redhat.com/browse/RHOAIENG-847 -func (c *configMapTemplateLoader) Load(ctx context.Context, authType AuthType, key types.NamespacedName) (authorinov1beta2.AuthConfig, error) { +func (c *configMapTemplateLoader) Load(ctx context.Context, authType AuthType, key types.NamespacedName, templateData map[string]any) (authorinov1beta2.AuthConfig, error) { // else - ac, err := c.fallback.Load(ctx, authType, key) + ac, err := c.fallback.Load(ctx, authType, key, templateData) if err != nil { return authorinov1beta2.AuthConfig{}, fmt.Errorf("could not load from fallback: %w", err) } diff --git a/pkg/authorization/types.go b/pkg/authorization/types.go index 3581da8..ae974c6 100644 --- a/pkg/authorization/types.go +++ b/pkg/authorization/types.go @@ -37,5 +37,5 @@ type AuthTypeDetector interface { // - Namespace / Resource name // - Loader source type AuthConfigTemplateLoader interface { - Load(ctx context.Context, authType AuthType, key types.NamespacedName) (v1beta2.AuthConfig, error) + Load(ctx context.Context, authType AuthType, key types.NamespacedName, templateData map[string]any) (v1beta2.AuthConfig, error) } diff --git a/pkg/metadata/annotations/types.go b/pkg/metadata/annotations/types.go index ab24b92..c273b65 100644 --- a/pkg/metadata/annotations/types.go +++ b/pkg/metadata/annotations/types.go @@ -5,6 +5,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const ( + RoutingExportModePrefix = "routing.opendatahub.io/export-mode-" +) + type Annotation interface { metadata.Option metadata.KeyValue @@ -44,21 +48,32 @@ func (a AuthorizationGroup) Value() string { return string(a) } -// RoutingExportMode defines the export mode for the routing capability. It can be -// either "public" or "external" or both, delimited by ";". -// It is intended to be defined on the component's Custom Resource. -type RoutingExportMode string +// RoutingExportMode defines an individual export mode for the routing capability. +// Each mode (currently: "public" or "external") is represented by a separate annotation. +// The annotation key is formed by prefixing the mode with "routing.opendatahub.io/export-mode-", value is boolean. +type RoutingExportMode struct { + mode string + value string +} + +func ExternalMode() RoutingExportMode { + return RoutingExportMode{mode: "external", value: "true"} +} + +func PublicMode() RoutingExportMode { + return RoutingExportMode{mode: "public", value: "true"} +} func (r RoutingExportMode) ApplyToMeta(obj metav1.Object) { addAnnotation(r, obj) } func (r RoutingExportMode) Key() string { - return "routing.opendatahub.io/export-mode" + return RoutingExportModePrefix + r.mode } func (r RoutingExportMode) Value() string { - return string(r) + return r.value } // RoutingAddressesPublic exposes the public addresses set by Platform's routing capability. diff --git a/pkg/routing/routing_test.go b/pkg/routing/routing_test.go index 903496f..ceab432 100644 --- a/pkg/routing/routing_test.go +++ b/pkg/routing/routing_test.go @@ -22,6 +22,16 @@ var _ = Describe("Resource functions", test.Unit(), func() { IngressSelectorValue: "rhoai-gateway", IngressService: "rhoai-router-ingress", } + httpPort := corev1.ServicePort{ + Name: "http-api", + Port: 80, + AppProtocol: ptr.To("http"), + } + grpcPort := corev1.ServicePort{ + Name: "grpc-api", + Port: 90, + AppProtocol: ptr.To("grpc"), + } data := routing.NewExposedServiceConfig(&corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -30,14 +40,12 @@ var _ = Describe("Resource functions", test.Unit(), func() { }, Spec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ - { - Name: "http-api", - Port: 80, - AppProtocol: ptr.To("http"), - }, + httpPort, + grpcPort, }, }, - }, config, "app-crc.testing") + }, + httpPort, config, "app-crc.testing") It("should load public resources", func() { // given @@ -71,7 +79,7 @@ var _ = Describe("Resource functions", test.Unit(), func() { // given extractor := spi.NewAnnotationHostExtractor(";", "A", "B") target := unstructured.Unstructured{ - Object: map[string]interface{}{}, + Object: map[string]any{}, } target.SetAnnotations(map[string]string{ "A": "a.com;a2.com", diff --git a/pkg/routing/template/routing_external.yaml b/pkg/routing/template/routing_external.yaml index 0bad2c0..82a4fbd 100644 --- a/pkg/routing/template/routing_external.yaml +++ b/pkg/routing/template/routing_external.yaml @@ -7,7 +7,7 @@ spec: to: kind: Service name: {{ .IngressService }} - host: {{ .PublicServiceName }}.{{ .Domain }} + host: {{ .ExternalHost }} port: targetPort: https tls: @@ -23,7 +23,7 @@ spec: gateways: - {{ .IngressService }} # name of wildcard Gateway hosts: - - {{ .PublicServiceName}}.{{ .Domain }} # hostname on the Route + - {{ .ExternalHost }} # hostname on the Route http: - name: {{ .PublicServiceName }}-ingress route: diff --git a/pkg/routing/template/routing_public.yaml b/pkg/routing/template/routing_public.yaml index 988c4a2..391a532 100644 --- a/pkg/routing/template/routing_public.yaml +++ b/pkg/routing/template/routing_public.yaml @@ -25,9 +25,9 @@ spec: {{ .IngressSelectorLabel }}: {{ .IngressSelectorValue }} servers: - hosts: - - {{ .PublicServiceName }}.{{ .GatewayNamespace }} - - {{ .PublicServiceName }}.{{ .GatewayNamespace }}.svc - - {{ .PublicServiceName }}.{{ .GatewayNamespace }}.svc.cluster.local +{{ range $host := .PublicHosts }} + - {{ $host }} +{{ end }} port: name: https number: 443 @@ -47,9 +47,9 @@ spec: - {{ .PublicServiceName }} # Gateway for public service - mesh # for clients in the mesh hosts: - - {{ .PublicServiceName }}.{{ .GatewayNamespace }} - - {{ .PublicServiceName }}.{{ .GatewayNamespace }}.svc - - {{ .PublicServiceName }}.{{ .GatewayNamespace }}.svc.cluster.local +{{ range $host := .PublicHosts }} + - {{ $host }} +{{ end }} http: - name: {{ .PublicServiceName }} route: diff --git a/pkg/routing/types.go b/pkg/routing/types.go index 2df159f..c424495 100644 --- a/pkg/routing/types.go +++ b/pkg/routing/types.go @@ -1,8 +1,9 @@ package routing import ( - "slices" + "strings" + "github.com/opendatahub-io/odh-platform/pkg/metadata/annotations" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -18,8 +19,20 @@ func AllRouteTypes() []RouteType { return []RouteType{PublicRoute, ExternalRoute} } -func IsValidRouteType(routeType RouteType) bool { - return slices.Contains(AllRouteTypes(), routeType) +func IsValidRouteType(annotationKey string) (RouteType, bool) { + if !strings.HasPrefix(annotationKey, annotations.RoutingExportModePrefix) { + return "", false + } + + routeType := RouteType(strings.TrimPrefix(annotationKey, annotations.RoutingExportModePrefix)) + + for _, validType := range AllRouteTypes() { + if routeType == validType { + return routeType, true + } + } + + return routeType, false } func UnusedRouteTypes(exportModes []RouteType) []RouteType { @@ -60,13 +73,25 @@ type ExposedServiceConfig struct { Domain string } -func NewExposedServiceConfig(svc *corev1.Service, config IngressConfig, domain string) *ExposedServiceConfig { +func (t ExposedServiceConfig) ExternalHost() string { + return t.PublicServiceName + "." + t.Domain +} + +func (t ExposedServiceConfig) PublicHosts() []string { + return []string{ + t.PublicServiceName + "." + t.IngressConfig.GatewayNamespace, + t.PublicServiceName + "." + t.IngressConfig.GatewayNamespace + ".svc", + t.PublicServiceName + "." + t.IngressConfig.GatewayNamespace + ".svc.cluster.local", + } +} + +func NewExposedServiceConfig(svc *corev1.Service, svcPort corev1.ServicePort, config IngressConfig, domain string) *ExposedServiceConfig { return &ExposedServiceConfig{ IngressConfig: config, - PublicServiceName: svc.GetName() + "-" + svc.GetNamespace(), + PublicServiceName: svc.GetName() + "-" + svcPort.Name + "-" + svc.GetNamespace(), ServiceName: svc.GetName(), ServiceNamespace: svc.GetNamespace(), - ServiceTargetPort: svc.Spec.Ports[0].TargetPort.String(), + ServiceTargetPort: svcPort.TargetPort.String(), Domain: domain, } } diff --git a/pkg/spi/host_extractor_test.go b/pkg/spi/host_extractor_test.go index 868906e..6518ba3 100644 --- a/pkg/spi/host_extractor_test.go +++ b/pkg/spi/host_extractor_test.go @@ -14,8 +14,8 @@ var _ = Describe("Host extraction", test.Unit(), func() { // given extractor := spi.NewPathExpressionExtractor([]string{"status.url"}) target := unstructured.Unstructured{ - Object: map[string]interface{}{ - "status": map[string]interface{}{ + Object: map[string]any{ + "status": map[string]any{ "url": "http://test.com", }, }, @@ -33,7 +33,7 @@ var _ = Describe("Host extraction", test.Unit(), func() { // given extractor := spi.NewPathExpressionExtractor([]string{"status.url"}) target := unstructured.Unstructured{ - Object: map[string]interface{}{}, + Object: map[string]any{}, } Expect(unstructured.SetNestedStringSlice(target.Object, []string{"test.com", "test2.com"}, "status", "url")).To(Succeed()) @@ -50,7 +50,7 @@ var _ = Describe("Host extraction", test.Unit(), func() { // given extractor := spi.NewPathExpressionExtractor([]string{"status.url"}) target := unstructured.Unstructured{ - Object: map[string]interface{}{}, + Object: map[string]any{}, } Expect(unstructured.SetNestedStringSlice(target.Object, []string{"test.com", "http://test.com", "https://test.com"}, "status", "url")).To(Succeed())