diff --git a/.golangci.yml b/.golangci.yml index 81023c7e9c7..cc5e93dc15a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -41,15 +41,16 @@ linters: - forbidigo - gochecknoglobals # Prevents use of global vars. - gofumpt - - gomnd # Doesnot allow hardcoded numbers - - gomoddirectives # Doesnot allow replace in go mod file + - golint # archived + - gomnd # Does not allow hardcoded numbers + - gomoddirectives # Does not allow replace in go mod file - interfacer - nestif - nilnil - nosnakecase # snakecase is used in a lot of places. Need to check if that is required. - paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test - tagliatelle - - varnamelen # doesnot allow shorter names like c,k etc. But golang prefers short named vars. + - varnamelen # does not allow shorter names like c,k etc. But golang prefers short named vars. - wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines - wrapcheck # check if this is required. Prevents direct return of err. @@ -69,4 +70,6 @@ issues: - path: tests/*/(.+)_test\.go linters: - typecheck - - dupl \ No newline at end of file + - dupl + - errcheck + - forcetypeassert \ No newline at end of file diff --git a/Dockerfiles/Dockerfile b/Dockerfiles/Dockerfile index bc9e7ed65ab..e0190e0ab83 100644 --- a/Dockerfiles/Dockerfile +++ b/Dockerfiles/Dockerfile @@ -1,27 +1,20 @@ -# Build the manager binary ARG GOLANG_VERSION=1.19 -ARG USE_LOCAL=false -ARG OVERWRITE_MANIFESTS="" ################################################################################ -FROM registry.access.redhat.com/ubi8/go-toolset:$GOLANG_VERSION as builder_local_false +FROM registry.access.redhat.com/ubi8/toolbox as manifests +ARG FETCH_MANIFESTS ARG OVERWRITE_MANIFESTS -# Get all manifests from remote git repo to builder_local_false by script USER root WORKDIR /opt COPY get_all_manifests.sh get_all_manifests.sh -RUN ./get_all_manifests.sh ${OVERWRITE_MANIFESTS} - -################################################################################ -FROM registry.access.redhat.com/ubi8/go-toolset:$GOLANG_VERSION as builder_local_true -# Get all manifests from local to builder_local_true -USER root -WORKDIR /opt -# copy local manifests to build COPY odh-manifests/ /opt/odh-manifests/ +RUN if [ "${FETCH_MANIFESTS}" = "true" ]; then \ + rm -rf /opt/odh-manifests/*; \ + ./get_all_manifests.sh ${OVERWRITE_MANIFESTS}; \ + fi ################################################################################ -FROM builder_local_${USE_LOCAL} as builder +FROM registry.access.redhat.com/ubi8/go-toolset:$GOLANG_VERSION as builder USER root WORKDIR /workspace # Copy the Go Modules manifests @@ -46,7 +39,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go FROM registry.access.redhat.com/ubi8/ubi-minimal:latest WORKDIR / COPY --from=builder /workspace/manager . -COPY --chown=1001:0 --from=builder /opt/odh-manifests /opt/manifests +COPY --chown=1001:0 --from=manifests /opt/odh-manifests /opt/manifests # Recursive change all files RUN chown -R 1001:0 /opt/manifests &&\ chmod -R a+r /opt/manifests diff --git a/Makefile b/Makefile index 528cbba4287..ddb2a2d6867 100644 --- a/Makefile +++ b/Makefile @@ -93,7 +93,7 @@ E2E_TEST_FLAGS = "--skip-deletion=false" -timeout 15m # See README.md, default g # Default image-build is to not use local odh-manifests folder # set to "true" to use local instead # see target "image-build" -IMAGE_BUILD_FLAGS = --build-arg USE_LOCAL=false +IMAGE_BUILD_FLAGS = --build-arg FETCH_MANIFESTS=true .PHONY: default default: lint unit-test build @@ -319,7 +319,7 @@ toolbox: ## Create a toolbox instance with the proper Golang and Operator SDK ve toolbox create opendatahub-toolbox --image localhost/opendatahub-toolbox:latest # Run tests. -TEST_SRC=./controllers/... ./tests/integration/features/... +TEST_SRC=./controllers/... ./tests/integration/... .PHONY: envtest envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. diff --git a/README.md b/README.md index 389fe0de1ef..2d9eddb8176 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,8 @@ make image-build By default, building an image without any local changes(as a clean build) This is what the production build system is doing. -In order to build an image with local `odh-manifests` folder, to set `IMAGE_BUILD_FLAGS ="--build-arg USE_LOCAL=true"` in make. -e.g `make image-build -e IMAGE_BUILD_FLAGS="--build-arg USE_LOCAL=true"` +In order to build an image with local `odh-manifests` folder, to set `IMAGE_BUILD_FLAGS ="--build-arg FETCH_MANIFESTS=false"` in make. +e.g `make image-build -e IMAGE_BUILD_FLAGS="--build-arg FETCH_MANIFESTS=false"` #### Build Image diff --git a/apis/dscinitialization/v1/dscinitialization_types.go b/apis/dscinitialization/v1/dscinitialization_types.go index 5f7ab7403d7..d028f527cad 100644 --- a/apis/dscinitialization/v1/dscinitialization_types.go +++ b/apis/dscinitialization/v1/dscinitialization_types.go @@ -40,8 +40,6 @@ type DSCInitializationSpec struct { // Configures Service Mesh as networking layer for Data Science Clusters components. // The Service Mesh is a mandatory prerequisite for single model serving (KServe) and // you should review this configuration if you are planning to use KServe. - // For other components, it enhances user experience; e.g. it provides unified - // authentication giving a Single Sign On experience. // +operator-sdk:csv:customresourcedefinitions:type=spec,order=3 // +optional ServiceMesh infrav1.ServiceMeshSpec `json:"serviceMesh,omitempty"` diff --git a/apis/dscinitialization/v1/zz_generated.deepcopy.go b/apis/dscinitialization/v1/zz_generated.deepcopy.go index 800f45905ed..ae1f71e7fe8 100644 --- a/apis/dscinitialization/v1/zz_generated.deepcopy.go +++ b/apis/dscinitialization/v1/zz_generated.deepcopy.go @@ -90,7 +90,7 @@ func (in *DSCInitializationList) DeepCopyObject() runtime.Object { func (in *DSCInitializationSpec) DeepCopyInto(out *DSCInitializationSpec) { *out = *in out.Monitoring = in.Monitoring - out.ServiceMesh = in.ServiceMesh + in.ServiceMesh.DeepCopyInto(&out.ServiceMesh) if in.DevFlags != nil { in, out := &in.DevFlags, &out.DevFlags *out = new(DevFlags) diff --git a/bundle/manifests/dscinitialization.opendatahub.io_dscinitializations.yaml b/bundle/manifests/dscinitialization.opendatahub.io_dscinitializations.yaml index a37df4ad4a2..aff0a3ec021 100644 --- a/bundle/manifests/dscinitialization.opendatahub.io_dscinitializations.yaml +++ b/bundle/manifests/dscinitialization.opendatahub.io_dscinitializations.yaml @@ -88,6 +88,48 @@ spec: user experience; e.g. it provides unified authentication giving a Single Sign On experience. properties: + auth: + description: Auth holds configuration of authentication and authorization + services used by Service Mesh in Opendatahub. + properties: + authorino: + description: Authorino holds configuration of Authorino service + used as external authorization provider. + properties: + audiences: + default: + - https://kubernetes.default.svc + description: Audiences is a list of the identifiers that + the resource server presented with the token identifies + as. Audience-aware token authenticators will verify + that the token was intended for at least one of the + audiences in this list. If no audiences are provided, + the audience will default to the audience of the Kubernetes + apiserver (kubernetes.default.svc). + items: + type: string + type: array + image: + default: quay.io/kuadrant/authorino:v0.13.0 + description: Image allows to define a custom container + image to be used when deploying Authorino's instance. + type: string + label: + default: authorino/topic=odh + description: Label narrows amount of AuthConfigs to process + by Authorino service. + type: string + name: + default: authorino-mesh-authz-provider + description: Name specifies how external authorization + provider should be called. + type: string + type: object + namespace: + default: auth-provider + description: Namespace where it is deployed. + type: string + type: object controlPlane: description: ControlPlane holds configuration of Service Mesh used by Opendatahub. diff --git a/bundle/manifests/opendatahub-operator.clusterserviceversion.yaml b/bundle/manifests/opendatahub-operator.clusterserviceversion.yaml index a61a6f34a8a..d4d3f91896e 100644 --- a/bundle/manifests/opendatahub-operator.clusterserviceversion.yaml +++ b/bundle/manifests/opendatahub-operator.clusterserviceversion.yaml @@ -96,9 +96,9 @@ metadata: "metadata": { "labels": { "app.kubernetes.io/created-by": "opendatahub-operator", - "app.kubernetes.io/instance": "default", + "app.kubernetes.io/instance": "default-feature", "app.kubernetes.io/managed-by": "kustomize", - "app.kubernetes.io/name": "default-feature", + "app.kubernetes.io/name": "featuretracker", "app.kubernetes.io/part-of": "opendatahub-operator" }, "name": "default-feature" @@ -318,6 +318,12 @@ spec: - tokenreviews verbs: - create + - apiGroups: + - authorino.kuadrant.io + resources: + - authconfigs + verbs: + - '*' - apiGroups: - authorization.k8s.io resources: @@ -1170,6 +1176,12 @@ spec: - deletecollection - get - patch + - apiGroups: + - networking.istio.io + resources: + - envoyfilters + verbs: + - '*' - apiGroups: - networking.istio.io resources: @@ -1250,6 +1262,12 @@ spec: - patch - update - watch + - apiGroups: + - operator.authorino.kuadrant.io + resources: + - authorinos + verbs: + - '*' - apiGroups: - operator.knative.dev resources: @@ -1390,6 +1408,19 @@ spec: - patch - update - watch + - apiGroups: + - route.openshift.io + resources: + - routes/custom-host + verbs: + - create + - get + - apiGroups: + - security.istio.io + resources: + - authorizationpolicies + verbs: + - '*' - apiGroups: - security.openshift.io resources: diff --git a/components/dashboard/dashboard.go b/components/dashboard/dashboard.go index 1d095008f3c..dda71cec0e6 100644 --- a/components/dashboard/dashboard.go +++ b/components/dashboard/dashboard.go @@ -21,23 +21,27 @@ import ( "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/common" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/monitoring" ) var ( - ComponentName = "dashboard" - ComponentNameSupported = "rhods-dashboard" - Path = deploy.DefaultManifestPath + "/" + ComponentName + "/base" - PathSupported = deploy.DefaultManifestPath + "/" + ComponentName + "/overlays/rhods" - PathISVSM = deploy.DefaultManifestPath + "/" + ComponentName + "/apps/apps-onprem" - PathISVAddOn = deploy.DefaultManifestPath + "/" + ComponentName + "/apps/apps-addon" - PathOVMS = deploy.DefaultManifestPath + "/" + ComponentName + "/modelserving" - PathODHDashboardConfig = deploy.DefaultManifestPath + "/" + ComponentName + "/odhdashboardconfig" - PathConsoleLink = deploy.DefaultManifestPath + "/" + ComponentName + "/consolelink" - PathCRDs = deploy.DefaultManifestPath + "/" + ComponentName + "/crd" - NameConsoleLink = "console" - NamespaceConsoleLink = "openshift-console" - PathAnaconda = deploy.DefaultManifestPath + "/partners/anaconda/base/" + ComponentName = "dashboard" + ComponentNameSupported = "rhods-dashboard" + Path = deploy.DefaultManifestPath + "/" + ComponentName + "/base" + PathServiceMesh = deploy.DefaultManifestPath + "/" + ComponentName + "/overlays/service-mesh" + PathSupported = deploy.DefaultManifestPath + "/" + ComponentName + "/overlays/rhods" + PathISVSM = deploy.DefaultManifestPath + "/" + ComponentName + "/apps/apps-onprem" + PathISVAddOn = deploy.DefaultManifestPath + "/" + ComponentName + "/apps/apps-addon" + PathOVMS = deploy.DefaultManifestPath + "/" + ComponentName + "/modelserving" + PathODHDashboardConfig = deploy.DefaultManifestPath + "/" + ComponentName + "/odhdashboardconfig" + PathODHProjectController = deploy.DefaultManifestPath + "/" + ProjectController + "/base" + PathConsoleLink = deploy.DefaultManifestPath + "/" + ComponentName + "/consolelink" + PathCRDs = deploy.DefaultManifestPath + "/" + ComponentName + "/crd" + NameConsoleLink = "console" + NamespaceConsoleLink = "openshift-console" + PathAnaconda = deploy.DefaultManifestPath + "/partners/anaconda/base/" + ProjectController = "odh-project-controller" ) // Verifies that Dashboard implements ComponentInterface. @@ -110,9 +114,8 @@ func (d *Dashboard) ReconcileComponent(ctx context.Context, return err } } - if platform == deploy.OpenDataHub || platform == "" { - err := cluster.UpdatePodSecurityRolebinding(cli, dscispec.ApplicationsNamespace, "odh-dashboard") - if err != nil { + if platform == deploy.OpenDataHub || platform == deploy.Unknown { + if err := cluster.UpdatePodSecurityRolebinding(cli, dscispec.ApplicationsNamespace, "odh-dashboard"); err != nil { return err } // Deploy CRDs @@ -122,8 +125,7 @@ func (d *Dashboard) ReconcileComponent(ctx context.Context, } if platform == deploy.SelfManagedRhods || platform == deploy.ManagedRhods { - err := cluster.UpdatePodSecurityRolebinding(cli, dscispec.ApplicationsNamespace, "rhods-dashboard") - if err != nil { + if err := cluster.UpdatePodSecurityRolebinding(cli, dscispec.ApplicationsNamespace, "rhods-dashboard"); err != nil { return err } // Deploy CRDs @@ -145,11 +147,22 @@ func (d *Dashboard) ReconcileComponent(ctx context.Context, } // Deploy odh-dashboard manifests - if platform == deploy.OpenDataHub || platform == "" { - if err = deploy.DeployManifestsFromPath(cli, owner, Path, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { + switch { + case platform == deploy.OpenDataHub, platform == deploy.Unknown: + base := Path + // TODO re-evaluate + if dscispec.ServiceMesh.ManagementState == operatorv1.Managed { + base = PathServiceMesh + } + if err = deploy.DeployManifestsFromPath(cli, owner, base, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { return err } - } else if platform == deploy.SelfManagedRhods || platform == deploy.ManagedRhods { + + if err := d.configureServiceMesh(cli, owner, dscispec); err != nil { + return err + } + + case platform == deploy.SelfManagedRhods, platform == deploy.ManagedRhods: // Apply authentication overlay if err := deploy.DeployManifestsFromPath(cli, owner, PathSupported, dscispec.ApplicationsNamespace, ComponentNameSupported, enabled); err != nil { return err @@ -196,6 +209,23 @@ func (d *Dashboard) ReconcileComponent(ctx context.Context, } } +func (d *Dashboard) Cleanup(cli client.Client, dscispec *dsciv1.DSCInitializationSpec) error { + shouldConfigureServiceMesh, err := deploy.ShouldConfigureServiceMesh(cli, dscispec) + if err != nil { + return err + } + + if shouldConfigureServiceMesh { + serviceMeshInitializer := feature.ComponentFeaturesHandler(d, dscispec, d.defineServiceMeshFeatures(dscispec)) + + if err := serviceMeshInitializer.Delete(); err != nil { + return err + } + } + + return nil +} + func (d *Dashboard) deployCRDsForPlatform(cli client.Client, owner metav1.Object, namespace string, platform deploy.Platform) error { componentName := ComponentName if platform == deploy.SelfManagedRhods || platform == deploy.ManagedRhods { @@ -214,7 +244,7 @@ func (d *Dashboard) applyRhodsSpecificConfigs(cli client.Client, owner metav1.Ob deploy.ManagedRhods: "dedicated-admins", }[platform] - if err := common.ReplaceStringsInFile(dashboardConfig, map[string]string{"": adminGroups}); err != nil { + if err := common.ReplaceInFile(dashboardConfig, map[string]string{"": adminGroups}); err != nil { return err } @@ -263,7 +293,7 @@ func (d *Dashboard) deployConsoleLink(cli client.Client, owner metav1.Object, na domainIndex := strings.Index(consoleRoute.Spec.Host, ".") consoleLinkDomain := consoleRoute.Spec.Host[domainIndex+1:] - err := common.ReplaceStringsInFile(pathConsoleLink, map[string]string{ + err := common.ReplaceInFile(pathConsoleLink, map[string]string{ "": "https://rhods-dashboard-" + namespace + "." + consoleLinkDomain, "": sectionTitle, }) diff --git a/components/dashboard/servicemesh_setup.go b/components/dashboard/servicemesh_setup.go new file mode 100644 index 00000000000..7e9a5b85c72 --- /dev/null +++ b/components/dashboard/servicemesh_setup.go @@ -0,0 +1,66 @@ +package dashboard + +import ( + "path" + + operatorv1 "github.com/openshift/api/operator/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + dsci "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature/servicemesh" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/gvr" +) + +func (d *Dashboard) configureServiceMesh(cli client.Client, owner metav1.Object, dscispec *dsci.DSCInitializationSpec) error { + shouldConfigureServiceMesh, err := deploy.ShouldConfigureServiceMesh(cli, dscispec) + if err != nil { + return err + } + + if shouldConfigureServiceMesh { + serviceMeshInitializer := feature.ComponentFeaturesHandler(d, dscispec, d.defineServiceMeshFeatures(dscispec)) + + if err := serviceMeshInitializer.Apply(); err != nil { + return err + } + + enabled := d.GetManagementState() == operatorv1.Managed + if err := deploy.DeployManifestsFromPath(cli, owner, PathODHProjectController, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { + return err + } + } + + return nil +} + +func (d *Dashboard) defineServiceMeshFeatures(dscispec *dsci.DSCInitializationSpec) feature.FeaturesProvider { + return func(handler *feature.FeaturesHandler) error { + createMeshResourcesErr := feature.CreateFeature("dashboard-create-service-mesh-routing-resources"). + For(handler). + Manifests( + path.Join(feature.ControlPlaneDir, "components", d.GetComponentName()), + ). + WithResources(servicemesh.EnabledInDashboard). + WithData( + servicemesh.DefaultValues, + servicemesh.ClusterDetails, + ). + PreConditions( + feature.WaitForResourceToBeCreated(dscispec.ApplicationsNamespace, gvr.ODHDashboardConfigGVR), + ). + PostConditions( + feature.WaitForPodsToBeReady(dscispec.ServiceMesh.ControlPlane.Namespace), + ). + OnDelete(servicemesh.DisabledInDashboard). + Load() + + if createMeshResourcesErr != nil { + return createMeshResourcesErr + } + + return nil + } +} diff --git a/components/workbenches/workbenches.go b/components/workbenches/workbenches.go index 43bc40c1e3b..5ca1cacde08 100644 --- a/components/workbenches/workbenches.go +++ b/components/workbenches/workbenches.go @@ -16,6 +16,7 @@ import ( "github.com/opendatahub-io/opendatahub-operator/v2/components" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature/servicemesh" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/monitoring" ) @@ -24,10 +25,11 @@ var ( DependentComponentName = "notebooks" // manifests for nbc in ODH and downstream + downstream use it for imageparams. notebookControllerPath = deploy.DefaultManifestPath + "/odh-notebook-controller/odh-notebook-controller/base" - // manifests for ODH nbc + downstream use it for imageparams. - kfnotebookControllerPath = deploy.DefaultManifestPath + "/odh-notebook-controller/kf-notebook-controller/overlays/openshift" - notebookImagesPath = deploy.DefaultManifestPath + "/notebooks/overlays/additional" - notebookImagesPathSupported = deploy.DefaultManifestPath + "/jupyterhub/notebook-images/overlays/additional" + // manifests for ODH nbc. + kfnotebookControllerPath = deploy.DefaultManifestPath + "/odh-notebook-controller/kf-notebook-controller/overlays/openshift" + kfnotebookControllerServiceMeshPath = deploy.DefaultManifestPath + "/odh-notebook-controller/kf-notebook-controller/overlays/service-mesh" + notebookImagesPath = deploy.DefaultManifestPath + "/notebooks/overlays/additional" + notebookImagesPathSupported = deploy.DefaultManifestPath + "/jupyterhub/notebook-images/overlays/additional" ) // Verifies that Workbench implements ComponentInterface. @@ -104,7 +106,6 @@ func (w *Workbenches) ReconcileComponent(ctx context.Context, cli client.Client, // Set default notebooks namespace // Create rhods-notebooks namespace in managed platforms enabled := w.GetManagementState() == operatorv1.Managed - monitoringEnabled := dscispec.Monitoring.ManagementState == operatorv1.Managed platform, err := deploy.GetPlatform(cli) if err != nil { return err @@ -120,20 +121,27 @@ func (w *Workbenches) ReconcileComponent(ctx context.Context, cli client.Client, } } if platform == deploy.SelfManagedRhods || platform == deploy.ManagedRhods { - _, err := cluster.CreateNamespace(cli, "rhods-notebooks", cluster.WithLabels(cluster.ODHGeneratedNamespaceLabel, "true")) - if err != nil { + if _, err := cluster.CreateNamespace(cli, "rhods-notebooks", cluster.WithLabels(cluster.ODHGeneratedNamespaceLabel, "true")); err != nil { // no need to log error as it was already logged in createOdhNamespace return err } } - // Update Default rolebinding - err = cluster.UpdatePodSecurityRolebinding(cli, dscispec.ApplicationsNamespace, "notebook-controller-service-account") - if err != nil { + if err := cluster.UpdatePodSecurityRolebinding(cli, dscispec.ApplicationsNamespace, "notebook-controller-service-account"); err != nil { + return err + } + } + + shouldConfigureServiceMesh, err := deploy.ShouldConfigureServiceMesh(cli, dscispec) + if err != nil { + return err + } + if shouldConfigureServiceMesh { + if err := servicemesh.OverwriteIstioGatewayVar(dscispec.ApplicationsNamespace, kfnotebookControllerServiceMeshPath); err != nil { return err } } - if err = deploy.DeployManifestsFromPath(cli, owner, notebookControllerPath, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { + if err := deploy.DeployManifestsFromPath(cli, owner, notebookControllerPath, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { return err } @@ -154,10 +162,14 @@ func (w *Workbenches) ReconcileComponent(ctx context.Context, cli client.Client, } var manifestsPath string - if platform == deploy.OpenDataHub || platform == "" { + if platform == deploy.OpenDataHub || platform == deploy.Unknown { + path := kfnotebookControllerPath + if shouldConfigureServiceMesh { + path = kfnotebookControllerServiceMeshPath + } // only for ODH after transit to kubeflow repo if err = deploy.DeployManifestsFromPath(cli, owner, - kfnotebookControllerPath, + path, dscispec.ApplicationsNamespace, ComponentName, enabled); err != nil { return err @@ -172,26 +184,32 @@ func (w *Workbenches) ReconcileComponent(ctx context.Context, cli client.Client, ComponentName, enabled); err != nil { return err } + // CloudService Monitoring handling if platform == deploy.ManagedRhods { - if enabled { - // first check if the service is up, so prometheus wont fire alerts when it is just startup - // only 1 replica set timeout to 1min - if err := monitoring.WaitForDeploymentAvailable(ctx, resConf, ComponentName, dscispec.ApplicationsNamespace, 10, 1); err != nil { - return fmt.Errorf("deployments for %s are not ready to server: %w", ComponentName, err) - } - fmt.Printf("deployments for %s are done, updating monitoring rules\n", ComponentName) - } - if err := w.UpdatePrometheusConfig(cli, enabled && monitoringEnabled, ComponentName); err != nil { - return err - } - if err = deploy.DeployManifestsFromPath(cli, owner, - filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), - dscispec.Monitoring.Namespace, - "prometheus", true); err != nil { - return err - } + return w.configureMonitoring(ctx, cli, resConf, owner, dscispec) } return nil } + +func (w *Workbenches) configureMonitoring(ctx context.Context, cli client.Client, resConf *rest.Config, owner metav1.Object, dscispec *dsci.DSCInitializationSpec) error { + enabled := w.GetManagementState() == operatorv1.Managed + monitoringEnabled := dscispec.Monitoring.ManagementState == operatorv1.Managed + if enabled { + // first check if the service is up, so prometheus wont fire alerts when it is just startup + // only 1 replica set timeout to 1min + if err := monitoring.WaitForDeploymentAvailable(ctx, resConf, ComponentName, dscispec.ApplicationsNamespace, 10, 1); err != nil { + return fmt.Errorf("deployments for %s are not ready to server: %w", ComponentName, err) + } + fmt.Printf("deployments for %s are done, updating monitoring rules\n", ComponentName) + } + if err := w.UpdatePrometheusConfig(cli, enabled && monitoringEnabled, ComponentName); err != nil { + return err + } + + return deploy.DeployManifestsFromPath(cli, owner, + filepath.Join(deploy.DefaultManifestPath, "monitoring", "prometheus", "apps"), + dscispec.Monitoring.Namespace, + "prometheus", true) +} diff --git a/config/crd/bases/dscinitialization.opendatahub.io_dscinitializations.yaml b/config/crd/bases/dscinitialization.opendatahub.io_dscinitializations.yaml index 6e701ec8895..e1561aab95b 100644 --- a/config/crd/bases/dscinitialization.opendatahub.io_dscinitializations.yaml +++ b/config/crd/bases/dscinitialization.opendatahub.io_dscinitializations.yaml @@ -85,14 +85,76 @@ spec: description: Configures Service Mesh as networking layer for Data Science Clusters components. The Service Mesh is a mandatory prerequisite for single model serving (KServe) and you should review this configuration - if you are planning to use KServe. For other components, it enhances - user experience; e.g. it provides unified authentication giving - a Single Sign On experience. + if you are planning to use KServe. properties: + auth: + description: Auth holds configuration of authentication and authorization + services used by Service Mesh in Opendatahub. + properties: + authorino: + description: Authorino holds configuration of Authorino service + used as external authorization provider. + properties: + audiences: + default: + - https://kubernetes.default.svc + description: Audiences is a list of the identifiers that + the resource server presented with the token identifies + as. Audience-aware token authenticators will verify + that the token was intended for at least one of the + audiences in this list. If no audiences are provided, + the audience will default to the audience of the Kubernetes + apiserver (kubernetes.default.svc). + items: + type: string + type: array + image: + default: quay.io/kuadrant/authorino:v0.16.0 + description: Image allows to define a custom container + image to be used when deploying Authorino's instance. + type: string + label: + default: authorino/topic=odh + description: Label narrows amount of AuthConfigs to process + by Authorino service. + type: string + name: + default: authorino-mesh-authz-provider + description: Name specifies how external authorization + provider should be called. + type: string + type: object + namespace: + default: auth-provider + description: Namespace where it is deployed. + type: string + type: object controlPlane: description: ControlPlane holds configuration of Service Mesh used by Opendatahub. properties: + certificate: + description: Certificate specifies configuration of the TLS + certificate securing communications within the mesh. + properties: + secretName: + description: SecretName specifies the name of the Kubernetes + Secret resource that contains a TLS certificate secure + HTTP communications for the KNative network. + type: string + type: + default: SelfSigned + description: 'Type specifies if the TLS certificate should + be generated automatically, or if the certificate is + provided by the user. Allowed values are: * SelfSigned: + A certificate is going to be generated using an own + private key. * Provided: Pre-existence of the TLS Secret + (see SecretName) with a valid certificate is assumed.' + enum: + - SelfSigned + - Provided + type: string + type: object metricsCollection: default: Istio description: MetricsCollection specifies if metrics from components diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index e2d0c9e42ea..b18337d3ab1 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -2,9 +2,9 @@ # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default resources: +- bases/features.opendatahub.io_featuretrackers.yaml - bases/dscinitialization.opendatahub.io_dscinitializations.yaml - bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml -- bases/features.opendatahub.io_featuretrackers.yaml #+kubebuilder:scaffold:crdkustomizeresource patches: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 1624ddca325..e7b9560d787 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -132,6 +132,12 @@ rules: - tokenreviews verbs: - create +- apiGroups: + - authorino.kuadrant.io + resources: + - authconfigs + verbs: + - '*' - apiGroups: - authorization.k8s.io resources: @@ -984,6 +990,12 @@ rules: - deletecollection - get - patch +- apiGroups: + - networking.istio.io + resources: + - envoyfilters + verbs: + - '*' - apiGroups: - networking.istio.io resources: @@ -1064,6 +1076,12 @@ rules: - patch - update - watch +- apiGroups: + - operator.authorino.kuadrant.io + resources: + - authorinos + verbs: + - '*' - apiGroups: - operator.knative.dev resources: @@ -1204,6 +1222,19 @@ rules: - patch - update - watch +- apiGroups: + - route.openshift.io + resources: + - routes/custom-host + verbs: + - create + - get +- apiGroups: + - security.istio.io + resources: + - authorizationpolicies + verbs: + - '*' - apiGroups: - security.openshift.io resources: diff --git a/config/samples/console_v1_odhquickstarts.yaml b/config/samples/console_v1_odhquickstarts.yaml new file mode 100644 index 00000000000..40a487c2ab6 --- /dev/null +++ b/config/samples/console_v1_odhquickstarts.yaml @@ -0,0 +1,20 @@ +apiVersion: console.openshift.io/v1 +kind: OdhQuickStart +metadata: + annotations: + internal.config.kubernetes.io/previousKinds: OdhQuickStart + internal.config.kubernetes.io/previousNames: create-jupyter-notebook + internal.config.kubernetes.io/previousNamespaces: default + opendatahub.io/categories: 'Getting started,Notebook environments' + name: create-jupyter-notebook-sample + namespace: opendatahub + labels: + app.kubernetes.io/part-of: odh-dashboard + app.kubernetes.io/name: odh-dashboard + app.kubernetes.io/instance: odh-dashboard-sample + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: odh-dashboard +spec: + durationMinutes: 5 + appName: jupyterhub + displayName: Creating a Jupyter notebook \ No newline at end of file diff --git a/config/samples/dashboard_v1_odhapplications.yaml b/config/samples/dashboard_v1_odhapplications.yaml new file mode 100644 index 00000000000..bd9a61a37b6 --- /dev/null +++ b/config/samples/dashboard_v1_odhapplications.yaml @@ -0,0 +1,20 @@ +apiVersion: dashboard.opendatahub.io/v1 +kind: OdhApplication +metadata: + name: jupyterhub-sample + namespace: opendatahub + labels: + app.kubernetes.io/part-of: odh-dashboard + app.kubernetes.io/name: odh-dashboard + app.kubernetes.io/instance: odh-dashboard-sample + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: odh-dashboard +spec: + getStartedLink: 'https://jupyterhub.readthedocs.io/en/stable/getting-started/index.html' + route: jupyterhub + displayName: JupyterHub + provider: Jupyter + docsLink: 'https://jupyter.org/hub' + quickStart: create-jupyter-notebook + getStartedMarkDown: >- + # MarkDown Description \ No newline at end of file diff --git a/config/samples/dashboard_v1_odhdocuments.yaml b/config/samples/dashboard_v1_odhdocuments.yaml new file mode 100644 index 00000000000..8a7307864af --- /dev/null +++ b/config/samples/dashboard_v1_odhdocuments.yaml @@ -0,0 +1,16 @@ +apiVersion: dashboard.opendatahub.io/v1 +kind: OdhDocument +metadata: + name: jupyterhub-view-installed-packages-sample + namespace: opendatahub + labels: + app.kubernetes.io/part-of: odh-dashboard + app.kubernetes.io/name: odh-dashboard + app.kubernetes.io/instance: odh-dashboard-sample + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: odh-dashboard +spec: + appName: jupyter + durationMinutes: 15 + type: how-to + url: "https://url.sample.com" \ No newline at end of file diff --git a/config/samples/opendatahub_v1alpha1_odhdashboardconfigs.yaml b/config/samples/opendatahub_v1alpha1_odhdashboardconfigs.yaml new file mode 100644 index 00000000000..1294f28ebb2 --- /dev/null +++ b/config/samples/opendatahub_v1alpha1_odhdashboardconfigs.yaml @@ -0,0 +1,21 @@ +apiVersion: opendatahub.io/v1alpha +kind: OdhDashboardConfig +metadata: + labels: + opendatahub.io/dashboard: 'true' + app.kubernetes.io/part-of: odh-dashboard + app.kubernetes.io/name: odh-dashboard + app.kubernetes.io/instance: odh-dashboard-sample + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: odh-dashboard + name: odh-dashboard-config-sample + namespace: opendatahub +spec: + dashboardConfig: + groupsConfig: + adminGroups: odh-admins + allowedGroups: 'system:authenticated' + notebookController: + enabled: true + templateOrder: [] + templateDisablement: [] diff --git a/controllers/datasciencecluster/kubebuilder_rbac.go b/controllers/datasciencecluster/kubebuilder_rbac.go index 62a56189d51..4c62b1e7783 100644 --- a/controllers/datasciencecluster/kubebuilder_rbac.go +++ b/controllers/datasciencecluster/kubebuilder_rbac.go @@ -4,14 +4,25 @@ package datasciencecluster //+kubebuilder:rbac:groups="datasciencecluster.opendatahub.io",resources=datascienceclusters/finalizers,verbs=update;patch //+kubebuilder:rbac:groups="datasciencecluster.opendatahub.io",resources=datascienceclusters,verbs=get;list;watch;create;update;patch;delete -/* Service Mesh prerequisite */ -// +kubebuilder:rbac:groups="maistra.io",resources=servicemeshcontrolplanes,verbs=create;get;list;patch;update;use;watch - /* Serverless prerequisite */ // +kubebuilder:rbac:groups="networking.istio.io",resources=gateways,verbs=* // +kubebuilder:rbac:groups="operator.knative.dev",resources=knativeservings,verbs=* // +kubebuilder:rbac:groups="config.openshift.io",resources=ingresses,verbs=get +/* Service Mesh Integration */ +// +kubebuilder:rbac:groups="maistra.io",resources=servicemeshcontrolplanes,verbs=create;get;list;patch;update;use;watch +// +kubebuilder:rbac:groups="maistra.io",resources=servicemeshmemberrolls,verbs=create;get;list;patch;update;use;watch +// +kubebuilder:rbac:groups="maistra.io",resources=servicemeshmembers,verbs=create;get;list;patch;update;use;watch +// +kubebuilder:rbac:groups="maistra.io",resources=servicemeshmembers/finalizers,verbs=create;get;list;patch;update;use;watch +// +kubebuilder:rbac:groups="networking.istio.io",resources=virtualservices/status,verbs=update;patch;delete +// +kubebuilder:rbac:groups="networking.istio.io",resources=virtualservices/finalizers,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="networking.istio.io",resources=virtualservices,verbs=* +// +kubebuilder:rbac:groups="networking.istio.io",resources=gateways,verbs=* +// +kubebuilder:rbac:groups="networking.istio.io",resources=envoyfilters,verbs=* +// +kubebuilder:rbac:groups="security.istio.io",resources=authorizationpolicies,verbs=* +// +kubebuilder:rbac:groups="authorino.kuadrant.io",resources=authconfigs,verbs=* +// +kubebuilder:rbac:groups="operator.authorino.kuadrant.io",resources=authorinos,verbs=* + /* This is for DSP */ //+kubebuilder:rbac:groups="datasciencepipelinesapplications.opendatahub.io",resources=datasciencepipelinesapplications/status,verbs=update;patch;get //+kubebuilder:rbac:groups="datasciencepipelinesapplications.opendatahub.io",resources=datasciencepipelinesapplications/finalizers,verbs=update;patch @@ -70,7 +81,8 @@ package datasciencecluster // +kubebuilder:rbac:groups="security.openshift.io",resources=securitycontextconstraints,verbs=*,resourceNames=anyuid // +kubebuilder:rbac:groups="security.openshift.io",resources=securitycontextconstraints,verbs=* -// +kubebuilder:rbac:groups="route.openshift.io",resources=routes,verbs=get;list;watch;create;delete;update;patch +// +kubebuilder:rbac:groups="route.openshift.io",resources=routes,verbs=create;delete;list;update;watch;patch;get +// +kubebuilder:rbac:groups="route.openshift.io",resources=routes/custom-host,verbs=create;get // +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=* @@ -93,10 +105,6 @@ package datasciencecluster // +kubebuilder:rbac:groups="networking.k8s.io",resources=networkpolicies,verbs=get;create;list;watch;delete;update;patch // +kubebuilder:rbac:groups="networking.k8s.io",resources=ingresses,verbs=create;delete;list;update;watch;patch;get -// +kubebuilder:rbac:groups="networking.istio.io",resources=virtualservices/status,verbs=update;patch;delete -// +kubebuilder:rbac:groups="networking.istio.io",resources=virtualservices/finalizers,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups="networking.istio.io",resources=virtualservices,verbs=* - // +kubebuilder:rbac:groups="monitoring.coreos.com",resources=servicemonitors,verbs=get;create;delete;update;watch;list;patch;deletecollection // +kubebuilder:rbac:groups="monitoring.coreos.com",resources=podmonitors,verbs=get;create;delete;update;watch;list;patch // +kubebuilder:rbac:groups="monitoring.coreos.com",resources=prometheusrules,verbs=get;create;patch;delete;deletecollection @@ -248,11 +256,6 @@ package datasciencecluster // +kubebuilder:rbac:groups="*",resources=customresourcedefinitions,verbs=get;list;watch -// +kubebuilder:rbac:groups="maistra.io",resources=servicemeshcontrolplanes,verbs=create;get;list;patch;update;use;watch -// +kubebuilder:rbac:groups="maistra.io",resources=servicemeshmemberrolls,verbs=create;get;list;patch;update;use;watch -// +kubebuilder:rbac:groups="maistra.io",resources=servicemeshmembers,verbs=create;get;list;patch;update;use;watch -// +kubebuilder:rbac:groups="maistra.io",resources=servicemeshmembers/finalizers,verbs=create;get;list;patch;update;use;watch - /* Only for RHODS */ // +kubebuilder:rbac:groups="user.openshift.io",resources=groups,verbs=get;create;list;watch;patch;delete // +kubebuilder:rbac:groups="console.openshift.io",resources=consolelinks,verbs=create;get;patch;delete diff --git a/controllers/dscinitialization/dscinitialization_controller.go b/controllers/dscinitialization/dscinitialization_controller.go index 07bebc44776..0b8f40b5a1b 100644 --- a/controllers/dscinitialization/dscinitialization_controller.go +++ b/controllers/dscinitialization/dscinitialization_controller.go @@ -125,8 +125,9 @@ func (r *DSCInitializationReconciler) Reconcile(ctx context.Context, req ctrl.Re } else { r.Log.Info("Finalization DSCInitialization start deleting instance", "name", instance.Name, "finalizer", finalizerName) if err := r.removeServiceMesh(instance); err != nil { - return reconcile.Result{}, err + return ctrl.Result{}, err } + if controllerutil.ContainsFinalizer(instance, finalizerName) { controllerutil.RemoveFinalizer(instance, finalizerName) if err := r.Update(ctx, instance); err != nil { @@ -157,8 +158,7 @@ func (r *DSCInitializationReconciler) Reconcile(ctx context.Context, req ctrl.Re // Check namespace namespace := instance.Spec.ApplicationsNamespace - err = r.createOdhNamespace(ctx, instance, namespace) - if err != nil { + if err := r.createOdhNamespace(ctx, instance, namespace); err != nil { // no need to log error as it was already logged in createOdhNamespace return reconcile.Result{}, err } diff --git a/controllers/dscinitialization/dscinitialization_test.go b/controllers/dscinitialization/dscinitialization_test.go index 5aca29fe6fe..adc7118b87a 100644 --- a/controllers/dscinitialization/dscinitialization_test.go +++ b/controllers/dscinitialization/dscinitialization_test.go @@ -82,7 +82,7 @@ var _ = Describe("DataScienceCluster initialization", func() { expectedRoleRef := authv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", - Name: "system:openshift:scc:anyuid", + Name: "system:openshift:scc:restricted-v2", } Expect(foundRoleBinding.Name).To(Equal(applicationNamespace)) Expect(foundRoleBinding.Namespace).To(Equal(applicationNamespace)) diff --git a/controllers/dscinitialization/monitoring.go b/controllers/dscinitialization/monitoring.go index 530add5470b..9e15437af5e 100644 --- a/controllers/dscinitialization/monitoring.go +++ b/controllers/dscinitialization/monitoring.go @@ -17,6 +17,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" dsci "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/common" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/deploy" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/upgrade" @@ -77,7 +78,7 @@ func (r *DSCInitializationReconciler) configureManagedMonitoring(ctx context.Con } if initial == "init" { - err := common.UpdatePodSecurityRolebinding(r.Client, []string{"redhat-ods-monitoring"}, dscInit.Spec.Monitoring.Namespace) + err := cluster.UpdatePodSecurityRolebinding(r.Client, dscInit.Spec.Monitoring.Namespace, "redhat-ods-monitoring") if err != nil { return fmt.Errorf("error to update monitoring security rolebinding: %w", err) } @@ -114,7 +115,7 @@ func configureAlertManager(ctx context.Context, dsciInit *dsci.DSCInitialization // Replace variables in alertmanager configmap for the initial time // TODO: Following variables can later be exposed by the API - err = common.ReplaceStringsInFile(filepath.Join(alertManagerPath, "alertmanager-configs.yaml"), + err = common.ReplaceInFile(filepath.Join(alertManagerPath, "alertmanager-configs.yaml"), map[string]string{ "": string(deadmansnitchSecret.Data["SNITCH_URL"]), "": string(pagerDutySecret.Data["PAGERDUTY_KEY"]), @@ -136,7 +137,7 @@ func configureAlertManager(ctx context.Context, dsciInit *dsci.DSCInitialization } if strings.Contains(consolelinkDomain, "devshift.org") { r.Log.Info("inject alertmanage-configs.yaml for dev mode1") - err = common.ReplaceStringsInFile(filepath.Join(alertManagerPath, "alertmanager-configs.yaml"), + err = common.ReplaceInFile(filepath.Join(alertManagerPath, "alertmanager-configs.yaml"), map[string]string{ "@devshift.net": "@rhmw.io", }) @@ -147,7 +148,7 @@ func configureAlertManager(ctx context.Context, dsciInit *dsci.DSCInitialization } if strings.Contains(consolelinkDomain, "aisrhods") { r.Log.Info("inject alertmanage-configs.yaml for dev mode2") - err = common.ReplaceStringsInFile(filepath.Join(alertManagerPath, "alertmanager-configs.yaml"), + err = common.ReplaceInFile(filepath.Join(alertManagerPath, "alertmanager-configs.yaml"), map[string]string{ "receiver: PagerDuty": "receiver: alerts-sink", }) @@ -198,7 +199,7 @@ func configureAlertManager(ctx context.Context, dsciInit *dsci.DSCInitialization func configurePrometheus(ctx context.Context, dsciInit *dsci.DSCInitialization, r *DSCInitializationReconciler) error { // Update rolebinding-viewer - err := common.ReplaceStringsInFile(filepath.Join(prometheusManifestsPath, "prometheus-rolebinding-viewer.yaml"), + err := common.ReplaceInFile(filepath.Join(prometheusManifestsPath, "prometheus-rolebinding-viewer.yaml"), map[string]string{ "": dsciInit.Spec.Monitoring.Namespace, }) @@ -211,7 +212,7 @@ func configurePrometheus(ctx context.Context, dsciInit *dsci.DSCInitialization, if err != nil { return fmt.Errorf("error getting console route URL : %w", err) } - err = common.ReplaceStringsInFile(filepath.Join(prometheusConfigPath, "prometheus-configs.yaml"), + err = common.ReplaceInFile(filepath.Join(prometheusConfigPath, "prometheus-configs.yaml"), map[string]string{ "": dsciInit.Spec.ApplicationsNamespace, "": dsciInit.Spec.Monitoring.Namespace, @@ -287,7 +288,7 @@ func configurePrometheus(ctx context.Context, dsciInit *dsci.DSCInitialization, // r.Log.Info("Success: read alertmanager data from alertmanage.yml") // Update prometheus deployment with alertmanager and prometheus data - err = common.ReplaceStringsInFile(filepath.Join(prometheusManifestsPath, "prometheus-deployment.yaml"), + err = common.ReplaceInFile(filepath.Join(prometheusManifestsPath, "prometheus-deployment.yaml"), map[string]string{ "": alertmanagerRoute.Spec.Host, }) @@ -446,7 +447,7 @@ func (r *DSCInitializationReconciler) configureCommonMonitoring(dsciInit *dsci.D } // configure monitoring base monitoringBasePath := filepath.Join(deploy.DefaultManifestPath, "monitoring", "base") - err := common.ReplaceStringsInFile(filepath.Join(monitoringBasePath, "rhods-servicemonitor.yaml"), + err := common.ReplaceInFile(filepath.Join(monitoringBasePath, "rhods-servicemonitor.yaml"), map[string]string{ "": dsciInit.Spec.Monitoring.Namespace, }) diff --git a/controllers/dscinitialization/servicemesh_setup.go b/controllers/dscinitialization/servicemesh_setup.go index 7d67bf5205e..b5d9d2592de 100644 --- a/controllers/dscinitialization/servicemesh_setup.go +++ b/controllers/dscinitialization/servicemesh_setup.go @@ -19,6 +19,7 @@ func (r *DSCInitializationReconciler) configureServiceMesh(instance *dsciv1.DSCI if err := serviceMeshFeatures.Apply(); err != nil { r.Log.Error(err, "failed applying service mesh resources") r.Recorder.Eventf(instance, corev1.EventTypeWarning, "DSCInitializationReconcileError", "failed applying service mesh resources") + return err } case operatorv1.Unmanaged: @@ -56,7 +57,7 @@ func configureServiceMeshFeatures() feature.FeaturesProvider { smcpCreationErr := feature.CreateFeature("mesh-control-plane-creation"). For(handler). Manifests( - path.Join(feature.ServiceMeshDir, "base", "create-smcp.tmpl"), + path.Join(feature.ControlPlaneDir, "base", "create-control-plane.tmpl"), ). PreConditions( servicemesh.EnsureServiceMeshOperatorInstalled, @@ -86,6 +87,115 @@ func configureServiceMeshFeatures() feature.FeaturesProvider { } } + oauthErr := feature.CreateFeature("service-mesh-control-plane-configure-oauth"). + For(handler). + Manifests( + path.Join(feature.ControlPlaneDir, "base"), + path.Join(feature.ControlPlaneDir, "oauth"), + path.Join(feature.ControlPlaneDir, "filters"), + ). + WithResources( + servicemesh.DefaultValues, + servicemesh.SelfSignedCertificate, + servicemesh.EnvoyOAuthSecrets, + ). + WithData(servicemesh.ClusterDetails, servicemesh.OAuthConfig). + PreConditions( + servicemesh.EnsureServiceMeshInstalled, + ). + PostConditions( + feature.WaitForPodsToBeReady(serviceMeshSpec.ControlPlane.Namespace), + ). + OnDelete( + servicemesh.RemoveOAuthClient, + servicemesh.RemoveTokenVolumes, + ).Load() + + if oauthErr != nil { + return oauthErr + } + + cfMapsErr := feature.CreateFeature("shared-config-maps"). + For(handler). + WithResources(servicemesh.ConfigMaps). + Load() + + if cfMapsErr != nil { + return cfMapsErr + } + + // TODO rethink + enrollAppNsErr := feature.CreateFeature("app-add-namespace-to-service-mesh"). + For(handler). + Manifests( + path.Join(feature.ControlPlaneDir, "smm.tmpl"), + path.Join(feature.ControlPlaneDir, "namespace.patch.tmpl"), + ). + WithData(servicemesh.ClusterDetails). + Load() + + if enrollAppNsErr != nil { + return enrollAppNsErr + } + + // TODO make separate deployment + gatewayRouteErr := feature.CreateFeature("service-mesh-create-gateway-route"). + For(handler). + Manifests( + path.Join(feature.ControlPlaneDir, "routing"), + ). + WithData(servicemesh.ClusterDetails). + PostConditions( + feature.WaitForPodsToBeReady(serviceMeshSpec.ControlPlane.Namespace), + ). + Load() + + if gatewayRouteErr != nil { + return gatewayRouteErr + } + + dataScienceProjectsErr := feature.CreateFeature("app-migrate-data-science-projects"). + For(handler). + WithResources(servicemesh.MigratedDataScienceProjects). + Load() + + if dataScienceProjectsErr != nil { + return dataScienceProjectsErr + } + + extAuthzErr := feature.CreateFeature("service-mesh-control-plane-setup-external-authorization"). + For(handler). + Manifests( + path.Join(feature.AuthDir, "auth-smm.tmpl"), + path.Join(feature.AuthDir, "base"), + path.Join(feature.AuthDir, "rbac"), + path.Join(feature.AuthDir, "mesh-authz-ext-provider.patch.tmpl"), + ). + WithData(servicemesh.ClusterDetails). + PreConditions( + feature.EnsureCRDIsInstalled("authconfigs.authorino.kuadrant.io"), + servicemesh.EnsureServiceMeshInstalled, + feature.CreateNamespaceIfNotExists(serviceMeshSpec.Auth.Namespace), + ). + PostConditions( + feature.WaitForPodsToBeReady(serviceMeshSpec.ControlPlane.Namespace), + feature.WaitForPodsToBeReady(serviceMeshSpec.Auth.Namespace), + func(f *feature.Feature) error { + // We do not have the control over deployment resource creation. + // It is created by Authorino operator using Authorino CR + // + // To make it part of Service Mesh we have to patch it with injection + // enabled instead, otherwise it will not have proxy pod injected. + return f.ApplyManifest(path.Join(feature.AuthDir, "deployment.injection.patch.tmpl")) + }, + ). + OnDelete(servicemesh.RemoveExtensionProvider). + Load() + + if extAuthzErr != nil { + return extAuthzErr + } + return nil } } diff --git a/controllers/dscinitialization/utils.go b/controllers/dscinitialization/utils.go index fc87113204e..8b0520df72d 100644 --- a/controllers/dscinitialization/utils.go +++ b/controllers/dscinitialization/utils.go @@ -185,7 +185,7 @@ func (r *DSCInitializationReconciler) createDefaultRoleBinding(ctx context.Conte RoleRef: authv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", - Name: "system:openshift:scc:anyuid", + Name: "system:openshift:scc:restricted-v2", }, } diff --git a/docs/SERVICE-MESH.md b/docs/SERVICE-MESH.md new file mode 100644 index 00000000000..60824c33459 --- /dev/null +++ b/docs/SERVICE-MESH.md @@ -0,0 +1,242 @@ +# Open Data Hub (ODH) Installation Guide with OpenShift Service Mesh (OSSM) + +This guide will walk you through the installation of Open Data Hub with OpenShift Service Mesh. + +## Prerequisites + +* OpenShift cluster +* Command Line Interface (CLI) tools + * `kubectl` + * `operator-sdk` v1.24.1 (until operator changes are merged - to build new bundles) + +* Pre-installed operators + * Openshift Service Mesh + * Authorino + * Open Data Hub + +* Service Mesh Control Plane configured + +### Check Installed Operators + +You can use the following command to verify that all required operators are installed: + +```sh +kubectl get operators | awk -v RS= '/servicemesh/ && /opendatahub/ && /authorino/ {exit 0} {exit 1}' || echo "Please install all required operators." +``` + +#### Install Required Operators + +The `createSubscription` function can be used to simplify the installation of required operators: + +```sh +createSubscription() { + local name=$1 + local source=${2:-"redhat-operators"} + local channel=${3:-"stable"} + + echo "Create Subscription resource for $name" + eval "kubectl apply -f - < **Warning** +> +> You may need to manually update the installation of the Authorino operator via the Installed Operators tab in the OpenShift Console. + + +> **Warning** +> +> Please ensure that the Service Mesh Control Plane is properly configured as we apply patches to it. It is assumed that the installation has already been done. + + +For example, the following commands configure a slimmed-down profile: + +```sh +kubectl create ns istio-system +kubectl apply -n istio-system -f -< dsci.ign.yaml +apiVersion: dscinitialization.opendatahub.io/v1 +kind: DSCInitialization +metadata: + name: default +spec: + applicationsNamespace: opendatahub + monitoring: + managementState: Managed + namespace: opendatahub + serviceMesh: + managementState: Managed # (1) + mesh: + name: minimal # (2) + certificate: + generate: true # (3) +EOF +``` + +* **(1)**: setting this value will enable Service Mesh support for Open Data Hub +* **(2)**: name of Service Mesh Control Plane (defaults to `basic`) +* **(3)**: instructs operator to generate self-signed certificate + +This will instruct the DSCI controller to perform following steps: + +- Deploys an instance of Authorino controller which will handle all `AuthConfig`s for ODH (using certain label) +- Registers Authorino as external authorization provider for Istio +- Configure Envoy filters to handle OAuth2 flow for Dashboard +- Create Gateway route for Dashboard +- Create necessary Istio resources such as Virtual Services, Gateways and Policies + +Next, create DataScienceCluster with managed `Dashboard` and `Workbenches` components: + +```sh +apiVersion: datasciencecluster.opendatahub.io/v1 +kind: DataScienceCluster +metadata: + name: default +spec: + components: + dashboard: + managementState: "Managed" + workbenches: + managementState: "Managed" +``` +` +> **Warning** +> +> Other components are not supported yet. + +Go to the Open Data Hub dashboard in the browser: + +```sh +export ODH_ROUTE=$(kubectl get route --all-namespaces -l maistra.io/gateway-name=odh-gateway -o yaml | yq '.items[].spec.host') + +xdg-open https://$ODH_ROUTE > /dev/null 2>&1 & +``` + +## Troubleshooting + +### Audience-aware tokens + +Audience-aware token authenticators will verify that the token was intended for at least one of the audiences in this list. This can be crucial for environments such as ROSA. If no audiences are provided, the audience will default to the audience of the Kubernetes apiserver (`kubernetes.default.svc`). In order to change it, you should first know the token audience of your `serviceaccount`. + +```shell +TOKEN=YOUR_USER_TOKEN +ODH_NS=opendatahub +kubectl create -o jsonpath='{.status.audiences[0]}' -f -< get_client=client check failed, client_id=${ODH_NS}-oauth2-client`)`, check if the token is the same everywhere by comparing the output of the following commands: + + +```sh +kubectl get oauthclient.oauth.openshift.io ${ODH_NS}-oauth2-client +kubectl exec $(kubectl get pods -n istio-system -l app=istio-ingressgateway -o jsonpath='{.items[*].metadata.name}') -n istio-system -c istio-proxy -- cat /etc/istio/${ODH_NS}-oauth2-tokens/token-secret.yaml +kubectl get secret ${ODH_NS}-oauth2-tokens -n istio-system -o yaml +``` +To read the actual value of secrets you could use a [`kubectl` plugin](https://github.com/elsesiy/kubectl-view-secret) instead. Then the last line would look as follows `kubectl view-secret ${ODH_NS}-oauth2-tokens -n istio-system -a`. + +The `i`stio-ingressgateway` pod might be out of sync (and so `EnvoyFilter` responsible for OAuth2 flow). Check its logs and consider restarting it: + +```sh +kubectl rollout restart deployment -n istio-system istio-ingressgateway +``` diff --git a/get_all_manifests.sh b/get_all_manifests.sh index 00df995a331..fd8eb894b8a 100755 --- a/get_all_manifests.sh +++ b/get_all_manifests.sh @@ -17,6 +17,7 @@ declare -A COMPONENT_MANIFESTS=( ["trustyai"]="trustyai-explainability:trustyai-service-operator:release/1.10.2:config:trustyai-service-operator" ["model-mesh"]="opendatahub-io:modelmesh-serving:release-0.11.0:config:model-mesh" ["odh-model-controller"]="opendatahub-io:odh-model-controller:release-0.11.0:config:odh-model-controller" + ["odh-project-controller"]="maistra:odh-project-controller:main:config:odh-project-controller" ["kserve"]="opendatahub-io:kserve:release-v0.11.0:config:kserve" ["modelregistry"]="opendatahub-io:model-registry-operator:main:config:model-registry-operator" ) @@ -66,5 +67,4 @@ for key in "${!COMPONENT_MANIFESTS[@]}"; do mkdir -p ./odh-manifests/${target_path} cp -rf ${repo_dir}/${source_path}/* ./odh-manifests/${target_path} - done diff --git a/go.mod b/go.mod index cc31b21f696..3d727d5c876 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/opendatahub-io/opendatahub-operator/v2 go 1.19 require ( + github.com/bitly/go-simplejson v0.5.1 github.com/ghodss/yaml v1.0.0 github.com/go-logr/logr v1.2.4 github.com/hashicorp/go-multierror v1.1.1 diff --git a/go.sum b/go.sum index e571c958055..21194e38387 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= +github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= diff --git a/infrastructure/v1/servicemesh_types.go b/infrastructure/v1/servicemesh_types.go index 9c093d0c663..37427d8d527 100644 --- a/infrastructure/v1/servicemesh_types.go +++ b/infrastructure/v1/servicemesh_types.go @@ -9,6 +9,9 @@ type ServiceMeshSpec struct { ManagementState operatorv1.ManagementState `json:"managementState,omitempty"` // ControlPlane holds configuration of Service Mesh used by Opendatahub. ControlPlane ControlPlaneSpec `json:"controlPlane,omitempty"` + // Auth holds configuration of authentication and authorization services + // used by Service Mesh in Opendatahub. + Auth AuthSpec `json:"auth,omitempty"` } type ControlPlaneSpec struct { @@ -25,6 +28,8 @@ type ControlPlaneSpec struct { // +kubebuilder:validation:Enum=Istio;None // +kubebuilder:default=Istio MetricsCollection string `json:"metricsCollection,omitempty"` + // Certificate specifies configuration of the TLS certificate securing communications within the mesh. + Certificate CertificateSpec `json:"certificate,omitempty"` } // IngressGatewaySpec represents the configuration of the Ingress Gateways. @@ -38,3 +43,30 @@ type IngressGatewaySpec struct { // the for Ingress Gateway. Certificate CertificateSpec `json:"certificate,omitempty"` } + +type AuthSpec struct { + // Namespace where it is deployed. + // +kubebuilder:default=auth-provider + Namespace string `json:"namespace,omitempty"` + // Authorino holds configuration of Authorino service used as external authorization provider. + Authorino AuthorinoSpec `json:"authorino,omitempty"` +} + +type AuthorinoSpec struct { + // Name specifies how external authorization provider should be called. + // +kubebuilder:default=authorino-mesh-authz-provider + Name string `json:"name,omitempty"` + // Audiences is a list of the identifiers that the resource server presented + // with the token identifies as. Audience-aware token authenticators will verify + // that the token was intended for at least one of the audiences in this list. + // If no audiences are provided, the audience will default to the audience of the + // Kubernetes apiserver (kubernetes.default.svc). + // +kubebuilder:default={"https://kubernetes.default.svc"} + Audiences []string `json:"audiences,omitempty"` + // Label narrows amount of AuthConfigs to process by Authorino service. + // +kubebuilder:default=authorino/topic=odh + Label string `json:"label,omitempty"` + // Image allows to define a custom container image to be used when deploying Authorino's instance. + // +kubebuilder:default="quay.io/kuadrant/authorino:v0.16.0" + Image string `json:"image,omitempty"` +} diff --git a/infrastructure/v1/zz_generated.deepcopy.go b/infrastructure/v1/zz_generated.deepcopy.go index efc062e396b..1edc3c9de48 100644 --- a/infrastructure/v1/zz_generated.deepcopy.go +++ b/infrastructure/v1/zz_generated.deepcopy.go @@ -23,6 +23,42 @@ package v1 import () +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthSpec) DeepCopyInto(out *AuthSpec) { + *out = *in + in.Authorino.DeepCopyInto(&out.Authorino) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthSpec. +func (in *AuthSpec) DeepCopy() *AuthSpec { + if in == nil { + return nil + } + out := new(AuthSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorinoSpec) DeepCopyInto(out *AuthorinoSpec) { + *out = *in + if in.Audiences != nil { + in, out := &in.Audiences, &out.Audiences + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorinoSpec. +func (in *AuthorinoSpec) DeepCopy() *AuthorinoSpec { + if in == nil { + return nil + } + out := new(AuthorinoSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) { *out = *in @@ -41,6 +77,7 @@ func (in *CertificateSpec) DeepCopy() *CertificateSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ControlPlaneSpec) DeepCopyInto(out *ControlPlaneSpec) { *out = *in + out.Certificate = in.Certificate } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlaneSpec. @@ -73,6 +110,7 @@ func (in *IngressGatewaySpec) DeepCopy() *IngressGatewaySpec { func (in *ServiceMeshSpec) DeepCopyInto(out *ServiceMeshSpec) { *out = *in out.ControlPlane = in.ControlPlane + in.Auth.DeepCopyInto(&out.Auth) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceMeshSpec. diff --git a/pkg/cluster/cluster_config.go b/pkg/cluster/cluster_config.go new file mode 100644 index 00000000000..a65c9b23cce --- /dev/null +++ b/pkg/cluster/cluster_config.go @@ -0,0 +1,131 @@ +package cluster + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strconv" + "strings" + + "github.com/bitly/go-simplejson" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client/config" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/gvr" +) + +// +kubebuilder:rbac:groups="config.openshift.io",resources=ingresses,verbs=get + +func GetDomain(dynamicClient dynamic.Interface) (string, error) { + cluster, err := dynamicClient.Resource(gvr.OpenshiftIngress).Get(context.TODO(), "cluster", metav1.GetOptions{}) + if err != nil { + panic(err.Error()) + } + + domain, found, err := unstructured.NestedString(cluster.Object, "spec", "domain") + if !found { + return "", errors.New("spec.domain not found") + } + return domain, err +} + +func GetOAuthServerDetails() (*simplejson.Json, error) { + response, err := request(http.MethodGet, "/.well-known/oauth-authorization-server") + if err != nil { + return nil, err + } + + return simplejson.NewJson(response) +} + +func request(method string, url string) ([]byte, error) { + restCfg, err := config.GetConfig() + if err != nil { + return nil, err + } + + client, err := rest.HTTPClientFor(restCfg) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP client, error: %w", err) + } + + request, err := http.NewRequestWithContext(context.Background(), method, getKubeAPIURLWithPath(url).String(), nil) + if err != nil { + return nil, fmt.Errorf("failed to get api endpoint %s, error: %w", url, err) + } + + response, err := client.Do(request) + if err != nil { + return nil, fmt.Errorf("failed to call api endpoint %s, error: %w", url, err) + } + + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + if err != nil || response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to get api endpoint %s, error: %w", url, err) + } + + return body, nil +} + +func getKubernetesServiceHost() string { + if host := os.Getenv("KUBERNETES_SERVICE_HOST"); len(host) > 0 { + // assume IPv6 if host contains colons + if strings.IndexByte(host, ':') != -1 { + host = "[" + host + "]" + } + + return host + } + + return "kubernetes.default.svc" +} + +func getKubeAPIURLWithPath(path string) *url.URL { + return &url.URL{ + Scheme: "https", + Host: getKubernetesServiceHost(), + Path: path, + } +} + +// ExtractHostNameAndPort strips given URL in string from http(s):// prefix and subsequent path, +// returning host name and port if defined (otherwise defaults to 443). +// +// This is useful when getting value from http headers (such as origin). +// If given string does not start with http(s) prefix it will be returned as is. +func ExtractHostNameAndPort(s string) (string, string, error) { + u, err := url.Parse(s) + if err != nil { + return "", "", err + } + + if u.Scheme != "http" && u.Scheme != "https" { + return s, "", nil + } + + hostname := u.Hostname() + + port := "443" // default for https + if u.Scheme == "http" { + port = "80" + } + + if u.Port() != "" { + port = u.Port() + _, err := strconv.Atoi(port) + if err != nil { + return "", "", errors.New("invalid port number: " + port) + } + } + + return hostname, port, nil +} diff --git a/pkg/cluster/cluster_suite_test.go b/pkg/cluster/cluster_suite_test.go new file mode 100644 index 00000000000..d666ea1ad5d --- /dev/null +++ b/pkg/cluster/cluster_suite_test.go @@ -0,0 +1,14 @@ +package cluster_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestClusterHelpers(t *testing.T) { + RegisterFailHandler(Fail) + // for integration tests see tests/integration directory + RunSpecs(t, "Cluster helper funcs unit tests") +} diff --git a/pkg/cluster/hostname_and_port_extraction_unit_test.go b/pkg/cluster/hostname_and_port_extraction_unit_test.go new file mode 100644 index 00000000000..3eccb388394 --- /dev/null +++ b/pkg/cluster/hostname_and_port_extraction_unit_test.go @@ -0,0 +1,47 @@ +package cluster_test + +import ( + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("extracting hostname and port from URL", func() { + + It("should extract hostname and port for HTTP URL", func() { + hostname, port, err := cluster.ExtractHostNameAndPort("http://opendatahub.io:8080/path") + Expect(err).ToNot(HaveOccurred()) + Expect(hostname).To(Equal("opendatahub.io")) + Expect(port).To(Equal("8080")) + }) + + It("should return original URL if it does not start with http(s) but with other valid protocol", func() { + originalURL := "gopher://opendatahub.io" + hostname, port, err := cluster.ExtractHostNameAndPort(originalURL) + Expect(err).ToNot(HaveOccurred()) + Expect(hostname).To(Equal(originalURL)) + Expect(port).To(Equal("")) + }) + + It("should handle invalid URLs by returning corresponding error", func() { + invalidURL := ":opendatahub.io" + _, _, err := cluster.ExtractHostNameAndPort(invalidURL) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(ContainSubstring("missing protocol scheme"))) + }) + + It("should handle URLs without port and default to 443 for HTTPS", func() { + hostname, port, err := cluster.ExtractHostNameAndPort("https://opendatahub.io") + Expect(err).ToNot(HaveOccurred()) + Expect(hostname).To(Equal("opendatahub.io")) + Expect(port).To(Equal("443")) + }) + + It("should handle URLs without port and default to 80 for HTTP", func() { + hostname, port, err := cluster.ExtractHostNameAndPort("http://opendatahub.io") + Expect(err).ToNot(HaveOccurred()) + Expect(hostname).To(Equal("opendatahub.io")) + Expect(port).To(Equal("80")) + }) +}) diff --git a/pkg/common/common.go b/pkg/common/common.go index dd287df3f3f..3a278625eb1 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -27,57 +27,25 @@ import ( "strings" routev1 "github.com/openshift/api/route/v1" - authv1 "k8s.io/api/rbac/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) -// UpdatePodSecurityRolebinding update default rolebinding which is created in namespace by manifests -// being used by different components and sre monitoring. -func UpdatePodSecurityRolebinding(cli client.Client, serviceAccountsList []string, namespace string) error { - foundRoleBinding := &authv1.RoleBinding{} - err := cli.Get(context.TODO(), client.ObjectKey{Name: namespace, Namespace: namespace}, foundRoleBinding) - if err != nil { - return err - } - - for _, sa := range serviceAccountsList { - // Append serviceAccount if not added already - if !subjectExistInRoleBinding(foundRoleBinding.Subjects, sa, namespace) { - foundRoleBinding.Subjects = append(foundRoleBinding.Subjects, authv1.Subject{ - Kind: authv1.ServiceAccountKind, - Name: sa, - Namespace: namespace, - }) - } - } - - return cli.Update(context.TODO(), foundRoleBinding) -} - -// Internal function used by UpdatePodSecurityRolebinding() -// Return whether Rolebinding matching service account and namespace exists or not. -func subjectExistInRoleBinding(subjectList []authv1.Subject, serviceAccountName, namespace string) bool { - for _, subject := range subjectList { - if subject.Name == serviceAccountName && subject.Namespace == namespace { - return true - } - } - - return false -} - -// ReplaceStringsInFile replaces variable with value in manifests during runtime. -func ReplaceStringsInFile(fileName string, replacements map[string]string) error { +// ReplaceInFile replaces content in the given file either by plain strings or regex patterns based on the content. +func ReplaceInFile(fileName string, replacements map[string]string) error { // Read the contents of the file fileContent, err := os.ReadFile(fileName) if err != nil { return fmt.Errorf("failed to read file: %w", err) } - // Replace all occurrences of the strings in the map + // Replace content using string or regex newContent := string(fileContent) - for string1, string2 := range replacements { - newContent = strings.ReplaceAll(newContent, string1, string2) + for pattern, replacement := range replacements { + regexPattern, err := regexp.Compile(pattern) + if err != nil { + return fmt.Errorf("failed to compile pattern: %w", err) + } + newContent = regexPattern.ReplaceAllString(newContent, replacement) } // Write the modified content back to the file diff --git a/pkg/deploy/setup.go b/pkg/deploy/setup.go index 206ef9eab49..febbf754ee7 100644 --- a/pkg/deploy/setup.go +++ b/pkg/deploy/setup.go @@ -4,10 +4,13 @@ import ( "context" "strings" + operatorv1 "github.com/openshift/api/operator/v1" ofapi "github.com/operator-framework/api/pkg/operators/v1alpha1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" + + dsci "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" ) const ( @@ -89,3 +92,14 @@ func GetPlatform(cli client.Client) (Platform, error) { // check and return whether ODH or self-managed platform return isSelfManaged(cli) } + +// ShouldConfigureServiceMesh determines if the operator should invoke service-mesh specific setup. +func ShouldConfigureServiceMesh(cli client.Client, dscispec *dsci.DSCInitializationSpec) (bool, error) { + platform, err := GetPlatform(cli) + if err != nil { + return false, err + } + + supportedPlatforms := platform == OpenDataHub || platform == Unknown + return dscispec.ServiceMesh.ManagementState == operatorv1.Managed && supportedPlatforms, nil +} diff --git a/pkg/feature/builder.go b/pkg/feature/builder.go index 99be036bac4..4fba0fa5e33 100644 --- a/pkg/feature/builder.go +++ b/pkg/feature/builder.go @@ -206,6 +206,7 @@ func (fb *featureBuilder) withDefaultClient() error { } fb.config = restCfg + return nil } diff --git a/pkg/feature/feature.go b/pkg/feature/feature.go index 5ccb5d6f3a5..bd0b306c6d2 100644 --- a/pkg/feature/feature.go +++ b/pkg/feature/feature.go @@ -182,6 +182,16 @@ func (f *Feature) addCleanup(cleanupFuncs ...Action) { f.cleanups = append(f.cleanups, cleanupFuncs...) } +func (f *Feature) ApplyManifest(path string) error { + m := createManifestFrom(embeddedFiles, path) + + if err := m.process(f.Spec); err != nil { + return err + } + + return f.apply(m) +} + type apply func(data string) error func (f *Feature) apply(m manifest) error { diff --git a/pkg/feature/manifest.go b/pkg/feature/manifest.go index db4b111f1dc..809575698ae 100644 --- a/pkg/feature/manifest.go +++ b/pkg/feature/manifest.go @@ -16,9 +16,11 @@ import ( var embeddedFiles embed.FS var ( - BaseDir = "templates" - ServiceMeshDir = path.Join(BaseDir, "servicemesh") - ServerlessDir = path.Join(BaseDir, "serverless") + BaseDir = "templates" + ServiceMeshDir = path.Join(BaseDir, "servicemesh") + ServerlessDir = path.Join(BaseDir, "serverless") + ControlPlaneDir = path.Join(ServiceMeshDir, "control-plane") + AuthDir = path.Join(ServiceMeshDir, "authorino") ) type manifest struct { diff --git a/pkg/feature/raw_resources.go b/pkg/feature/raw_resources.go index 73468c78082..4e59be4a14f 100644 --- a/pkg/feature/raw_resources.go +++ b/pkg/feature/raw_resources.go @@ -15,7 +15,6 @@ package feature import ( "context" - "fmt" "regexp" "strings" @@ -84,7 +83,6 @@ func (f *Feature) patchResources(resources string) error { u := &unstructured.Unstructured{} if err := yaml.Unmarshal([]byte(str), u); err != nil { f.Log.Error(err, "error unmarshalling yaml") - return errors.WithStack(err) } @@ -100,21 +98,12 @@ func (f *Feature) patchResources(resources string) error { patchAsJSON, err := yaml.YAMLToJSON([]byte(str)) if err != nil { f.Log.Error(err, "error converting yaml to json") - return errors.WithStack(err) } _, err = f.DynamicClient.Resource(gvr). Namespace(u.GetNamespace()). Patch(context.TODO(), u.GetName(), k8stypes.MergePatchType, patchAsJSON, metav1.PatchOptions{}) - if err != nil { - f.Log.Error(err, "error patching resource", - "gvr", fmt.Sprintf("%+v\n", gvr), - "patch", fmt.Sprintf("%+v\n", u), - "json", fmt.Sprintf("%+v\n", patchAsJSON)) - - return errors.WithStack(err) - } if err != nil { return errors.WithStack(err) diff --git a/pkg/feature/servicemesh/cleanup.go b/pkg/feature/servicemesh/cleanup.go new file mode 100644 index 00000000000..9ae7147eda1 --- /dev/null +++ b/pkg/feature/servicemesh/cleanup.go @@ -0,0 +1,131 @@ +package servicemesh + +import ( + "context" + "fmt" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + ctrlLog "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/gvr" +) + +var log = ctrlLog.Log.WithName("features") + +func RemoveTokenVolumes(f *feature.Feature) error { + tokenVolume := fmt.Sprintf("%s-oauth2-tokens", f.Spec.AppNamespace) + + meshNs := f.Spec.ControlPlane.Namespace + meshName := f.Spec.ControlPlane.Name + + smcp, err := f.DynamicClient.Resource(gvr.SMCP).Namespace(meshNs).Get(context.TODO(), meshName, metav1.GetOptions{}) + if err != nil { + return err + } + volumes, found, err := unstructured.NestedSlice(smcp.Object, "spec", "gateways", "ingress", "volumes") + if err != nil { + return err + } + if !found { + log.Info("no volumes found", "f", f.Name, "control-plane", meshName, "istio-ns", meshNs) + return nil + } + + for i, v := range volumes { + volume, ok := v.(map[string]interface{}) + if !ok { + log.Info("unexpected type for volume", "f", f.Name, "type", fmt.Sprintf("%T", volume)) + continue + } + + volumeMount, found, err := unstructured.NestedMap(volume, "volumeMount") + if err != nil { + return err + } + if !found { + log.Info("no volumeMount found in the volume", "f", f.Name) + continue + } + + if volumeMount["name"] == tokenVolume { + volumes = append(volumes[:i], volumes[i+1:]...) + err = unstructured.SetNestedSlice(smcp.Object, volumes, "spec", "gateways", "ingress", "volumes") + if err != nil { + return err + } + break + } + } + + _, err = f.DynamicClient.Resource(gvr.SMCP).Namespace(meshNs).Update(context.TODO(), smcp, metav1.UpdateOptions{}) + + return err +} + +func RemoveOAuthClient(f *feature.Feature) error { + oauthClientName := fmt.Sprintf("%s-oauth2-client", f.Spec.AppNamespace) + + if _, err := f.DynamicClient.Resource(gvr.OAuthClient).Get(context.TODO(), oauthClientName, metav1.GetOptions{}); err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + + return err + } + + if err := f.DynamicClient.Resource(gvr.OAuthClient).Delete(context.TODO(), oauthClientName, metav1.DeleteOptions{}); err != nil { + log.Error(err, "failed deleting OAuthClient", "f", f.Name, "name", oauthClientName) + + return err + } + + return nil +} + +func RemoveExtensionProvider(f *feature.Feature) error { + ossmAuthzProvider := fmt.Sprintf("%s-odh-auth-provider", f.Spec.AppNamespace) + + mesh := f.Spec.ControlPlane + + smcp, err := f.DynamicClient.Resource(gvr.SMCP). + Namespace(mesh.Namespace). + Get(context.TODO(), mesh.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + extensionProviders, found, err := unstructured.NestedSlice(smcp.Object, "spec", "techPreview", "meshConfig", "extensionProviders") + if err != nil { + return err + } + if !found { + log.Info("no extension providers found", "f", f.Name, "control-plane", mesh.Name, "namespace", mesh.Namespace) + return nil + } + + for i, v := range extensionProviders { + extensionProvider, ok := v.(map[string]interface{}) + if !ok { + fmt.Println("Unexpected type for extensionProvider") + continue + } + + if extensionProvider["name"] == ossmAuthzProvider { + extensionProviders = append(extensionProviders[:i], extensionProviders[i+1:]...) + err = unstructured.SetNestedSlice(smcp.Object, extensionProviders, "spec", "techPreview", "meshConfig", "extensionProviders") + if err != nil { + return err + } + break + } + } + + _, err = f.DynamicClient.Resource(gvr.SMCP). + Namespace(mesh.Namespace). + Update(context.TODO(), smcp, metav1.UpdateOptions{}) + + return err +} diff --git a/pkg/feature/servicemesh/envoy_secrets.go b/pkg/feature/servicemesh/envoy_secrets.go new file mode 100644 index 00000000000..23793d74f09 --- /dev/null +++ b/pkg/feature/servicemesh/envoy_secrets.go @@ -0,0 +1,68 @@ +package servicemesh + +import ( + "bytes" + "fmt" + "text/template" + + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" +) + +//nolint:gosec //reason no hardcoded credentials, template placeholder +const tokenSecret = ` +resources: +- "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" + name: token + generic_secret: + secret: + inline_string: "{{ .Secret }}" +` + +//nolint:gosec //reason no hardcoded credentials, template placeholder +const hmacSecret = ` +resources: +- "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" + name: hmac + generic_secret: + secret: + inline_bytes: "{{ .Secret }}" +` + +func createEnvoySecret(oAuth feature.OAuth, objectMeta metav1.ObjectMeta) (*corev1.Secret, error) { + clientSecret, err := processInlineTemplate(tokenSecret, struct{ Secret string }{Secret: oAuth.ClientSecret}) + if err != nil { + return nil, errors.WithStack(err) + } + + hmacSecret, err := processInlineTemplate(hmacSecret, struct{ Secret string }{Secret: oAuth.Hmac}) + if err != nil { + return nil, errors.WithStack(err) + } + + return &corev1.Secret{ + ObjectMeta: objectMeta, + Data: map[string][]byte{ + "token-secret.yaml": clientSecret, + "hmac-secret.yaml": hmacSecret, + }, + }, nil +} + +func processInlineTemplate(templateString string, data interface{}) ([]byte, error) { + tmpl, err := template.New("inline-template").Parse(templateString) + if err != nil { + return nil, fmt.Errorf("error parsing template: %w", err) + } + + var output bytes.Buffer + err = tmpl.Execute(&output, data) + if err != nil { + return nil, fmt.Errorf("error executing template: %w", err) + } + + return output.Bytes(), nil +} diff --git a/pkg/feature/servicemesh/loaders.go b/pkg/feature/servicemesh/loaders.go new file mode 100644 index 00000000000..c6f4ab6ea74 --- /dev/null +++ b/pkg/feature/servicemesh/loaders.go @@ -0,0 +1,69 @@ +package servicemesh + +import ( + "strings" + + "github.com/pkg/errors" + + "github.com/opendatahub-io/opendatahub-operator/v2/controllers/secretgenerator" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/cluster" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" +) + +const DefaultCertificateSecretName = "opendatahub-cert" //nolint:gosec //reason false-positive. it is a name of the secret, not the value. + +func DefaultValues(f *feature.Feature) error { + certificateSecretName := strings.TrimSpace(f.Spec.ControlPlane.Certificate.SecretName) + if len(certificateSecretName) == 0 { + certificateSecretName = DefaultCertificateSecretName + } + + f.Spec.ControlPlane.Certificate.SecretName = certificateSecretName + return nil +} + +func ClusterDetails(f *feature.Feature) error { + data := f.Spec + + if domain, err := cluster.GetDomain(f.DynamicClient); err == nil { + data.Domain = domain + } else { + return errors.WithStack(err) + } + + return nil +} + +func OAuthConfig(f *feature.Feature) error { + data := f.Spec + + var err error + var clientSecret, hmac *secretgenerator.Secret + if clientSecret, err = secretgenerator.NewSecret("ossm-odh-oauth", "random", 32); err != nil { + return errors.WithStack(err) + } + + if hmac, err = secretgenerator.NewSecret("ossm-odh-hmac", "random", 32); err != nil { + return errors.WithStack(err) + } + + if oauthServerDetailsJSON, err := cluster.GetOAuthServerDetails(); err == nil { + hostName, port, errURLParsing := cluster.ExtractHostNameAndPort(oauthServerDetailsJSON.Get("issuer").MustString("issuer")) + if errURLParsing != nil { + return errURLParsing + } + + data.OAuth = feature.OAuth{ + AuthzEndpoint: oauthServerDetailsJSON.Get("authorization_endpoint").MustString("authorization_endpoint"), + TokenEndpoint: oauthServerDetailsJSON.Get("token_endpoint").MustString("token_endpoint"), + Route: hostName, + Port: port, + ClientSecret: clientSecret.Value, + Hmac: hmac.Value, + } + } else { + return errors.WithStack(err) + } + + return nil +} diff --git a/pkg/feature/servicemesh/manifests_config.go b/pkg/feature/servicemesh/manifests_config.go new file mode 100644 index 00000000000..76cb40dba81 --- /dev/null +++ b/pkg/feature/servicemesh/manifests_config.go @@ -0,0 +1,22 @@ +package servicemesh + +import ( + "fmt" + "path/filepath" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/common" +) + +const ( + gatewayPattern = `ISTIO_GATEWAY=(.*)` +) + +// OverwriteIstioGatewayVar replaces the ISTIO_GATEWAY with given namespace and "odh-gateway" in the specified ossm.env file. +// This is used in conjunction with kustomize overlays for Kubeflow notebook controllers. By overwriting referenced we can set +// proper values for environment variables populated through Kustomize. +func OverwriteIstioGatewayVar(namespace, path string) error { + envFile := filepath.Join(path, "ossm.env") + replacement := fmt.Sprintf("ISTIO_GATEWAY=%s", namespace+"/opendatahub-gateway") + + return common.ReplaceInFile(envFile, map[string]string{gatewayPattern: replacement}) +} diff --git a/pkg/feature/servicemesh/manifests_config_unit_test.go b/pkg/feature/servicemesh/manifests_config_unit_test.go new file mode 100644 index 00000000000..e1cf1307668 --- /dev/null +++ b/pkg/feature/servicemesh/manifests_config_unit_test.go @@ -0,0 +1,61 @@ +package servicemesh_test + +import ( + "os" + "path/filepath" + + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature/servicemesh" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Overwriting gateway name in env file", func() { + + It("should replace gateway name in the file", func() { + namespace := "test-namespace" + path := createTempEnvFile() + Expect(servicemesh.OverwriteIstioGatewayVar(namespace, path)).To(Succeed()) + + updatedContents, err := os.ReadFile(filepath.Join(path, "ossm.env")) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(updatedContents)).To(ContainSubstring("ISTIO_GATEWAY=test-namespace/odh-gateway")) + }) + + It("should fail if the file does not exist", func() { + Expect(servicemesh.OverwriteIstioGatewayVar("test-namespace", "wrong_directory")).To(Not(Succeed())) + }) + + It("should not modify other text in the file", func() { + namespace := "test-namespace" + path := createTempEnvFile() + + Expect(servicemesh.OverwriteIstioGatewayVar(namespace, path)).To(Succeed()) + + updatedContents, err := os.ReadFile(filepath.Join(path, "ossm.env")) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(updatedContents)).To(ContainSubstring("AnotherSetting=value")) + }) +}) + +const testContent = ` +ISTIO_GATEWAY=default-namespace/odh-gateway +AnotherSetting=value` + +func createTempEnvFile() string { + var err error + + tempDir := GinkgoT().TempDir() + + tempFilePath := filepath.Join(tempDir, "ossm.env") + tempFile, err := os.Create(tempFilePath) + Expect(err).NotTo(HaveOccurred()) + + _, err = tempFile.WriteString(testContent) + Expect(err).NotTo(HaveOccurred()) + defer tempFile.Close() + + return tempDir +} diff --git a/pkg/feature/servicemesh/resources.go b/pkg/feature/servicemesh/resources.go new file mode 100644 index 00000000000..87cc144a175 --- /dev/null +++ b/pkg/feature/servicemesh/resources.go @@ -0,0 +1,173 @@ +package servicemesh + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + + v1 "github.com/opendatahub-io/opendatahub-operator/v2/infrastructure/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/gvr" +) + +// TODO rework (dupl). +func SelfSignedCertificate(f *feature.Feature) error { + if f.Spec.ControlPlane.Certificate.Type == v1.SelfSigned { + meta := metav1.ObjectMeta{ + Name: f.Spec.ControlPlane.Certificate.SecretName, + Namespace: f.Spec.ControlPlane.Namespace, + OwnerReferences: []metav1.OwnerReference{ + f.AsOwnerReference(), + }, + } + + cert, err := feature.GenerateSelfSignedCertificateAsSecret(f.Spec.Domain, meta) + if err != nil { + return errors.WithStack(err) + } + + _, err = f.Clientset.CoreV1(). + Secrets(f.Spec.ControlPlane.Namespace). + Create(context.TODO(), cert, metav1.CreateOptions{}) + if err != nil && !k8serrors.IsAlreadyExists(err) { + return errors.WithStack(err) + } + } + + return nil +} + +func EnvoyOAuthSecrets(feature *feature.Feature) error { + objectMeta := metav1.ObjectMeta{ + Name: feature.Spec.AppNamespace + "-oauth2-tokens", + Namespace: feature.Spec.ControlPlane.Namespace, + OwnerReferences: []metav1.OwnerReference{ + feature.AsOwnerReference(), + }, + } + + envoySecret, err := createEnvoySecret(feature.Spec.OAuth, objectMeta) + if err != nil { + return errors.WithStack(err) + } + + _, err = feature.Clientset.CoreV1(). + Secrets(objectMeta.Namespace). + Create(context.TODO(), envoySecret, metav1.CreateOptions{}) + if err != nil && !k8serrors.IsAlreadyExists(err) { + return errors.WithStack(err) + } + + return nil +} + +func ConfigMaps(feature *feature.Feature) error { + meshConfig := feature.Spec.ControlPlane + if err := feature.CreateConfigMap("service-mesh-refs", + map[string]string{ + "CONTROL_PLANE_NAME": meshConfig.Name, + "MESH_NAMESPACE": meshConfig.Namespace, + }); err != nil { + return errors.WithStack(err) + } + + authorinoConfig := feature.Spec.Auth.Authorino + if err := feature.CreateConfigMap("auth-refs", + map[string]string{ + "AUTHORINO_LABEL": authorinoConfig.Label, + "AUTH_AUDIENCE": strings.Join(authorinoConfig.Audiences, ","), + }); err != nil { + return errors.WithStack(err) + } + + return nil +} + +func EnabledInDashboard(feature *feature.Feature) error { + return setServiceMeshDisabledFlag(false)(feature) +} + +func DisabledInDashboard(feature *feature.Feature) error { + return setServiceMeshDisabledFlag(true)(feature) +} + +func setServiceMeshDisabledFlag(disabled bool) feature.Action { + return func(feature *feature.Feature) error { + configs, err := feature.DynamicClient. + Resource(gvr.ODHDashboardConfigGVR). + Namespace(feature.Spec.AppNamespace). + List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return err + } + + if len(configs.Items) == 0 { + log.Info("No odhdashboardconfig found in namespace, doing nothing", "name", feature.Name) + return nil + } + + // Assuming there is only one odhdashboardconfig in the namespace, patching the first one + config := configs.Items[0] + if config.Object["spec"] == nil { + config.Object["spec"] = map[string]interface{}{} + } + spec, ok := config.Object["spec"].(map[string]interface{}) + if !ok { + return errors.New("unable to cast .spec to map[string]interface{}") + } + if spec["dashboardConfig"] == nil { + spec["dashboardConfig"] = map[string]interface{}{} + } + dashboardConfig, ok := spec["dashboardConfig"].(map[string]interface{}) + if !ok { + return errors.New("unable to cast .dashboardConfig to map[string]interface{}") + } + dashboardConfig["disableServiceMesh"] = disabled + + if _, err := feature.DynamicClient.Resource(gvr.ODHDashboardConfigGVR). + Namespace(feature.Spec.AppNamespace). + Update(context.TODO(), &config, metav1.UpdateOptions{}); err != nil { + log.Error(err, "Failed to update odhdashboardconfig", "name", feature.Name) + + return err + } + + log.Info("Successfully patched odhdashboardconfig", "name", feature.Name) + return nil + } +} + +func MigratedDataScienceProjects(feature *feature.Feature) error { + selector := labels.SelectorFromSet(labels.Set{"opendatahub.io/dashboard": "true"}) + + namespaceClient := feature.Clientset.CoreV1().Namespaces() + + namespaces, err := namespaceClient.List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()}) + if err != nil { + return fmt.Errorf("failed to get namespaces: %w", err) + } + + var result *multierror.Error + + for i := range namespaces.Items { + namespace := namespaces.Items[i] + annotations := namespace.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + annotations["opendatahub.io/service-mesh"] = "true" + namespace.SetAnnotations(annotations) + + if _, err := namespaceClient.Update(context.TODO(), &namespace, metav1.UpdateOptions{}); err != nil { + result = multierror.Append(result, err) + } + } + + return result.ErrorOrNil() +} diff --git a/pkg/feature/servicemesh/servicemesh_suite_unit_test.go b/pkg/feature/servicemesh/servicemesh_suite_unit_test.go new file mode 100644 index 00000000000..2386b14327f --- /dev/null +++ b/pkg/feature/servicemesh/servicemesh_suite_unit_test.go @@ -0,0 +1,13 @@ +package servicemesh_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestServiceMeshSetup(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Service Mesh setup unit tests") +} diff --git a/pkg/feature/templates/servicemesh/authorino/auth-smm.tmpl b/pkg/feature/templates/servicemesh/authorino/auth-smm.tmpl new file mode 100644 index 00000000000..6b0aa06aa82 --- /dev/null +++ b/pkg/feature/templates/servicemesh/authorino/auth-smm.tmpl @@ -0,0 +1,10 @@ +apiVersion: maistra.io/v1 +kind: ServiceMeshMember +metadata: + name: default + namespace: {{ .Auth.Namespace }} +spec: + controlPlaneRef: + namespace: {{ .ControlPlane.Namespace }} + name: {{ .ControlPlane.Name }} + diff --git a/pkg/feature/templates/servicemesh/authorino/base/authconfig.tmpl b/pkg/feature/templates/servicemesh/authorino/base/authconfig.tmpl new file mode 100644 index 00000000000..ce9c7db58b4 --- /dev/null +++ b/pkg/feature/templates/servicemesh/authorino/base/authconfig.tmpl @@ -0,0 +1,74 @@ +apiVersion: authorino.kuadrant.io/v1beta2 +kind: AuthConfig +metadata: + name: odh-dashboard-protection + namespace: {{ .AppNamespace }} + labels: + {{ ReplaceChar .Auth.Authorino.Label "=" ": " }} +spec: + hosts: + - {{ .AppNamespace }}.{{ .Domain }} + authentication: + cluster-users: + kubernetesTokenReview: + audiences: +{{- range .Auth.Authorino.Audiences }} + - "{{ . }}" +{{- end }} + + authorization: + + dashboard-access: + kubernetesSubjectAccessReview: + resourceAttributes: + group: + value: "" + name: + value: odh-dashboard + namespace: + value: {{ .AppNamespace }} + resource: + value: services + verb: + value: get + user: + selector: auth.identity.user.username + when: + - operator: neq + selector: context.request.http.path.@extract:{"sep":"/","pos":1} + value: notebook + + notebook-access: + kubernetesSubjectAccessReview: + resourceAttributes: + group: + value: kubeflow.org + name: + value: "" + namespace: + selector: context.request.http.path.@extract:{"sep":"/","pos":2} + resource: + value: notebooks + verb: + value: get + user: + selector: auth.identity.user.username + when: + - operator: eq + selector: context.request.http.path.@extract:{"sep":"/","pos":1} + value: notebook + + response: + success: + headers: + x-auth-data: + json: + properties: + username: + selector: auth.identity.username + unauthenticated: + message: + value: Access denied + unauthorized: + message: + value: Unauthorized diff --git a/pkg/feature/templates/servicemesh/authorino/base/operator-cluster-wide-no-tls.tmpl b/pkg/feature/templates/servicemesh/authorino/base/operator-cluster-wide-no-tls.tmpl new file mode 100644 index 00000000000..95b1a8fb63b --- /dev/null +++ b/pkg/feature/templates/servicemesh/authorino/base/operator-cluster-wide-no-tls.tmpl @@ -0,0 +1,15 @@ +apiVersion: operator.authorino.kuadrant.io/v1beta1 +kind: Authorino +metadata: + name: {{ .Auth.Authorino.Name }} + namespace: {{ .Auth.Namespace }} +spec: + image: {{ .Auth.Authorino.Image }} + authConfigLabelSelectors: {{ .Auth.Authorino.Label }} + clusterWide: true + listener: + tls: + enabled: false + oidcServer: + tls: + enabled: false diff --git a/pkg/feature/templates/servicemesh/authorino/deployment.injection.patch.tmpl b/pkg/feature/templates/servicemesh/authorino/deployment.injection.patch.tmpl new file mode 100644 index 00000000000..e15fb31100f --- /dev/null +++ b/pkg/feature/templates/servicemesh/authorino/deployment.injection.patch.tmpl @@ -0,0 +1,10 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Auth.Authorino.Name }} + namespace: {{ .Auth.Namespace }} +spec: + template: + metadata: + annotations: + sidecar.istio.io/inject: "true" diff --git a/pkg/feature/templates/servicemesh/authorino/mesh-authz-ext-provider.patch.tmpl b/pkg/feature/templates/servicemesh/authorino/mesh-authz-ext-provider.patch.tmpl new file mode 100644 index 00000000000..706c995dc4c --- /dev/null +++ b/pkg/feature/templates/servicemesh/authorino/mesh-authz-ext-provider.patch.tmpl @@ -0,0 +1,13 @@ +apiVersion: maistra.io/v2 +kind: ServiceMeshControlPlane +metadata: + name: {{ .ControlPlane.Name }} + namespace: {{ .ControlPlane.Namespace }} +spec: + techPreview: + meshConfig: + extensionProviders: + - name: {{ .AppNamespace }}-odh-auth-provider + envoyExtAuthzGrpc: + service: {{ .Auth.Authorino.Name }}-authorino-authorization.{{ .Auth.Namespace }}.svc.cluster.local + port: 50051 diff --git a/pkg/feature/templates/servicemesh/authorino/rbac/cluster-monitoring-role-binding.tmpl b/pkg/feature/templates/servicemesh/authorino/rbac/cluster-monitoring-role-binding.tmpl new file mode 100644 index 00000000000..cf4114eac88 --- /dev/null +++ b/pkg/feature/templates/servicemesh/authorino/rbac/cluster-monitoring-role-binding.tmpl @@ -0,0 +1,13 @@ +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: auth-service-monitoring + namespace: {{ .Auth.Namespace }} +subjects: + - kind: ServiceAccount + name: auth-service + namespace: {{ .Auth.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-monitoring-view diff --git a/pkg/feature/templates/servicemesh/authorino/rbac/cluster-role-binding.tmpl b/pkg/feature/templates/servicemesh/authorino/rbac/cluster-role-binding.tmpl new file mode 100644 index 00000000000..f54ca7c7703 --- /dev/null +++ b/pkg/feature/templates/servicemesh/authorino/rbac/cluster-role-binding.tmpl @@ -0,0 +1,13 @@ +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: auth-service + namespace: {{ .Auth.Namespace }} +subjects: + - kind: ServiceAccount + name: auth-service + namespace: {{ .Auth.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: auth-service diff --git a/pkg/feature/templates/servicemesh/authorino/rbac/cluster-role.tmpl b/pkg/feature/templates/servicemesh/authorino/rbac/cluster-role.tmpl new file mode 100644 index 00000000000..bf47469fb4c --- /dev/null +++ b/pkg/feature/templates/servicemesh/authorino/rbac/cluster-role.tmpl @@ -0,0 +1,160 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: auth-service + namespace: {{ .Auth.Namespace }} +rules: + - verbs: + - get + - list + apiGroups: + - machine.openshift.io + - autoscaling.openshift.io + resources: + - machineautoscalers + - machinesets + - verbs: + - get + - watch + - list + apiGroups: + - '' + - config.openshift.io + resources: + - clusterversions + - verbs: + - get + - list + - watch + apiGroups: + - operators.coreos.com + resources: + - clusterserviceversions + - subscriptions + - apiGroups: + - '' + - image.openshift.io + resources: + - imagestreams/layers + verbs: + - get + - apiGroups: + - '' + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + resources: + - configmaps + - persistentvolumeclaims + - secrets + - verbs: + - get + - list + - watch + apiGroups: + - route.openshift.io + resources: + - routes + - verbs: + - get + - list + - watch + apiGroups: + - console.openshift.io + resources: + - consolelinks + - verbs: + - get + - list + - watch + apiGroups: + - operator.openshift.io + resources: + - consoles + - verbs: + - get + - watch + - list + apiGroups: + - '' + - integreatly.org + resources: + - rhmis + - verbs: + - get + - list + - watch + apiGroups: + - user.openshift.io + resources: + - groups + - verbs: + - get + - list + - watch + apiGroups: + - user.openshift.io + resources: + - users + - verbs: + - get + - list + - watch + apiGroups: + - '' + resources: + - pods + - serviceaccounts + - services + - apiGroups: + - '' + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + resources: + - namespaces + - apiGroups: + - rbac.authorization.k8s.io + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + resources: + - rolebindings + - clusterrolebindings + - roles + - apiGroups: + - '' + - events.k8s.io + resources: + - events + verbs: + - get + - list + - watch + - apiGroups: + - kubeflow.org + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + resources: + - notebooks diff --git a/pkg/feature/templates/servicemesh/authorino/rbac/role-binding.tmpl b/pkg/feature/templates/servicemesh/authorino/rbac/role-binding.tmpl new file mode 100644 index 00000000000..70d1f9316ed --- /dev/null +++ b/pkg/feature/templates/servicemesh/authorino/rbac/role-binding.tmpl @@ -0,0 +1,12 @@ +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: auth-service + namespace: {{ .Auth.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: auth-service +subjects: + - kind: ServiceAccount + name: auth-service diff --git a/pkg/feature/templates/servicemesh/authorino/rbac/role.tmpl b/pkg/feature/templates/servicemesh/authorino/rbac/role.tmpl new file mode 100644 index 00000000000..9afc140f814 --- /dev/null +++ b/pkg/feature/templates/servicemesh/authorino/rbac/role.tmpl @@ -0,0 +1,125 @@ +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: auth-service + namespace: {{ .Auth.Namespace }} +rules: + - apiGroups: + - route.openshift.io + resources: + - routes + verbs: + - get + - list + - watch + - apiGroups: + - kfdef.apps.kubeflow.org + resources: + - kfdefs + verbs: + - get + - list + - watch + - apiGroups: + - batch + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + resources: + - cronjobs + - jobs + - jobs/status + - apiGroups: + - image.openshift.io + verbs: + - create + - get + - list + - update + - patch + - delete + resources: + - imagestreams + - apiGroups: + - build.openshift.io + verbs: + - get + - list + - watch + - create + - patch + - delete + resources: + - builds + - buildconfigs + - buildconfigs/instantiate + - apiGroups: + - apps + verbs: + - patch + - update + resources: + - deployments + - apiGroups: + - apps.openshift.io + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + resources: + - deploymentconfigs + - deploymentconfigs/instantiate + - apiGroups: + - opendatahub.io + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + resources: + - odhdashboardconfigs + - apiGroups: + - kubeflow.org + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + resources: + - notebooks + - verbs: + - get + - list + apiGroups: + - dashboard.opendatahub.io + resources: + - odhapplications + - verbs: + - get + - list + apiGroups: + - dashboard.opendatahub.io + resources: + - odhdocuments + - verbs: + - get + - list + apiGroups: + - console.openshift.io + resources: + - odhquickstarts diff --git a/pkg/feature/templates/servicemesh/control-plane/base/control-plane-ingress.patch.tmpl b/pkg/feature/templates/servicemesh/control-plane/base/control-plane-ingress.patch.tmpl new file mode 100644 index 00000000000..15ebe1c9c9c --- /dev/null +++ b/pkg/feature/templates/servicemesh/control-plane/base/control-plane-ingress.patch.tmpl @@ -0,0 +1,21 @@ +apiVersion: maistra.io/v2 +kind: ServiceMeshControlPlane +metadata: + name: {{ .ControlPlane.Name }} + namespace: {{ .ControlPlane.Namespace }} +spec: + proxy: + injection: + autoInject: false + gateways: + ingress: + volumes: + - volume: + secret: + secretName: {{ .AppNamespace }}-oauth2-tokens + optional: true + volumeMount: + name: {{ .AppNamespace }}-oauth2-tokens + mountPath: "/etc/istio/{{ .AppNamespace }}-oauth2-tokens" + readOnly: true + diff --git a/pkg/feature/templates/servicemesh/base/create-smcp.tmpl b/pkg/feature/templates/servicemesh/control-plane/base/create-control-plane.tmpl similarity index 100% rename from pkg/feature/templates/servicemesh/base/create-smcp.tmpl rename to pkg/feature/templates/servicemesh/control-plane/base/create-control-plane.tmpl diff --git a/pkg/feature/templates/servicemesh/control-plane/components/dashboard/gateway.tmpl b/pkg/feature/templates/servicemesh/control-plane/components/dashboard/gateway.tmpl new file mode 100644 index 00000000000..70628122721 --- /dev/null +++ b/pkg/feature/templates/servicemesh/control-plane/components/dashboard/gateway.tmpl @@ -0,0 +1,24 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: opendatahub-gateway + namespace: {{ .AppNamespace }} +spec: + selector: + istio: ingressgateway + servers: + - port: + number: 443 + name: https + protocol: HTTPS + tls: + mode: SIMPLE + credentialName: {{ .ControlPlane.Certificate.SecretName }} + hosts: + - "{{ .AppNamespace }}.{{ .Domain }}" + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "{{ .AppNamespace }}.{{ .Domain }}" \ No newline at end of file diff --git a/pkg/feature/templates/servicemesh/control-plane/components/dashboard/virtual-service.tmpl b/pkg/feature/templates/servicemesh/control-plane/components/dashboard/virtual-service.tmpl new file mode 100644 index 00000000000..36fd9aef6fa --- /dev/null +++ b/pkg/feature/templates/servicemesh/control-plane/components/dashboard/virtual-service.tmpl @@ -0,0 +1,27 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: odh-dashboard-vs + namespace: {{ .AppNamespace }} +spec: + gateways: + - opendatahub-gateway + hosts: + - "{{ .AppNamespace }}.{{ .Domain }}" + http: + - match: + - uri: + # match host.com/notebook/ns/username/logout + regex: "^/notebook/.*/.*/logout" + route: + - destination: + host: odh-dashboard + port: + number: 80 + rewrite: + uri: "/oauth/sign_out" + - route: + - destination: + host: odh-dashboard + port: + number: 80 diff --git a/pkg/feature/templates/servicemesh/control-plane/filters/filter-oauth2.tmpl b/pkg/feature/templates/servicemesh/control-plane/filters/filter-oauth2.tmpl new file mode 100644 index 00000000000..c6b6a5fe55f --- /dev/null +++ b/pkg/feature/templates/servicemesh/control-plane/filters/filter-oauth2.tmpl @@ -0,0 +1,90 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: EnvoyFilter +metadata: + name: oauth2-ingress + namespace: {{ .ControlPlane.Namespace }} + labels: + name: oauth2-envoy + app: opendatahub +spec: + priority: 10 + workloadSelector: + labels: + istio: ingressgateway # this is all or nothing option - we should narrow to more sane labels / ns? + configPatches: + - applyTo: CLUSTER + match: + cluster: + service: oauth-openshift + patch: + operation: ADD + value: + name: oauth-openshift + dns_lookup_family: V4_ONLY + type: LOGICAL_DNS + connect_timeout: 10s + lb_policy: ROUND_ROBIN + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + sni: {{ .OAuth.Route }} + load_assignment: + cluster_name: oauth-openshift + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: {{ .OAuth.Route }} + port_value: {{ .OAuth.Port }} + - applyTo: HTTP_FILTER + match: + context: GATEWAY + listener: + filterChain: + filter: + name: "envoy.filters.network.http_connection_manager" + # subFilter: + # name: "envoy.filters.http.jwt_authn" + patch: + operation: INSERT_BEFORE + value: + name: envoy.filters.http.oauth2 + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3.OAuth2 + config: + token_endpoint: + cluster: oauth-openshift + uri: {{ .OAuth.TokenEndpoint }} + timeout: 3s + authorization_endpoint: {{ .OAuth.AuthzEndpoint }} + redirect_uri: "https://%REQ(:authority)%/callback" + redirect_path_matcher: + path: + exact: /callback + signout_path: + path: + exact: /oauth/sign_out + credentials: + client_id: {{ .AppNamespace }}-oauth2-client + token_secret: + name: token + sds_config: + path: "/etc/istio/{{ .AppNamespace }}-oauth2-tokens/token-secret.yaml" + hmac_secret: + name: hmac + sds_config: + path: "/etc/istio/{{ .AppNamespace }}-oauth2-tokens/hmac-secret.yaml" + auth_scopes: + - user:full + forward_bearer_token: true + # FIXME: This always bypasses authN for ModelMesh. It should not if + # the model is flagged to be auth-protected. + pass_through_matcher: + - name: ":path" + prefix_match: "/modelmesh/" + - name: ":path" + prefix_match: "/vmodel-route/" + - name: content-type + prefix_match: application/grpc diff --git a/pkg/feature/templates/servicemesh/control-plane/filters/filter-propagate-token.tmpl b/pkg/feature/templates/servicemesh/control-plane/filters/filter-propagate-token.tmpl new file mode 100644 index 00000000000..776ed3f3440 --- /dev/null +++ b/pkg/feature/templates/servicemesh/control-plane/filters/filter-propagate-token.tmpl @@ -0,0 +1,53 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: EnvoyFilter +metadata: + name: propagate-token + namespace: {{ .ControlPlane.Namespace }} + labels: + name: oauth2-envoy + app: opendatahub +spec: + workloadSelector: + labels: + istio: ingressgateway + priority: 20 # after oauth2 filter + configPatches: + - applyTo: HTTP_FILTER + match: + context: GATEWAY + listener: + filterChain: + filter: + name: envoy.filters.network.http_connection_manager + patch: + operation: INSERT_BEFORE + value: + name: envoy.filters.http.lua + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + inlineCode: | + function envoy_on_request(request_handle) + local headers = request_handle:headers() + if not headers then + return + end + + local bearer_token = nil + local cookies = headers:get("cookie") + -- Extracting token value from the header which is in form of + -- cookie: OauthHMAC=HMAC_VALUE;OauthExpires=1679994106; BearerToken=TOKEN_VALUE` + if cookies then + for cookie in cookies:gmatch("([^;]+)") do + local name, value = cookie:match("^%s*([^=]+)=(.*)$") + if name and value and name:lower() == "bearertoken" then + bearer_token = value + break + end + end + end + + -- Set the "x-forwarded-access-token" header if the bearer token was found + if bearer_token then + headers:add("x-forwarded-access-token", bearer_token) + end + end diff --git a/pkg/feature/templates/servicemesh/control-plane/namespace.patch.tmpl b/pkg/feature/templates/servicemesh/control-plane/namespace.patch.tmpl new file mode 100644 index 00000000000..98b12916ebd --- /dev/null +++ b/pkg/feature/templates/servicemesh/control-plane/namespace.patch.tmpl @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .AppNamespace }} + annotations: + opendatahub.io/service-mesh: "true" + \ No newline at end of file diff --git a/pkg/feature/templates/servicemesh/control-plane/oauth/auth-policy.tmpl b/pkg/feature/templates/servicemesh/control-plane/oauth/auth-policy.tmpl new file mode 100644 index 00000000000..6568921bcfe --- /dev/null +++ b/pkg/feature/templates/servicemesh/control-plane/oauth/auth-policy.tmpl @@ -0,0 +1,26 @@ +kind: AuthorizationPolicy +apiVersion: security.istio.io/v1beta1 +metadata: + name: {{ .AppNamespace }}-odh-auth-policy + namespace: {{ .ControlPlane.Namespace }} +spec: + selector: + matchLabels: + app: istio-ingressgateway + action: CUSTOM + provider: + name: {{ .AppNamespace }}-odh-auth-provider + rules: + - to: + - operation: + notPaths: # todo: see if this is necessary + - "/auth/*" + - "/metrics/*" + # FIXME: This always bypasses authZ for ModelMesh. It should not if + # the model is flagged to be auth-protected. + - "/modelmesh/*" + - "/vmodel-route/*" + when: + - key: request.headers[Content-Type] + notValues: + - "application/grpc*" diff --git a/pkg/feature/templates/servicemesh/control-plane/oauth/oauth-client.tmpl b/pkg/feature/templates/servicemesh/control-plane/oauth/oauth-client.tmpl new file mode 100644 index 00000000000..5ca66161d24 --- /dev/null +++ b/pkg/feature/templates/servicemesh/control-plane/oauth/oauth-client.tmpl @@ -0,0 +1,8 @@ +kind: OAuthClient +apiVersion: oauth.openshift.io/v1 +metadata: + name: {{ .AppNamespace }}-oauth2-client +secret: "{{ .OAuth.ClientSecret }}" +redirectURIs: +- "https://{{ .AppNamespace }}.{{ .Domain}}" +grantMethod: prompt diff --git a/pkg/feature/templates/servicemesh/control-plane/routing/gateway-route.tmpl b/pkg/feature/templates/servicemesh/control-plane/routing/gateway-route.tmpl new file mode 100644 index 00000000000..ab56dc57450 --- /dev/null +++ b/pkg/feature/templates/servicemesh/control-plane/routing/gateway-route.tmpl @@ -0,0 +1,19 @@ +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ .AppNamespace }}-odh-gateway + namespace: {{ .ControlPlane.Namespace }} + labels: + app: odh-dashboard + app.kubernetes.io/part-of: odh-dashboard +spec: + host: {{ .AppNamespace }}.{{ .Domain }} + to: + kind: Service + name: istio-ingressgateway + weight: 100 + port: + targetPort: https + tls: + termination: passthrough + wildcardPolicy: None diff --git a/pkg/feature/templates/servicemesh/control-plane/smm.tmpl b/pkg/feature/templates/servicemesh/control-plane/smm.tmpl new file mode 100644 index 00000000000..821c4c21753 --- /dev/null +++ b/pkg/feature/templates/servicemesh/control-plane/smm.tmpl @@ -0,0 +1,9 @@ +apiVersion: maistra.io/v1 +kind: ServiceMeshMember +metadata: + name: default + namespace: {{ .AppNamespace }} +spec: + controlPlaneRef: + namespace: {{ .ControlPlane.Namespace }} + name: {{ .ControlPlane.Name }} diff --git a/pkg/gvr/gvr.go b/pkg/gvr/gvr.go index c8066ea4a12..f6159b8d450 100644 --- a/pkg/gvr/gvr.go +++ b/pkg/gvr/gvr.go @@ -15,10 +15,10 @@ var ( Resource: "ingresses", } - FeatureTracker = schema.GroupVersionResource{ - Group: "features.opendatahub.io", - Version: "v1", - Resource: "featuretrackers", + ODHDashboardConfigGVR = schema.GroupVersionResource{ + Group: "opendatahub.io", + Version: "v1alpha", + Resource: "odhdashboardconfigs", } SMCP = schema.GroupVersionResource{ @@ -27,9 +27,9 @@ var ( Resource: "servicemeshcontrolplanes", } - NetworkPolicies = schema.GroupVersionResource{ - Group: "networking.k8s.io", + OAuthClient = schema.GroupVersionResource{ + Group: "oauth.openshift.io", Version: "v1", - Resource: "networkpolicies", + Resource: "oauthclients", } ) diff --git a/tests/assertions/gomega_matchers.go b/tests/assertions/gomega_matchers.go new file mode 100644 index 00000000000..32f53cfed14 --- /dev/null +++ b/tests/assertions/gomega_matchers.go @@ -0,0 +1,70 @@ +package assertions + +import ( + "fmt" + + "github.com/onsi/gomega/format" + "github.com/onsi/gomega/types" + conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" + corev1 "k8s.io/api/core/v1" +) + +var _ types.GomegaMatcher = (*HaveConditionMatcher)(nil) + +func HaveCondition(conditionType conditionsv1.ConditionType, conditionStatus corev1.ConditionStatus, reason string) *HaveConditionMatcher { + return &HaveConditionMatcher{ + conditionType: conditionType, + conditionStatus: conditionStatus, + reason: reason, + } +} + +type HaveConditionMatcher struct { + conditionType conditionsv1.ConditionType + conditionStatus corev1.ConditionStatus + reason string +} + +func (h HaveConditionMatcher) Match(actual interface{}) (bool, error) { + conditions, err := asConditions(actual) + if err != nil { + return false, err + } + + desiredCondition := conditionsv1.FindStatusCondition(conditions, h.conditionType) + + return desiredCondition != nil && desiredCondition.Status == h.conditionStatus && desiredCondition.Reason == h.reason, nil +} + +func asConditions(actual interface{}) ([]conditionsv1.Condition, error) { + var conditions []conditionsv1.Condition + + switch v := actual.(type) { + case []conditionsv1.Condition: + conditions = v + case *[]conditionsv1.Condition: + if v != nil { + conditions = *v + } else { + conditions = []conditionsv1.Condition{} + } + default: + return nil, fmt.Errorf("unsupported type: %T", v) + } + + return conditions, nil +} + +func (h HaveConditionMatcher) FailureMessage(actual interface{}) string { + return fmt.Sprintf("Expected %s to be:\n%s", format.Object(actual, 1), h.desiredCondition()) +} + +func (h HaveConditionMatcher) NegatedFailureMessage(actual interface{}) string { + return fmt.Sprintf("Expected %s to not be:\n%s", format.Object(actual, 1), h.desiredCondition()) +} + +func (h HaveConditionMatcher) desiredCondition() interface{} { + return "Type: " + string(h.conditionType) + "\n" + + "Status: " + string(h.conditionStatus) + "\n" + + "Reason: " + h.reason +} diff --git a/tests/integration/features/features_int_test.go b/tests/integration/features/features_int_test.go index 3d52595614e..8b494d4ccf0 100644 --- a/tests/integration/features/features_int_test.go +++ b/tests/integration/features/features_int_test.go @@ -13,7 +13,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" + k8stypes "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" @@ -606,14 +606,14 @@ func newDSCInitialization(ns string) *dsciv1.DSCInitialization { func getNamespace(namespace string) (*v1.Namespace, error) { ns := newNamespace(namespace) - err := envTestClient.Get(context.Background(), types.NamespacedName{Name: namespace}, ns) + err := envTestClient.Get(context.Background(), k8stypes.NamespacedName{Name: namespace}, ns) return ns, err } func getService(name, namespace string) (*v1.Service, error) { svc := &v1.Service{} - err := envTestClient.Get(context.Background(), types.NamespacedName{ + err := envTestClient.Get(context.Background(), k8stypes.NamespacedName{ Name: name, Namespace: namespace, }, svc) diff --git a/tests/integration/features/servicemesh_feature_test.go b/tests/integration/features/servicemesh_feature_test.go index bf32225611c..ab2035665aa 100644 --- a/tests/integration/features/servicemesh_feature_test.go +++ b/tests/integration/features/servicemesh_feature_test.go @@ -84,10 +84,6 @@ var _ = Describe("Service Mesh feature", func() { Expect(err).ToNot(HaveOccurred()) }) - AfterEach(func() { - - }) - Describe("preconditions", func() { When("operator is not installed", func() { diff --git a/tests/integration/servicemesh/crd/servicemeshcontrolplanes.crd.yaml b/tests/integration/servicemesh/crd/servicemeshcontrolplanes.crd.yaml new file mode 100644 index 00000000000..ecf91cb49cb --- /dev/null +++ b/tests/integration/servicemesh/crd/servicemeshcontrolplanes.crd.yaml @@ -0,0 +1,10237 @@ + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + maistra-version: 2.4.2 + annotations: + service.beta.openshift.io/inject-cabundle: "true" + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: servicemeshcontrolplanes.maistra.io +spec: + group: maistra.io + names: + categories: + - maistra-io + kind: ServiceMeshControlPlane + listKind: ServiceMeshControlPlaneList + plural: servicemeshcontrolplanes + shortNames: + - smcp + singular: servicemeshcontrolplane + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: How many of the total number of components are ready + jsonPath: .status.annotations.readyComponentCount + name: Ready + type: string + - description: Whether or not the control plane installation is up to date. + jsonPath: .status.conditions[?(@.type=="Reconciled")].reason + name: Status + type: string + - description: The configuration template to use as the base. + jsonPath: .status.lastAppliedConfiguration.template + name: Template + type: string + - description: The actual current version of the control plane installation. + jsonPath: .status.lastAppliedConfiguration.version + name: Version + type: string + - description: The age of the object + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: The image hub used as the base for all component images. + jsonPath: .status.lastAppliedConfiguration.istio.global.hub + name: Image HUB + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + istio: + type: object + x-kubernetes-preserve-unknown-fields: true + networkType: + type: string + profiles: + items: + type: string + type: array + template: + type: string + threeScale: + type: object + x-kubernetes-preserve-unknown-fields: true + version: + type: string + type: object + status: + nullable: true + properties: + annotations: + additionalProperties: + type: string + type: object + components: + items: + properties: + children: + items: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + type: object + type: array + type: object + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + type: object + type: array + resource: + type: string + type: object + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + type: object + type: array + lastAppliedConfiguration: + properties: + istio: + type: object + x-kubernetes-preserve-unknown-fields: true + networkType: + type: string + profiles: + items: + type: string + type: array + template: + type: string + threeScale: + type: object + x-kubernetes-preserve-unknown-fields: true + version: + type: string + type: object + observedGeneration: + format: int64 + type: integer + reconciledVersion: + type: string + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: How many of the total number of components are ready + jsonPath: .status.annotations.readyComponentCount + name: Ready + type: string + - description: Whether or not the control plane installation is up to date and ready to handle requests. + jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Status + type: string + - description: The configuration profiles applied to the configuration. + jsonPath: .status.appliedSpec.profiles + name: Profiles + type: string + - description: The actual current version of the control plane installation. + jsonPath: .status.chartVersion + name: Version + type: string + - description: The age of the object + jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: The image registry used as the base for all component images. + jsonPath: .status.appliedSpec.runtime.defaults.container.registry + name: Image Registry + priority: 1 + type: string + name: v2 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + addons: + properties: + 3scale: + properties: + backend: + properties: + cache_flush_interval: + format: int32 + type: integer + enable_cache: + type: boolean + policy_fail_closed: + type: boolean + type: object + client: + properties: + allow_insecure_connections: + type: boolean + timeout: + format: int32 + type: integer + type: object + enabled: + type: boolean + grpc: + properties: + max_conn_timeout: + format: int32 + type: integer + type: object + listen_addr: + format: int32 + type: integer + log_grpc: + type: boolean + log_json: + type: boolean + log_level: + type: string + metrics: + properties: + port: + format: int32 + type: integer + report: + type: boolean + type: object + system: + properties: + cache_max_size: + format: int64 + type: integer + cache_refresh_interval: + format: int32 + type: integer + cache_refresh_retries: + format: int32 + type: integer + cache_ttl: + format: int32 + type: integer + type: object + type: object + grafana: + properties: + address: + type: string + enabled: + type: boolean + install: + properties: + config: + properties: + env: + additionalProperties: + type: string + type: object + envSecrets: + additionalProperties: + type: string + type: object + type: object + persistence: + properties: + accessMode: + type: string + capacity: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + enabled: + type: boolean + storageClassName: + type: string + type: object + security: + properties: + enabled: + type: boolean + passphraseKey: + type: string + secretName: + type: string + usernameKey: + type: string + type: object + selfManaged: + type: boolean + service: + properties: + ingress: + properties: + contextPath: + type: string + enabled: + type: boolean + hosts: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + tls: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodePort: + format: int32 + type: integer + type: object + type: object + type: object + jaeger: + properties: + install: + properties: + ingress: + properties: + enabled: + type: boolean + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + type: object + storage: + properties: + elasticsearch: + properties: + indexCleaner: + type: object + x-kubernetes-preserve-unknown-fields: true + nodeCount: + format: int32 + type: integer + redundancyPolicy: + type: string + storage: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + memory: + properties: + maxTraces: + format: int64 + type: integer + type: object + type: + type: string + type: object + type: object + name: + type: string + type: object + kiali: + properties: + enabled: + type: boolean + install: + properties: + dashboard: + properties: + enableGrafana: + type: boolean + enablePrometheus: + type: boolean + enableTracing: + type: boolean + viewOnly: + type: boolean + type: object + deployment: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + service: + properties: + ingress: + properties: + contextPath: + type: string + enabled: + type: boolean + hosts: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + tls: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodePort: + format: int32 + type: integer + type: object + type: object + name: + type: string + type: object + prometheus: + properties: + address: + type: string + enabled: + type: boolean + install: + properties: + retention: + type: string + scrapeInterval: + type: string + selfManaged: + type: boolean + service: + properties: + ingress: + properties: + contextPath: + type: string + enabled: + type: boolean + hosts: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + tls: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodePort: + format: int32 + type: integer + type: object + useTLS: + type: boolean + type: object + metricsExpiryDuration: + type: string + scrape: + type: boolean + type: object + stackdriver: + properties: + telemetry: + properties: + accessLogging: + properties: + enabled: + type: boolean + logWindowDuration: + type: string + type: object + auth: + properties: + apiKey: + type: string + appCredentials: + type: boolean + serviceAccountPath: + type: string + type: object + configOverride: + type: object + x-kubernetes-preserve-unknown-fields: true + enableContextGraph: + type: boolean + enableLogging: + type: boolean + enableMetrics: + type: boolean + enabled: + type: boolean + type: object + tracer: + properties: + debug: + type: boolean + maxNumberOfAnnotations: + format: int64 + type: integer + maxNumberOfAttributes: + format: int64 + type: integer + maxNumberOfMessageEvents: + format: int64 + type: integer + type: object + type: object + type: object + cluster: + properties: + meshExpansion: + properties: + enabled: + type: boolean + ilbGateway: + properties: + enabled: + type: boolean + namespace: + type: string + routerMode: + type: string + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + service: + properties: + clusterIP: + type: string + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + format: int32 + type: integer + ipFamily: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + sessionAffinityConfig: + properties: + clientIP: + properties: + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + topologyKeys: + items: + type: string + type: array + type: + type: string + type: object + volumes: + items: + properties: + volume: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + type: object + volumeMount: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: object + type: array + type: object + type: object + multiCluster: + properties: + enabled: + type: boolean + meshNetworks: + additionalProperties: + properties: + endpoints: + items: + properties: + fromCIDR: + type: string + fromRegistry: + type: string + type: object + type: array + gateways: + items: + properties: + address: + type: string + port: + format: int32 + type: integer + registryServiceName: + type: string + service: + type: string + type: object + type: array + type: object + type: object + type: object + name: + type: string + network: + type: string + type: object + gateways: + properties: + additionalEgress: + additionalProperties: + properties: + enabled: + type: boolean + namespace: + type: string + requestedNetworkView: + items: + type: string + type: array + routerMode: + type: string + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + service: + properties: + clusterIP: + type: string + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + format: int32 + type: integer + ipFamily: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + sessionAffinityConfig: + properties: + clientIP: + properties: + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + topologyKeys: + items: + type: string + type: array + type: + type: string + type: object + volumes: + items: + properties: + volume: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + type: object + volumeMount: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: object + type: array + type: object + type: object + additionalIngress: + additionalProperties: + properties: + enabled: + type: boolean + namespace: + type: string + routerMode: + type: string + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + sds: + properties: + enabled: + type: boolean + runtime: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + type: object + service: + properties: + clusterIP: + type: string + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + format: int32 + type: integer + ipFamily: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + sessionAffinityConfig: + properties: + clientIP: + properties: + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + topologyKeys: + items: + type: string + type: array + type: + type: string + type: object + volumes: + items: + properties: + volume: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + type: object + volumeMount: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: object + type: array + type: object + type: object + egress: + properties: + enabled: + type: boolean + namespace: + type: string + requestedNetworkView: + items: + type: string + type: array + routerMode: + type: string + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + service: + properties: + clusterIP: + type: string + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + format: int32 + type: integer + ipFamily: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + sessionAffinityConfig: + properties: + clientIP: + properties: + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + topologyKeys: + items: + type: string + type: array + type: + type: string + type: object + volumes: + items: + properties: + volume: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + type: object + volumeMount: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: object + type: array + type: object + enabled: + type: boolean + ingress: + properties: + enabled: + type: boolean + ingress: + type: boolean + meshExpansionPorts: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + namespace: + type: string + routeConfig: + properties: + enabled: + type: boolean + type: object + routerMode: + type: string + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + sds: + properties: + enabled: + type: boolean + runtime: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + type: object + service: + properties: + clusterIP: + type: string + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + format: int32 + type: integer + ipFamily: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + sessionAffinityConfig: + properties: + clientIP: + properties: + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + topologyKeys: + items: + type: string + type: array + type: + type: string + type: object + volumes: + items: + properties: + volume: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + type: object + volumeMount: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: object + type: array + type: object + openshiftRoute: + properties: + enabled: + type: boolean + type: object + type: object + general: + properties: + logging: + properties: + componentLevels: + additionalProperties: + type: string + type: object + logAsJSON: + type: boolean + type: object + validationMessages: + type: boolean + type: object + meshConfig: + properties: + discoverySelectors: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: array + extensionProviders: + items: + properties: + envoyExtAuthzHttp: + properties: + failOpen: + type: boolean + includeAdditionalHeadersInCheck: + additionalProperties: + type: string + type: object + includeRequestBodyInCheck: + properties: + allowPartialMessage: + type: boolean + maxRequestBytes: + format: int64 + type: integer + packAsBytes: + type: boolean + type: object + includeRequestHeadersInCheck: + items: + type: string + type: array + pathPrefix: + type: string + port: + format: int64 + type: integer + service: + type: string + statusOnError: + type: string + timeout: + type: string + required: + - port + - service + type: object + name: + type: string + prometheus: + type: object + required: + - name + type: object + type: array + type: object + mode: + enum: + - MultiTenant + - ClusterWide + type: string + policy: + properties: + mixer: + properties: + adapters: + properties: + kubernetesenv: + type: boolean + useAdapterCRDs: + type: boolean + type: object + enableChecks: + type: boolean + failOpen: + type: boolean + sessionAffinity: + type: boolean + type: object + remote: + properties: + address: + type: string + createService: + type: boolean + enableChecks: + type: boolean + failOpen: + type: boolean + type: object + type: + type: string + type: object + profiles: + items: + type: string + type: array + proxy: + properties: + accessLogging: + properties: + envoyService: + properties: + address: + type: string + enabled: + type: boolean + tcpKeepalive: + properties: + interval: + type: string + probes: + format: int32 + type: integer + time: + type: string + type: object + tlsSettings: + properties: + caCertificates: + type: string + clientCertificate: + type: string + mode: + type: string + privateKey: + type: string + sni: + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + file: + properties: + encoding: + type: string + format: + type: string + name: + type: string + type: object + type: object + adminPort: + format: int32 + type: integer + concurrency: + format: int32 + type: integer + envoyMetricsService: + properties: + address: + type: string + enabled: + type: boolean + tcpKeepalive: + properties: + interval: + type: string + probes: + format: int32 + type: integer + time: + type: string + type: object + tlsSettings: + properties: + caCertificates: + type: string + clientCertificate: + type: string + mode: + type: string + privateKey: + type: string + sni: + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + injection: + properties: + alwaysInjectSelector: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: array + autoInject: + type: boolean + injectedAnnotations: + additionalProperties: + type: string + type: object + neverInjectSelector: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: array + type: object + logging: + properties: + componentLevels: + additionalProperties: + type: string + type: object + level: + type: string + type: object + networking: + properties: + clusterDomain: + type: string + connectionTimeout: + type: string + dns: + properties: + refreshRate: + type: string + searchSuffixes: + items: + type: string + type: array + type: object + initialization: + properties: + initContainer: + properties: + runtime: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + type: object + type: + type: string + type: object + maxConnectionAge: + type: string + protocol: + properties: + autoDetect: + properties: + inbound: + type: boolean + outbound: + type: boolean + timeout: + type: string + type: object + type: object + trafficControl: + properties: + inbound: + properties: + excludedPorts: + items: + format: int32 + type: integer + type: array + includedPorts: + items: + type: string + type: array + interceptionMode: + type: string + type: object + outbound: + properties: + excludedIPRanges: + items: + type: string + type: array + excludedPorts: + items: + format: int32 + type: integer + type: array + includedIPRanges: + items: + type: string + type: array + policy: + type: string + type: object + type: object + type: object + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + readiness: + properties: + failureThreshold: + format: int32 + type: integer + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + rewriteApplicationProbes: + type: boolean + statusPort: + format: int32 + type: integer + type: object + type: object + type: object + runtime: + properties: + components: + additionalProperties: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + type: object + defaults: + properties: + container: + properties: + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + podDisruption: + properties: + enabled: + type: boolean + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + pod: + properties: + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + type: object + security: + properties: + certificateAuthority: + properties: + cert-manager: + properties: + address: + type: string + pilotSecretName: + type: string + rootCAConfigMapName: + type: string + type: object + custom: + properties: + address: + type: string + type: object + istiod: + properties: + privateKey: + properties: + rootCADir: + type: string + type: object + selfSigned: + properties: + checkPeriod: + type: string + enableJitter: + type: boolean + gracePeriod: + type: string + ttl: + type: string + type: object + type: + type: string + workloadCertTTLDefault: + type: string + workloadCertTTLMax: + type: string + type: object + type: + type: string + type: object + controlPlane: + properties: + certProvider: + type: string + mtls: + type: boolean + tls: + properties: + cipherSuites: + items: + type: string + type: array + ecdhCurves: + items: + type: string + type: array + maxProtocolVersion: + type: string + minProtocolVersion: + type: string + type: object + type: object + dataPlane: + properties: + automtls: + type: boolean + mtls: + type: boolean + type: object + identity: + properties: + thirdParty: + properties: + audience: + type: string + issuer: + type: string + type: object + type: + type: string + type: object + jwksResolverCA: + type: string + manageNetworkPolicy: + type: boolean + trust: + properties: + additionalDomains: + items: + type: string + type: array + domain: + type: string + type: object + type: object + techPreview: + type: object + x-kubernetes-preserve-unknown-fields: true + telemetry: + properties: + mixer: + properties: + adapters: + properties: + kubernetesenv: + type: boolean + stdio: + properties: + enabled: + type: boolean + outputAsJSON: + type: boolean + type: object + useAdapterCRDs: + type: boolean + type: object + batching: + properties: + maxEntries: + format: int32 + type: integer + maxTime: + type: string + type: object + loadshedding: + properties: + latencyThreshold: + type: string + mode: + type: string + type: object + sessionAffinity: + type: boolean + type: object + remote: + properties: + address: + type: string + batching: + properties: + maxEntries: + format: int32 + type: integer + maxTime: + type: string + type: object + createService: + type: boolean + type: object + type: + type: string + type: object + tracing: + properties: + sampling: + format: int32 + maximum: 10000 + minimum: 0 + type: integer + type: + type: string + type: object + version: + type: string + type: object + status: + properties: + annotations: + additionalProperties: + type: string + type: object + appliedSpec: + properties: + addons: + properties: + 3scale: + properties: + backend: + properties: + cache_flush_interval: + format: int32 + type: integer + enable_cache: + type: boolean + policy_fail_closed: + type: boolean + type: object + client: + properties: + allow_insecure_connections: + type: boolean + timeout: + format: int32 + type: integer + type: object + enabled: + type: boolean + grpc: + properties: + max_conn_timeout: + format: int32 + type: integer + type: object + listen_addr: + format: int32 + type: integer + log_grpc: + type: boolean + log_json: + type: boolean + log_level: + type: string + metrics: + properties: + port: + format: int32 + type: integer + report: + type: boolean + type: object + system: + properties: + cache_max_size: + format: int64 + type: integer + cache_refresh_interval: + format: int32 + type: integer + cache_refresh_retries: + format: int32 + type: integer + cache_ttl: + format: int32 + type: integer + type: object + type: object + grafana: + properties: + address: + type: string + enabled: + type: boolean + install: + properties: + config: + properties: + env: + additionalProperties: + type: string + type: object + envSecrets: + additionalProperties: + type: string + type: object + type: object + persistence: + properties: + accessMode: + type: string + capacity: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + enabled: + type: boolean + storageClassName: + type: string + type: object + security: + properties: + enabled: + type: boolean + passphraseKey: + type: string + secretName: + type: string + usernameKey: + type: string + type: object + selfManaged: + type: boolean + service: + properties: + ingress: + properties: + contextPath: + type: string + enabled: + type: boolean + hosts: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + tls: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodePort: + format: int32 + type: integer + type: object + type: object + type: object + jaeger: + properties: + install: + properties: + ingress: + properties: + enabled: + type: boolean + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + type: object + storage: + properties: + elasticsearch: + properties: + indexCleaner: + type: object + x-kubernetes-preserve-unknown-fields: true + nodeCount: + format: int32 + type: integer + redundancyPolicy: + type: string + storage: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + memory: + properties: + maxTraces: + format: int64 + type: integer + type: object + type: + type: string + type: object + type: object + name: + type: string + type: object + kiali: + properties: + enabled: + type: boolean + install: + properties: + dashboard: + properties: + enableGrafana: + type: boolean + enablePrometheus: + type: boolean + enableTracing: + type: boolean + viewOnly: + type: boolean + type: object + deployment: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + service: + properties: + ingress: + properties: + contextPath: + type: string + enabled: + type: boolean + hosts: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + tls: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodePort: + format: int32 + type: integer + type: object + type: object + name: + type: string + type: object + prometheus: + properties: + address: + type: string + enabled: + type: boolean + install: + properties: + retention: + type: string + scrapeInterval: + type: string + selfManaged: + type: boolean + service: + properties: + ingress: + properties: + contextPath: + type: string + enabled: + type: boolean + hosts: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + tls: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodePort: + format: int32 + type: integer + type: object + useTLS: + type: boolean + type: object + metricsExpiryDuration: + type: string + scrape: + type: boolean + type: object + stackdriver: + properties: + telemetry: + properties: + accessLogging: + properties: + enabled: + type: boolean + logWindowDuration: + type: string + type: object + auth: + properties: + apiKey: + type: string + appCredentials: + type: boolean + serviceAccountPath: + type: string + type: object + configOverride: + type: object + x-kubernetes-preserve-unknown-fields: true + enableContextGraph: + type: boolean + enableLogging: + type: boolean + enableMetrics: + type: boolean + enabled: + type: boolean + type: object + tracer: + properties: + debug: + type: boolean + maxNumberOfAnnotations: + format: int64 + type: integer + maxNumberOfAttributes: + format: int64 + type: integer + maxNumberOfMessageEvents: + format: int64 + type: integer + type: object + type: object + type: object + cluster: + properties: + meshExpansion: + properties: + enabled: + type: boolean + ilbGateway: + properties: + enabled: + type: boolean + namespace: + type: string + routerMode: + type: string + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + service: + properties: + clusterIP: + type: string + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + format: int32 + type: integer + ipFamily: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + sessionAffinityConfig: + properties: + clientIP: + properties: + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + topologyKeys: + items: + type: string + type: array + type: + type: string + type: object + volumes: + items: + properties: + volume: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + type: object + volumeMount: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: object + type: array + type: object + type: object + multiCluster: + properties: + enabled: + type: boolean + meshNetworks: + additionalProperties: + properties: + endpoints: + items: + properties: + fromCIDR: + type: string + fromRegistry: + type: string + type: object + type: array + gateways: + items: + properties: + address: + type: string + port: + format: int32 + type: integer + registryServiceName: + type: string + service: + type: string + type: object + type: array + type: object + type: object + type: object + name: + type: string + network: + type: string + type: object + gateways: + properties: + additionalEgress: + additionalProperties: + properties: + enabled: + type: boolean + namespace: + type: string + requestedNetworkView: + items: + type: string + type: array + routerMode: + type: string + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + service: + properties: + clusterIP: + type: string + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + format: int32 + type: integer + ipFamily: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + sessionAffinityConfig: + properties: + clientIP: + properties: + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + topologyKeys: + items: + type: string + type: array + type: + type: string + type: object + volumes: + items: + properties: + volume: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + type: object + volumeMount: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: object + type: array + type: object + type: object + additionalIngress: + additionalProperties: + properties: + enabled: + type: boolean + namespace: + type: string + routerMode: + type: string + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + sds: + properties: + enabled: + type: boolean + runtime: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + type: object + service: + properties: + clusterIP: + type: string + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + format: int32 + type: integer + ipFamily: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + sessionAffinityConfig: + properties: + clientIP: + properties: + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + topologyKeys: + items: + type: string + type: array + type: + type: string + type: object + volumes: + items: + properties: + volume: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + type: object + volumeMount: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: object + type: array + type: object + type: object + egress: + properties: + enabled: + type: boolean + namespace: + type: string + requestedNetworkView: + items: + type: string + type: array + routerMode: + type: string + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + service: + properties: + clusterIP: + type: string + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + format: int32 + type: integer + ipFamily: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + sessionAffinityConfig: + properties: + clientIP: + properties: + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + topologyKeys: + items: + type: string + type: array + type: + type: string + type: object + volumes: + items: + properties: + volume: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + type: object + volumeMount: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: object + type: array + type: object + enabled: + type: boolean + ingress: + properties: + enabled: + type: boolean + ingress: + type: boolean + meshExpansionPorts: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + namespace: + type: string + routeConfig: + properties: + enabled: + type: boolean + type: object + routerMode: + type: string + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + sds: + properties: + enabled: + type: boolean + runtime: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + type: object + service: + properties: + clusterIP: + type: string + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + format: int32 + type: integer + ipFamily: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + x-kubernetes-list-map-keys: + - port + x-kubernetes-list-type: map + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + sessionAffinityConfig: + properties: + clientIP: + properties: + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + topologyKeys: + items: + type: string + type: array + type: + type: string + type: object + volumes: + items: + properties: + volume: + properties: + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + type: object + volumeMount: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: object + type: array + type: object + openshiftRoute: + properties: + enabled: + type: boolean + type: object + type: object + general: + properties: + logging: + properties: + componentLevels: + additionalProperties: + type: string + type: object + logAsJSON: + type: boolean + type: object + validationMessages: + type: boolean + type: object + meshConfig: + properties: + discoverySelectors: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: array + extensionProviders: + items: + properties: + envoyExtAuthzHttp: + properties: + failOpen: + type: boolean + includeAdditionalHeadersInCheck: + additionalProperties: + type: string + type: object + includeRequestBodyInCheck: + properties: + allowPartialMessage: + type: boolean + maxRequestBytes: + format: int64 + type: integer + packAsBytes: + type: boolean + type: object + includeRequestHeadersInCheck: + items: + type: string + type: array + pathPrefix: + type: string + port: + format: int64 + type: integer + service: + type: string + statusOnError: + type: string + timeout: + type: string + required: + - port + - service + type: object + name: + type: string + prometheus: + type: object + required: + - name + type: object + type: array + type: object + mode: + enum: + - MultiTenant + - ClusterWide + type: string + policy: + properties: + mixer: + properties: + adapters: + properties: + kubernetesenv: + type: boolean + useAdapterCRDs: + type: boolean + type: object + enableChecks: + type: boolean + failOpen: + type: boolean + sessionAffinity: + type: boolean + type: object + remote: + properties: + address: + type: string + createService: + type: boolean + enableChecks: + type: boolean + failOpen: + type: boolean + type: object + type: + type: string + type: object + profiles: + items: + type: string + type: array + proxy: + properties: + accessLogging: + properties: + envoyService: + properties: + address: + type: string + enabled: + type: boolean + tcpKeepalive: + properties: + interval: + type: string + probes: + format: int32 + type: integer + time: + type: string + type: object + tlsSettings: + properties: + caCertificates: + type: string + clientCertificate: + type: string + mode: + type: string + privateKey: + type: string + sni: + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + file: + properties: + encoding: + type: string + format: + type: string + name: + type: string + type: object + type: object + adminPort: + format: int32 + type: integer + concurrency: + format: int32 + type: integer + envoyMetricsService: + properties: + address: + type: string + enabled: + type: boolean + tcpKeepalive: + properties: + interval: + type: string + probes: + format: int32 + type: integer + time: + type: string + type: object + tlsSettings: + properties: + caCertificates: + type: string + clientCertificate: + type: string + mode: + type: string + privateKey: + type: string + sni: + type: string + subjectAltNames: + items: + type: string + type: array + type: object + type: object + injection: + properties: + alwaysInjectSelector: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: array + autoInject: + type: boolean + injectedAnnotations: + additionalProperties: + type: string + type: object + neverInjectSelector: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: array + type: object + logging: + properties: + componentLevels: + additionalProperties: + type: string + type: object + level: + type: string + type: object + networking: + properties: + clusterDomain: + type: string + connectionTimeout: + type: string + dns: + properties: + refreshRate: + type: string + searchSuffixes: + items: + type: string + type: array + type: object + initialization: + properties: + initContainer: + properties: + runtime: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + type: object + type: + type: string + type: object + maxConnectionAge: + type: string + protocol: + properties: + autoDetect: + properties: + inbound: + type: boolean + outbound: + type: boolean + timeout: + type: string + type: object + type: object + trafficControl: + properties: + inbound: + properties: + excludedPorts: + items: + format: int32 + type: integer + type: array + includedPorts: + items: + type: string + type: array + interceptionMode: + type: string + type: object + outbound: + properties: + excludedIPRanges: + items: + type: string + type: array + excludedPorts: + items: + format: int32 + type: integer + type: array + includedIPRanges: + items: + type: string + type: array + policy: + type: string + type: object + type: object + type: object + runtime: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + readiness: + properties: + failureThreshold: + format: int32 + type: integer + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + rewriteApplicationProbes: + type: boolean + statusPort: + format: int32 + type: integer + type: object + type: object + type: object + runtime: + properties: + components: + additionalProperties: + properties: + container: + properties: + env: + additionalProperties: + type: string + type: object + imageName: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + autoScaling: + properties: + enabled: + type: boolean + maxReplicas: + format: int32 + type: integer + minReplicas: + format: int32 + type: integer + targetCPUUtilizationPercentage: + format: int32 + type: integer + type: object + replicas: + format: int32 + type: integer + strategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object + type: object + pod: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringScheduling: + items: + properties: + key: + type: string + operator: + type: string + topologyKey: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + type: object + defaults: + properties: + container: + properties: + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + imageRegistry: + type: string + imageTag: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + type: object + deployment: + properties: + podDisruption: + properties: + enabled: + type: boolean + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + pod: + properties: + nodeSelector: + additionalProperties: + type: string + type: object + priorityClassName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + type: object + type: object + type: object + security: + properties: + certificateAuthority: + properties: + cert-manager: + properties: + address: + type: string + pilotSecretName: + type: string + rootCAConfigMapName: + type: string + type: object + custom: + properties: + address: + type: string + type: object + istiod: + properties: + privateKey: + properties: + rootCADir: + type: string + type: object + selfSigned: + properties: + checkPeriod: + type: string + enableJitter: + type: boolean + gracePeriod: + type: string + ttl: + type: string + type: object + type: + type: string + workloadCertTTLDefault: + type: string + workloadCertTTLMax: + type: string + type: object + type: + type: string + type: object + controlPlane: + properties: + certProvider: + type: string + mtls: + type: boolean + tls: + properties: + cipherSuites: + items: + type: string + type: array + ecdhCurves: + items: + type: string + type: array + maxProtocolVersion: + type: string + minProtocolVersion: + type: string + type: object + type: object + dataPlane: + properties: + automtls: + type: boolean + mtls: + type: boolean + type: object + identity: + properties: + thirdParty: + properties: + audience: + type: string + issuer: + type: string + type: object + type: + type: string + type: object + jwksResolverCA: + type: string + manageNetworkPolicy: + type: boolean + trust: + properties: + additionalDomains: + items: + type: string + type: array + domain: + type: string + type: object + type: object + techPreview: + type: object + x-kubernetes-preserve-unknown-fields: true + telemetry: + properties: + mixer: + properties: + adapters: + properties: + kubernetesenv: + type: boolean + stdio: + properties: + enabled: + type: boolean + outputAsJSON: + type: boolean + type: object + useAdapterCRDs: + type: boolean + type: object + batching: + properties: + maxEntries: + format: int32 + type: integer + maxTime: + type: string + type: object + loadshedding: + properties: + latencyThreshold: + type: string + mode: + type: string + type: object + sessionAffinity: + type: boolean + type: object + remote: + properties: + address: + type: string + batching: + properties: + maxEntries: + format: int32 + type: integer + maxTime: + type: string + type: object + createService: + type: boolean + type: object + type: + type: string + type: object + tracing: + properties: + sampling: + format: int32 + maximum: 10000 + minimum: 0 + type: integer + type: + type: string + type: object + version: + type: string + type: object + appliedValues: + properties: + istio: + type: object + x-kubernetes-preserve-unknown-fields: true + networkType: + type: string + profiles: + items: + type: string + type: array + template: + type: string + threeScale: + type: object + x-kubernetes-preserve-unknown-fields: true + version: + type: string + type: object + chartVersion: + type: string + components: + items: + properties: + children: + items: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + type: object + type: array + type: object + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + type: object + type: array + resource: + type: string + type: object + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + type: object + type: array + observedGeneration: + format: int64 + type: integer + operatorVersion: + type: string + readiness: + properties: + components: + additionalProperties: + items: + type: string + type: array + type: object + type: object + required: + - readiness + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/tests/integration/servicemesh/service_mesh_setup_features_int_test.go b/tests/integration/servicemesh/service_mesh_setup_features_int_test.go new file mode 100644 index 00000000000..28bd6783f46 --- /dev/null +++ b/tests/integration/servicemesh/service_mesh_setup_features_int_test.go @@ -0,0 +1,330 @@ +package servicemesh_test + +import ( + "context" + "path" + "time" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + + dsciv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + infrav1 "github.com/opendatahub-io/opendatahub-operator/v2/infrastructure/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature/servicemesh" + "github.com/opendatahub-io/opendatahub-operator/v2/pkg/gvr" + "github.com/opendatahub-io/opendatahub-operator/v2/tests/envtestutil" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +const ( + timeout = 5 * time.Second + interval = 250 * time.Millisecond + authorinoName = "authorino" + authorinoNamespace = "test-provider" +) + +var _ = Describe("Data Science Project Migration", func() { + + var ( + objectCleaner *envtestutil.Cleaner + dsci *dsciv1.DSCInitialization + ) + + BeforeEach(func() { + objectCleaner = envtestutil.CreateCleaner(envTestClient, envTest.Config, timeout, interval) + dsci = newDSCInitialization("default") + }) + + It("should migrate single namespace", func() { + // given + dataScienceNs := createDataScienceProject("dsp-01") + regularNs := createNamespace("non-dsp") + Expect(envTestClient.Create(context.Background(), dataScienceNs)).To(Succeed()) + Expect(envTestClient.Create(context.Background(), regularNs)).To(Succeed()) + defer objectCleaner.DeleteAll(dataScienceNs, regularNs) + + handler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + return feature.CreateFeature("datascience-project-migration"). + For(handler). + UsingConfig(envTest.Config). + WithResources(servicemesh.MigratedDataScienceProjects).Load() + }) + + // when + Expect(handler.Apply()).To(Succeed()) + + // then + Eventually(findMigratedNamespaces). + WithTimeout(timeout). + WithPolling(interval). + Should( + And( + HaveLen(1), + ContainElement("dsp-01"), + ), + ) + }) + + It("should not migrate any non-datascience namespace", func() { + // given + regularNs := createNamespace("non-dsp") + Expect(envTestClient.Create(context.Background(), regularNs)).To(Succeed()) + defer objectCleaner.DeleteAll(regularNs) + + handler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + return feature.CreateFeature("datascience-project-migration"). + For(handler). + UsingConfig(envTest.Config). + WithResources(servicemesh.MigratedDataScienceProjects).Load() + }) + + // when + Expect(handler.Apply()).To(Succeed()) + + // then + Consistently(findMigratedNamespaces). + WithTimeout(timeout). + WithPolling(interval). + Should(BeEmpty()) // we can't wait forever, but this should be good enough ;) + }) + + It("should migrate multiple namespaces", func() { + // given + dataScienceNs01 := createDataScienceProject("dsp-01") + dataScienceNs02 := createDataScienceProject("dsp-02") + dataScienceNs03 := createDataScienceProject("dsp-03") + regularNs := createNamespace("non-dsp") + Expect(envTestClient.Create(context.Background(), dataScienceNs01)).To(Succeed()) + Expect(envTestClient.Create(context.Background(), dataScienceNs02)).To(Succeed()) + Expect(envTestClient.Create(context.Background(), dataScienceNs03)).To(Succeed()) + Expect(envTestClient.Create(context.Background(), regularNs)).To(Succeed()) + defer objectCleaner.DeleteAll(dataScienceNs01, dataScienceNs02, dataScienceNs03, regularNs) + + handler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + return feature.CreateFeature("datascience-project-migration"). + For(handler). + UsingConfig(envTest.Config). + WithResources(servicemesh.MigratedDataScienceProjects).Load() + }) + + // when + Expect(handler.Apply()).To(Succeed()) + + // then + Eventually(findMigratedNamespaces). + WithTimeout(timeout). + WithPolling(interval). + Should( + And( + HaveLen(3), + ContainElements("dsp-01", "dsp-02", "dsp-03"), + ), + ) + }) + +}) + +var _ = Describe("Cleanup operations", func() { + + // TODO combine with others + Context("configuring control plane for auth(z)", func() { + + var ( + objectCleaner *envtestutil.Cleaner + dsci *dsciv1.DSCInitialization + serviceMeshSpec *infrav1.ServiceMeshSpec + namespace = "test" + name = "minimal" + ) + + BeforeEach(func() { + objectCleaner = envtestutil.CreateCleaner(envTestClient, envTest.Config, timeout, interval) + + dsci = newDSCInitialization(namespace) + + serviceMeshSpec = &dsci.Spec.ServiceMesh + + serviceMeshSpec.ControlPlane.Name = name + serviceMeshSpec.ControlPlane.Namespace = namespace + }) + + It("should be able to remove external provider on cleanup", func() { + // given + ns := createNamespace(namespace) + Expect(envTestClient.Create(context.Background(), ns)).To(Succeed()) + defer objectCleaner.DeleteAll(ns) + + serviceMeshSpec.Auth.Namespace = authorinoNamespace + serviceMeshSpec.Auth.Authorino.Name = authorinoName + + createServiceMeshControlPlane(name, namespace) + + handler := feature.ClusterFeaturesHandler(dsci, func(handler *feature.FeaturesHandler) error { + return feature.CreateFeature("control-plane-with-external-authz-provider"). + For(handler). + Manifests(path.Join(feature.AuthDir, "mesh-authz-ext-provider.patch.tmpl")). + OnDelete(servicemesh.RemoveExtensionProvider). + UsingConfig(envTest.Config). + Load() + }) + + // when + By("verifying extension provider has been added after applying feature", func() { + Expect(handler.Apply()).To(Succeed()) + serviceMeshControlPlane, err := getServiceMeshControlPlane(envTest.Config, namespace, name) + Expect(err).ToNot(HaveOccurred()) + + extensionProviders, found, err := unstructured.NestedSlice(serviceMeshControlPlane.Object, "spec", "techPreview", "meshConfig", "extensionProviders") + Expect(err).ToNot(HaveOccurred()) + Expect(found).To(BeTrue()) + + // TODO rework to nested fields + extensionProvider := extensionProviders[0].(map[string]interface{}) + Expect(extensionProvider["name"]).To(Equal("test-odh-auth-provider")) + Expect(extensionProvider["envoyExtAuthzGrpc"].(map[string]interface{})["service"]).To(Equal("authorino-authorino-authorization.test-provider.svc.cluster.local")) + }) + + // then + By("verifying that extension provider has been removed", func() { + Expect(handler.Delete()).To(Succeed()) + Eventually(func() []interface{} { + + serviceMeshControlPlane, err := getServiceMeshControlPlane(envTest.Config, namespace, name) + Expect(err).ToNot(HaveOccurred()) + + extensionProviders, found, err := unstructured.NestedSlice(serviceMeshControlPlane.Object, "spec", "techPreview", "meshConfig", "extensionProviders") + Expect(err).ToNot(HaveOccurred()) + Expect(found).To(BeTrue()) + return extensionProviders + + }).WithTimeout(timeout).WithPolling(interval).Should(BeEmpty()) + }) + + }) + + }) + +}) + +func createServiceMeshControlPlane(name, namespace string) { + serviceMeshControlPlane := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "maistra.io/v2", + "kind": "ServiceMeshControlPlane", + "metadata": map[string]interface{}{ + "name": name, + "namespace": namespace, + }, + "spec": map[string]interface{}{}, + }, + } + Expect(createSMCPInCluster(envTest.Config, serviceMeshControlPlane, namespace)).To(Succeed()) +} + +func createDataScienceProject(name string) *v1.Namespace { + namespace := createNamespace(name) + namespace.Labels = map[string]string{ + "opendatahub.io/dashboard": "true", + } + return namespace +} + +func createNamespace(name string) *v1.Namespace { + return &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } +} + +func findMigratedNamespaces() []string { + namespaces := &v1.NamespaceList{} + var ns []string + if err := envTestClient.List(context.Background(), namespaces); err != nil && !errors.IsNotFound(err) { + Fail(err.Error()) + } + for _, namespace := range namespaces.Items { + if _, ok := namespace.ObjectMeta.Annotations["opendatahub.io/service-mesh"]; ok { + ns = append(ns, namespace.Name) + } + } + return ns +} + +func newDSCInitialization(ns string) *dsciv1.DSCInitialization { + return &dsciv1.DSCInitialization{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default-dsci", + }, + Spec: dsciv1.DSCInitializationSpec{ + ApplicationsNamespace: ns, + }, + } +} + +// createSMCPInCluster uses dynamic client to create a dummy SMCP resource for testing. +func createSMCPInCluster(cfg *rest.Config, smcpObj *unstructured.Unstructured, namespace string) error { + dynamicClient, err := dynamic.NewForConfig(cfg) + if err != nil { + return err + } + + result, err := dynamicClient.Resource(gvr.SMCP).Namespace(namespace).Create(context.TODO(), smcpObj, metav1.CreateOptions{}) + if err != nil { + return err + } + + statusConditions := []interface{}{ + map[string]interface{}{ + "type": "Ready", + "status": "True", + }, + } + + // Since we don't have actual service mesh operator deployed, we simulate the status + status := map[string]interface{}{ + "conditions": statusConditions, + "readiness": map[string]interface{}{ + "components": map[string]interface{}{ + "pending": []interface{}{}, + "ready": []interface{}{ + "istiod", + "ingress-gateway", + }, + "unready": []interface{}{}, + }, + }, + } + + if err := unstructured.SetNestedField(result.Object, status, "status"); err != nil { + return err + } + + _, err = dynamicClient.Resource(gvr.SMCP).Namespace(namespace).UpdateStatus(context.TODO(), result, metav1.UpdateOptions{}) + if err != nil { + return err + } + + return nil +} + +func getServiceMeshControlPlane(cfg *rest.Config, namespace, name string) (*unstructured.Unstructured, error) { + dynamicClient, err := dynamic.NewForConfig(cfg) + if err != nil { + return nil, err + } + + smcp, err := dynamicClient.Resource(gvr.SMCP).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + return smcp, nil +} diff --git a/tests/integration/servicemesh/service_mesh_suite_int_test.go b/tests/integration/servicemesh/service_mesh_suite_int_test.go new file mode 100644 index 00000000000..8b8edb0a8c8 --- /dev/null +++ b/tests/integration/servicemesh/service_mesh_suite_int_test.go @@ -0,0 +1,82 @@ +package servicemesh_test + +import ( + "context" + "fmt" + "math/rand" + "path/filepath" + "testing" + "time" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + "github.com/opendatahub-io/opendatahub-operator/v2/tests/envtestutil" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var ( + envTestClient client.Client + envTest *envtest.Environment + ctx context.Context + cancel context.CancelFunc +) + +var testScheme = runtime.NewScheme() + +func TestServiceMeshSetupIntegration(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Openshift Service Mesh infra setup integration") +} + +var _ = BeforeSuite(func() { + rand.Seed(time.Now().UTC().UnixNano()) + + ctx, cancel = context.WithCancel(context.TODO()) + + opts := zap.Options{Development: true} + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseFlagOptions(&opts))) + + By("Bootstrapping k8s test environment") + projectDir, err := envtestutil.FindProjectRoot() + if err != nil { + fmt.Printf("Error finding project root: %v\n", err) + return + } + + utilruntime.Must(v1.AddToScheme(testScheme)) + + envTest = &envtest.Environment{ + CRDInstallOptions: envtest.CRDInstallOptions{ + Scheme: testScheme, + Paths: []string{ + filepath.Join(projectDir, "config", "crd", "bases"), + filepath.Join(projectDir, "config", "crd", "dashboard-crds"), + filepath.Join(projectDir, "tests", "integration", "servicemesh", "crd"), + }, + ErrorIfPathMissing: true, + CleanUpAfterUse: false, + }, + } + + config, err := envTest.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(config).NotTo(BeNil()) + + envTestClient, err = client.New(config, client.Options{Scheme: testScheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(envTestClient).NotTo(BeNil()) +}) + +var _ = AfterSuite(func() { + By("Tearing down the test environment") + cancel() + Expect(envTest.Stop()).To(Succeed()) +}) diff --git a/tests/integration/servicemesh/test-templates/authorino/mesh-authz-ext-provider.patch.tmpl b/tests/integration/servicemesh/test-templates/authorino/mesh-authz-ext-provider.patch.tmpl new file mode 100644 index 00000000000..263676cb6a0 --- /dev/null +++ b/tests/integration/servicemesh/test-templates/authorino/mesh-authz-ext-provider.patch.tmpl @@ -0,0 +1,13 @@ +apiVersion: maistra.io/v2 +kind: ServiceMeshControlPlane +metadata: + name: {{ .ControlPlane.Name }} + namespace: {{ .ControlPlane.Namespace }} +spec: + techPreview: + meshConfig: + extensionProviders: + - name: {{ .AppNamespace }}-odh-different-auth-provider + envoyExtAuthzGrpc: + service: {{ .Auth.Authorino.Name }}-authorino-authorization.{{ .Auth.Namespace }}.svc.cluster.local + port: 50051