diff --git a/api/v1alpha1/modelregistry_types.go b/api/v1alpha1/modelregistry_types.go index 64188d4..269f83f 100644 --- a/api/v1alpha1/modelregistry_types.go +++ b/api/v1alpha1/modelregistry_types.go @@ -93,6 +93,11 @@ type RestSpec struct { // Listen port for REST connections, defaults to 8080. Port *int32 `json:"port,omitempty"` + //+kubebuilder:validation:Enum=disabled;enabled + //+kubebuilder:default=disabled + // Create an OpenShift Route for REST Service + ServiceRoute string `json:"serviceRoute,omitempty"` + // Resource requirements //+optional Resources *v1.ResourceRequirements `json:"resources,omitempty"` diff --git a/api/v1alpha1/modelregistry_webhook.go b/api/v1alpha1/modelregistry_webhook.go index 777a54a..4a2c545 100644 --- a/api/v1alpha1/modelregistry_webhook.go +++ b/api/v1alpha1/modelregistry_webhook.go @@ -51,6 +51,10 @@ func (r *ModelRegistry) Default() { if len(r.Spec.Grpc.Image) == 0 { r.Spec.Grpc.Image = config.GetStringConfigWithDefault(config.GrpcImage, config.DefaultGrpcImage) } + + if len(r.Spec.Rest.ServiceRoute) == 0 { + r.Spec.Rest.ServiceRoute = config.RouteDisabled + } if r.Spec.Rest.Resources == nil { r.Spec.Rest.Resources = config.MlmdRestResourceRequirements.DeepCopy() } diff --git a/cmd/main.go b/cmd/main.go index 61effd8..d7e21b1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,6 +18,7 @@ package main import ( "flag" + "k8s.io/client-go/discovery" "os" "github.com/opendatahub-io/model-registry-operator/internal/controller/config" @@ -26,6 +27,7 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + oapi "github.com/openshift/api" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -48,6 +50,7 @@ const EnableWebhooks = "ENABLE_WEBHOOKS" func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(oapi.Install(scheme)) utilruntime.Must(modelregistryv1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme @@ -100,6 +103,19 @@ func main() { } setupLog.Info("parsed kubernetes templates", "templates", template.DefinedTemplates()) + discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(mgr.GetConfig()) + groups, err := discoveryClient.ServerGroups() + if err != nil { + setupLog.Error(err, "error discovering server groups") + os.Exit(1) + } + isOpenShift := false + for _, g := range groups.Groups { + if g.Name == "route.openshift.io" { + isOpenShift = true + } + } + enableWebhooks := os.Getenv(EnableWebhooks) != "false" if err = (&controller.ModelRegistryReconciler{ Client: mgr.GetClient(), @@ -108,6 +124,7 @@ func main() { Log: ctrl.Log.WithName("controller"), Template: template, EnableWebhooks: enableWebhooks, + IsOpenShift: isOpenShift, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ModelRegistry") os.Exit(1) diff --git a/config/crd/bases/modelregistry.opendatahub.io_modelregistries.yaml b/config/crd/bases/modelregistry.opendatahub.io_modelregistries.yaml index a143b4e..1bd3312 100644 --- a/config/crd/bases/modelregistry.opendatahub.io_modelregistries.yaml +++ b/config/crd/bases/modelregistry.opendatahub.io_modelregistries.yaml @@ -249,6 +249,13 @@ spec: Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + serviceRoute: + default: disabled + description: Create an OpenShift Route for REST Service + enum: + - disabled + - enabled + type: string type: object required: - grpc diff --git a/config/samples/modelregistry_v1alpha1_modelregistry.yaml b/config/samples/modelregistry_v1alpha1_modelregistry.yaml index 48f1e1f..4a89f54 100644 --- a/config/samples/modelregistry_v1alpha1_modelregistry.yaml +++ b/config/samples/modelregistry_v1alpha1_modelregistry.yaml @@ -14,6 +14,7 @@ spec: port: 9090 rest: port: 8080 + serviceRoute: disabled postgres: host: model-registry-db database: model-registry diff --git a/go.mod b/go.mod index aa59f4b..3974ca3 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/go-logr/logr v1.3.0 github.com/onsi/ginkgo/v2 v2.13.1 github.com/onsi/gomega v1.30.0 + github.com/openshift/api v0.0.0-20231116201359-a5824a0c15b6 github.com/spf13/viper v1.17.0 k8s.io/api v0.28.3 k8s.io/apimachinery v0.28.3 diff --git a/go.sum b/go.sum index e244c0b..6589ab4 100644 --- a/go.sum +++ b/go.sum @@ -246,6 +246,8 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/openshift/api v0.0.0-20231116201359-a5824a0c15b6 h1:4RuImZKF08UNKj3vt77jPLDdWIKOXudGhPSV25Vwca8= +github.com/openshift/api v0.0.0-20231116201359-a5824a0c15b6/go.mod h1:qNtV0315F+f8ld52TLtPvrfivZpdimOzTi3kn9IVbtU= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/internal/controller/config/defaults.go b/internal/controller/config/defaults.go index 00c5dfe..6af70fa 100644 --- a/internal/controller/config/defaults.go +++ b/internal/controller/config/defaults.go @@ -32,6 +32,8 @@ const ( RestImage = "REST_IMAGE" DefaultGrpcImage = "gcr.io/tfx-oss-public/ml_metadata_store_server:1.14.0" DefaultRestImage = "quay.io/opendatahub/model-registry:latest" + RouteDisabled = "disabled" + RouteEnabled = "enabled" ) // Default ResourceRequirements diff --git a/internal/controller/config/templates/http-route.yaml.tmpl b/internal/controller/config/templates/http-route.yaml.tmpl new file mode 100644 index 0000000..79d83fa --- /dev/null +++ b/internal/controller/config/templates/http-route.yaml.tmpl @@ -0,0 +1,14 @@ +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{.Name}}-http + namespace: {{.Namespace}} + labels: + app: {{.Name}} + component: model-registry +spec: + to: + kind: Service + name: {{.Name}} + port: + targetPort: http-api diff --git a/internal/controller/modelregistry_controller.go b/internal/controller/modelregistry_controller.go index f63c94d..b9c9376 100644 --- a/internal/controller/modelregistry_controller.go +++ b/internal/controller/modelregistry_controller.go @@ -21,6 +21,9 @@ import ( "fmt" "github.com/banzaicloud/k8s-objectmatcher/patch" "github.com/go-logr/logr" + modelregistryv1alpha1 "github.com/opendatahub-io/model-registry-operator/api/v1alpha1" + "github.com/opendatahub-io/model-registry-operator/internal/controller/config" + routev1 "github.com/openshift/api/route/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -35,8 +38,6 @@ import ( klog "sigs.k8s.io/controller-runtime/pkg/log" "strings" "text/template" - - modelregistryv1alpha1 "github.com/opendatahub-io/model-registry-operator/api/v1alpha1" ) const modelRegistryFinalizer = "modelregistry.opendatahub.io/finalizer" @@ -65,6 +66,7 @@ type ModelRegistryReconciler struct { Log logr.Logger Template *template.Template EnableWebhooks bool + IsOpenShift bool } // Reconcile is part of the main kubernetes reconciliation loop which aims to @@ -215,12 +217,15 @@ func IgnoreDeletingErrors(err error) error { // SetupWithManager sets up the controller with the Manager. func (r *ModelRegistryReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). + builder := ctrl.NewControllerManagedBy(mgr). For(&modelregistryv1alpha1.ModelRegistry{}). Owns(&corev1.Service{}). Owns(&corev1.ServiceAccount{}). - Owns(&appsv1.Deployment{}). - Complete(r) + Owns(&appsv1.Deployment{}) + if r.IsOpenShift { + builder = builder.Owns(&routev1.Route{}) + } + return builder.Complete(r) } //+kubebuilder:rbac:groups=modelregistry.opendatahub.io,resources=modelregistries,verbs=get;list;watch;create;update;patch;delete @@ -232,7 +237,7 @@ func (r *ModelRegistryReconciler) SetupWithManager(mgr ctrl.Manager) error { //+kubebuilder:rbac:groups=core,resources=services;serviceaccounts,verbs=get;list;watch;create;update;patch;delete func (r *ModelRegistryReconciler) updateRegistryResources(ctx context.Context, params *ModelRegistryParams, registry *modelregistryv1alpha1.ModelRegistry) (OperationResult, error) { - var result, result2, result3 OperationResult + var result, result2 OperationResult var err error result, err = r.createOrUpdateServiceAccount(ctx, params, registry, "serviceaccount.yaml.tmpl") @@ -248,12 +253,22 @@ func (r *ModelRegistryReconciler) updateRegistryResources(ctx context.Context, p result = result2 } - result3, err = r.createOrUpdateDeployment(ctx, params, registry, "deployment.yaml.tmpl") + if r.IsOpenShift { + result2, err = r.createOrUpdateRoute(ctx, params, registry, "http-route.yaml.tmpl") + if err != nil { + return result2, err + } + if result2 != ResourceUnchanged { + result = result2 + } + } + + result2, err = r.createOrUpdateDeployment(ctx, params, registry, "deployment.yaml.tmpl") if err != nil { - return result3, err + return result2, err } - if result3 != ResourceUnchanged { - result = result3 + if result2 != ResourceUnchanged { + result = result2 } return result, nil @@ -341,6 +356,32 @@ func (r *ModelRegistryReconciler) createOrUpdateDeployment(ctx context.Context, return result, nil } +func (r *ModelRegistryReconciler) createOrUpdateRoute(ctx context.Context, params *ModelRegistryParams, + registry *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { + result = ResourceUnchanged + var route routev1.Route + if err = r.Apply(params, templateName, &route); err != nil { + return result, err + } + if err = ctrl.SetControllerReference(registry, &route, r.Scheme); err != nil { + return result, err + } + + if registry.Spec.Rest.ServiceRoute == config.RouteEnabled { + if result, err = r.createOrUpdate(ctx, route.DeepCopy(), &route); err != nil { + return result, err + } + } else { + // delete the route if it exists + if err = r.Client.Delete(ctx, &route); client.IgnoreNotFound(err) != nil { + result = ResourceUpdated + return result, err + } + } + + return result, nil +} + func (r *ModelRegistryReconciler) createOrUpdateService(ctx context.Context, params *ModelRegistryParams, registry *modelregistryv1alpha1.ModelRegistry, templateName string) (result OperationResult, err error) { result = ResourceUnchanged diff --git a/internal/controller/modelregistry_controller_test.go b/internal/controller/modelregistry_controller_test.go index 31eabac..4e04f90 100644 --- a/internal/controller/modelregistry_controller_test.go +++ b/internal/controller/modelregistry_controller_test.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "github.com/opendatahub-io/model-registry-operator/internal/controller/config" + "github.com/openshift/api" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/client-go/tools/record" "os" @@ -135,9 +136,11 @@ var _ = Describe("ModelRegistry controller", func() { return k8sClient.Get(ctx, typeNamespaceName, found) }, time.Minute, time.Second).Should(Succeed()) + scheme := k8sClient.Scheme() + _ = api.Install(scheme) modelRegistryReconciler := &ModelRegistryReconciler{ Client: k8sClient, - Scheme: k8sClient.Scheme(), + Scheme: scheme, Recorder: &record.FakeRecorder{}, Log: ctrl.Log.WithName("controller"), Template: template,