From acbf0b82e56d532b82f160d397076d7b472d90ae Mon Sep 17 00:00:00 2001 From: Akshay Iyyadurai Balasundaram Date: Mon, 27 Jan 2025 14:23:35 +0100 Subject: [PATCH 1/7] refactor(conditions): remove deprecated NoHelmChartTestFailures condition and related tests Signed-off-by: Akshay Iyyadurai Balasundaram --- pkg/apis/greenhouse/v1alpha1/conditions.go | 13 ----- .../greenhouse/v1alpha1/conditions_test.go | 56 +------------------ pkg/apis/greenhouse/v1alpha1/plugin_types.go | 3 - 3 files changed, 1 insertion(+), 71 deletions(-) diff --git a/pkg/apis/greenhouse/v1alpha1/conditions.go b/pkg/apis/greenhouse/v1alpha1/conditions.go index ca3a94f0b..9ede76b86 100644 --- a/pkg/apis/greenhouse/v1alpha1/conditions.go +++ b/pkg/apis/greenhouse/v1alpha1/conditions.go @@ -4,8 +4,6 @@ package v1alpha1 import ( - "slices" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -135,10 +133,6 @@ func (sc *StatusConditions) SetConditions(conditionsToSet ...Condition) { break } - // Remove the deprecated NoHelmChartTestFailures condition if it exists - if currentCondition.Type == NoHelmChartTestFailuresCondition { - sc.Conditions = removeCondition(sc.Conditions, NoHelmChartTestFailuresCondition) - } } // if the condition does not exist, append it if !exists { @@ -147,13 +141,6 @@ func (sc *StatusConditions) SetConditions(conditionsToSet ...Condition) { } } -// removeCondition removes the condition with the given type from the list of conditions. -func removeCondition(conditions []Condition, conditionType ConditionType) []Condition { - return slices.DeleteFunc(conditions, func(c Condition) bool { - return c.Type == conditionType - }) -} - // GetConditionByType returns the condition of the given type, if it exists. func (sc *StatusConditions) GetConditionByType(conditionType ConditionType) *Condition { if len(sc.Conditions) == 0 { diff --git a/pkg/apis/greenhouse/v1alpha1/conditions_test.go b/pkg/apis/greenhouse/v1alpha1/conditions_test.go index 46e8c9fce..6a0319dc5 100644 --- a/pkg/apis/greenhouse/v1alpha1/conditions_test.go +++ b/pkg/apis/greenhouse/v1alpha1/conditions_test.go @@ -353,59 +353,5 @@ var _ = Describe("Test conditions util functions", func() { Expect(condition1.Equal(condition2)).To(BeTrue()) }) - - It("should remove the deprecated NoHelmChartTestFailures condition", func() { - By("removing the deprecated NoHelmChartTestFailures condition") - noHelmChartTestFailuresCondition := greenhousev1alpha1.Condition{ - Type: greenhousev1alpha1.NoHelmChartTestFailuresCondition, - Status: metav1.ConditionTrue, - LastTransitionTime: timeNow, - Message: "test", - } - - existingCondition1 := greenhousev1alpha1.Condition{ - Type: greenhousev1alpha1.HelmReconcileFailedCondition, - Status: metav1.ConditionFalse, - LastTransitionTime: timeNow, - Message: "test", - } - - existingCondition2 := greenhousev1alpha1.Condition{ - Type: greenhousev1alpha1.HelmDriftDetectedCondition, - Status: metav1.ConditionTrue, - LastTransitionTime: timeNow, - Message: "test", - } - - statusConditions := greenhousev1alpha1.StatusConditions{ - Conditions: []greenhousev1alpha1.Condition{noHelmChartTestFailuresCondition, existingCondition1, existingCondition2}, - } - - newCondition1 := greenhousev1alpha1.Condition{ - Type: greenhousev1alpha1.HelmChartTestSucceededCondition, - Status: metav1.ConditionTrue, - LastTransitionTime: timeNow, - Message: "test", - } - - newCondition2 := greenhousev1alpha1.Condition{ - Type: greenhousev1alpha1.ReadyCondition, - Status: metav1.ConditionTrue, - LastTransitionTime: metav1.NewTime(metav1.Now().AddDate(0, 0, -1)), - Message: "test", - } - - // Set new condition - statusConditions.SetConditions(newCondition1) - statusConditions.SetConditions(newCondition2) - - // Check if the deprecated condition is removed - Expect(statusConditions.GetConditionByType(greenhousev1alpha1.NoHelmChartTestFailuresCondition)).To(BeNil()) - - // Check if the new condition is added - condition := statusConditions.GetConditionByType(greenhousev1alpha1.HelmChartTestSucceededCondition) - Expect(condition).NotTo(BeNil()) - Expect(condition.Status).To(Equal(metav1.ConditionTrue)) - Expect(condition.LastTransitionTime).To(Equal(timeNow)) - }) + }) diff --git a/pkg/apis/greenhouse/v1alpha1/plugin_types.go b/pkg/apis/greenhouse/v1alpha1/plugin_types.go index a33c5def3..1315443ef 100644 --- a/pkg/apis/greenhouse/v1alpha1/plugin_types.go +++ b/pkg/apis/greenhouse/v1alpha1/plugin_types.go @@ -67,9 +67,6 @@ const ( // StatusUpToDateCondition reflects the failed reconciliation of the Plugin. StatusUpToDateCondition ConditionType = "StatusUpToDate" - // Deprecated: NoHelmChartTestFailuresCondition reflects the status of the HelmChart tests. - NoHelmChartTestFailuresCondition ConditionType = "NoHelmChartTestFailures" - // HelmChartTestSucceededCondition reflects the status of the HelmChart tests. HelmChartTestSucceededCondition ConditionType = "HelmChartTestSucceeded" From 586c910594782db326575fcf2869549e3b1ed1ed Mon Sep 17 00:00:00 2001 From: Akshay Iyyadurai Balasundaram Date: Wed, 29 Jan 2025 11:34:43 +0100 Subject: [PATCH 2/7] fix(service-proxy): rewrite logic + a lot of tests Signed-off-by: Akshay Iyyadurai Balasundaram --- cmd/service-proxy/proxy.go | 54 +++++++++---- cmd/service-proxy/proxy_test.go | 135 +++++++++++++++++++++++++++----- 2 files changed, 154 insertions(+), 35 deletions(-) diff --git a/cmd/service-proxy/proxy.go b/cmd/service-proxy/proxy.go index d2ae3d0ec..e600ad845 100644 --- a/cmd/service-proxy/proxy.go +++ b/cmd/service-proxy/proxy.go @@ -25,6 +25,7 @@ import ( "net/http/httputil" "net/url" "regexp" + "strings" "sync" "github.com/go-logr/logr" @@ -190,46 +191,69 @@ func (pm *ProxyManager) RoundTrip(req *http.Request) (resp *http.Response, err e resp, err = cls.transport.RoundTrip(req) // errors are logged by pm.Errorhandler if err == nil { - log.FromContext(req.Context()).Info("Forwarded request", "status", resp.StatusCode, "upstream", req.URL.String()) + log.FromContext(req.Context()).Info("Forwarded request", "status", resp.StatusCode, "upstreamServiceRouteURL", req.URL.String()) } return } -// rewrite rewrites the request to the backend URL and sets the cluster in the context to be used by RoundTrip to identify the correct transport func (pm *ProxyManager) rewrite(req *httputil.ProxyRequest) { req.SetXForwarded() - l := pm.logger.WithValues("host", req.In.Host, "url", req.In.URL.String(), "method", req.In.Method) + // Create a logger with relevant request details + l := pm.logger.WithValues( + "incomingHost", req.In.Host, + "incomingRequestURL", req.In.URL.String(), + "incomingMethod", req.In.Method, + ) - // inject current logger into context before returning - defer func() { - req.Out = req.Out.WithContext(log.IntoContext(req.Out.Context(), l)) - }() - - // hostname is expected to have the format specified in ./pkg/common/url.go + // Extract cluster from the incoming request host cluster, err := common.ExtractCluster(req.In.Host) if err != nil { + l.Error(err, "Failed to extract cluster from host", "host", req.In.Host) + req.Out = req.Out.WithContext(log.IntoContext(req.Out.Context(), l)) return } + // Retrieve the upstream service route for the cluster route, ok := pm.GetClusterRoute(cluster, "https://"+req.In.Host) if !ok { - l.Info("No route found for cluster and URL", "cluster", cluster, "url", req.In.URL.String()) + l.Info("No route found for cluster and URL", "cluster", cluster, "incomingRequestURL", req.In.URL.String()) + req.Out = req.Out.WithContext(log.IntoContext(req.Out.Context(), l)) return } - backendURL := route.url - // set cluster in context - ctx := context.WithValue(req.Out.Context(), contextClusterKey{}, cluster) + upstreamServiceRouteURL := route.url - l.WithValues("cluster", cluster, "namespace", route.namespace, "name", route.serviceName) + // Ensure the outgoing request URL is properly updated + if !strings.HasPrefix(req.Out.URL.Path, upstreamServiceRouteURL.Path) { + // Append the original request path to the upstream service route URL path + req.Out.URL.Path = strings.TrimSuffix(upstreamServiceRouteURL.Path, "/") + req.Out.URL.Path + } + + // Set the correct upstream service route URL details + req.Out.URL.Scheme = upstreamServiceRouteURL.Scheme + req.Out.URL.Host = upstreamServiceRouteURL.Host + + // Inject the cluster into the outgoing request context + ctx := context.WithValue(req.Out.Context(), contextClusterKey{}, cluster) + ctx = log.IntoContext(ctx, l) req.Out = req.Out.WithContext(ctx) - req.SetURL(backendURL) + + // Log the successful rewrite for debugging purposes + l.Info("Request rewrite completed", + "cluster", cluster, + "namespace", route.namespace, + "serviceName", route.serviceName, + "upstreamServiceRouteURL", req.Out.URL.String(), + ) } // modifyResponse strips the k8s API server proxy path prepended to the location header during redirects: // https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/proxy/transport.go#L113 func (pm *ProxyManager) modifyResponse(resp *http.Response) error { + logger := log.FromContext(resp.Request.Context()) + logger.Info("Modifying response", "statusCode", resp.StatusCode, "originalLocation", resp.Header.Get("Location")) + if location := resp.Header.Get("Location"); location != "" { location = apiServerProxyPathRegex.ReplaceAllString(location, "/") resp.Header.Set("Location", location) diff --git a/cmd/service-proxy/proxy_test.go b/cmd/service-proxy/proxy_test.go index cb728fd87..19c0fe510 100644 --- a/cmd/service-proxy/proxy_test.go +++ b/cmd/service-proxy/proxy_test.go @@ -10,7 +10,6 @@ import ( "net/url" "testing" - "github.com/go-logr/logr" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -20,6 +19,7 @@ import ( greenhouseapis "github.com/cloudoperators/greenhouse/pkg/apis" greenhousev1alpha1 "github.com/cloudoperators/greenhouse/pkg/apis/greenhouse/v1alpha1" "github.com/cloudoperators/greenhouse/pkg/test" + "github.com/go-logr/logr" ) // TestRewrite tests the rewrite function of the proxy manager. @@ -27,63 +27,91 @@ import ( // If checks if he url is properly rewritten and the request context contains the cluster name // and a logger with the correct values. func TestRewrite(t *testing.T) { - proxyURL, err := url.Parse("https://apiserver/proxy/url") + proxyURL, err := url.Parse("https://api.blueprints.greenhouse.shoot.canary.k8s-hana.ondemand.com/api/v1/namespaces/kube-monitoring/services/test-service:8080") if err != nil { - t.Fatal("failed to parse proxy url") + t.Fatal("failed to parse proxy URL") } tests := []struct { - name string - url string - expectedURL string - contextVal any + name string + url string + expectedupstreamServiceRouteURL string + contextVal any }{ { - name: "valid host", - url: "https://cluster--1234567.organisation.basedomain/abcd", - expectedURL: "https://apiserver/proxy/url/abcd", - contextVal: "cluster", + name: "valid host with path", + url: "https://cluster--1234567.organisation.basedomain/dashboard", + expectedupstreamServiceRouteURL: "https://api.blueprints.greenhouse.shoot.canary.k8s-hana.ondemand.com/api/v1/namespaces/kube-monitoring/services/test-service:8080/dashboard", + contextVal: "cluster", + }, + { + name: "valid host with deeper path", + url: "https://cluster--1234567.organisation.basedomain/api/resource", + expectedupstreamServiceRouteURL: "https://api.blueprints.greenhouse.shoot.canary.k8s-hana.ondemand.com/api/v1/namespaces/kube-monitoring/services/test-service:8080/api/resource", + contextVal: "cluster", + }, + { + name: "valid host with already prefixed path", + url: "https://cluster--1234567.organisation.basedomain/api/v1/namespaces/kube-monitoring/services/test-service:8080/existing-path", + expectedupstreamServiceRouteURL: "https://api.blueprints.greenhouse.shoot.canary.k8s-hana.ondemand.com/api/v1/namespaces/kube-monitoring/services/test-service:8080/existing-path", + contextVal: "cluster", }, { - name: "invalid host", - url: "https://something.organisation.basedomain/abcd", - expectedURL: "https://something.organisation.basedomain/abcd", - contextVal: nil, + name: "unknown cluster request", + url: "https://unknown-cluster.organisation.basedomain/dashboard", + expectedupstreamServiceRouteURL: "https://unknown-cluster.organisation.basedomain/dashboard", // No rewrite expected + contextVal: nil, + }, + { + name: "invalid host format", + url: "https://something.organisation.basedomain/abcd", + expectedupstreamServiceRouteURL: "https://something.organisation.basedomain/abcd", // No rewrite expected + contextVal: nil, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { inputURL, err := url.Parse(tt.url) if err != nil { - t.Fatal("failed to parse url") + t.Fatal("failed to parse URL") } + pm := NewProxyManager() pm.clusters["cluster"] = clusterRoutes{ routes: map[string]route{ inputURL.Scheme + "://" + inputURL.Host: { url: proxyURL, - namespace: "namespace", - serviceName: "test", + namespace: "kube-monitoring", + serviceName: "test-service", }, }, } + r, err := http.NewRequestWithContext(context.Background(), http.MethodGet, inputURL.String(), http.NoBody) if err != nil { t.Fatal("failed to create request") return } + req := httputil.ProxyRequest{ In: r, Out: r.Clone(r.Context()), } + pm.rewrite(&req) + // Ensure logger is propagated if _, err := logr.FromContext(req.Out.Context()); err != nil { t.Error("expected logger in outgoing request context") } - if req.Out.URL.String() != tt.expectedURL { - t.Errorf("expected url %s, got %s", tt.expectedURL, req.Out.URL.String()) + + // Validate the rewritten URL + if req.Out.URL.String() != tt.expectedupstreamServiceRouteURL { + t.Errorf("expected URL %s, got %s", tt.expectedupstreamServiceRouteURL, req.Out.URL.String()) } + + // Validate the cluster context if req.Out.Context().Value(contextClusterKey{}) != tt.contextVal { t.Errorf("expected cluster %s in context, got %s", "cluster", req.Out.Context().Value(contextClusterKey{})) } @@ -185,6 +213,73 @@ users: } } +func TestModifyResponse(t *testing.T) { + tests := []struct { + name string + locationHeader string + expectedLocation string + expectHeaderChange bool + }{ + { + name: "Valid input with proxy paths", + locationHeader: "/api/v1/namespaces/kube-monitoring/services/test-service:8080/proxy/api/main.js", + expectedLocation: "/api/main.js", + expectHeaderChange: true, + }, + { + name: "Single proxy path", + locationHeader: "/api/v1/namespaces/kube-monitoring/services/test-service:8080/proxy/", + expectedLocation: "/", + expectHeaderChange: true, + }, + { + name: "No match in location header", + locationHeader: "/other/path/that/does/not/match", + expectedLocation: "/other/path/that/does/not/match", + expectHeaderChange: false, + }, + { + name: "Empty location header", + locationHeader: "", + expectedLocation: "", + expectHeaderChange: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Prepare response + resp := &http.Response{ + Header: http.Header{ + "Location": []string{tt.locationHeader}, + }, + Request: &http.Request{}, + } + + // Call modifyResponse + pm := NewProxyManager() // Assuming NewProxyManager is implemented + err := pm.modifyResponse(resp) + + // Check for errors + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Validate the location header + location := resp.Header.Get("Location") + if location != tt.expectedLocation { + t.Errorf("expected location %s, got %s", tt.expectedLocation, location) + } + + // Validate whether the header was modified + headerChanged := location != tt.locationHeader + if headerChanged != tt.expectHeaderChange { + t.Errorf("expected header change: %v, got: %v", tt.expectHeaderChange, headerChanged) + } + }) + } +} + // helper function to create string pointer func pointer(s string) *string { return &s From e1168754b548dce1c1f724571260b52c20fa9b65 Mon Sep 17 00:00:00 2001 From: Cloud Operator <169066274+cloud-operator@users.noreply.github.com> Date: Wed, 29 Jan 2025 12:16:19 +0000 Subject: [PATCH 3/7] Automatic generation of CRD API Docs --- docs/reference/api/openapi.yaml | 1574 +++++++++++++++---------------- 1 file changed, 787 insertions(+), 787 deletions(-) diff --git a/docs/reference/api/openapi.yaml b/docs/reference/api/openapi.yaml index 3d36d7eb3..604916287 100755 --- a/docs/reference/api/openapi.yaml +++ b/docs/reference/api/openapi.yaml @@ -4,64 +4,64 @@ info: version: main description: PlusOne operations platform paths: - /TeamMembership: - post: - responses: - default: - description: TeamMembership - /Plugin: + /ClusterKubeconfig: post: responses: default: - description: Plugin - /Organization: + description: ClusterKubeconfig + /PluginDefinition: post: responses: default: - description: Organization - /PluginDefinition: + description: PluginDefinition + /Plugin: post: responses: default: - description: PluginDefinition - /PluginPreset: + description: Plugin + /Team: post: responses: default: - description: PluginPreset - /ClusterKubeconfig: + description: Team + /TeamMembership: post: responses: default: - description: ClusterKubeconfig + description: TeamMembership /TeamRoleBinding: post: responses: default: description: TeamRoleBinding - /Team: + /Organization: post: responses: default: - description: Team - /Cluster: + description: Organization + /PluginPreset: post: responses: default: - description: Cluster + description: PluginPreset /TeamRole: post: responses: default: description: TeamRole + /Cluster: + post: + responses: + default: + description: Cluster components: schemas: - TeamMembership: + ClusterKubeconfig: xml: name: greenhouse.sap namespace: v1alpha1 - title: TeamMembership - description: TeamMembership is the Schema for the teammemberships API + title: ClusterKubeconfig + description: ClusterKubeconfig is the Schema for the clusterkubeconfigs API\nObjectMeta.OwnerReferences is used to link the ClusterKubeconfig to the Cluster\nObjectMeta.Generation is used to detect changes in the ClusterKubeconfig and sync local kubeconfig files\nObjectMeta.Name is designed to be the same with the Cluster name properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' @@ -72,46 +72,94 @@ components: metadata: type: object spec: - description: TeamMembershipSpec defines the desired state of TeamMembership + description: ClusterKubeconfigSpec stores the kubeconfig data for the cluster\nThe idea is to use kubeconfig data locally with minimum effort (with local tools or plain kubectl):\nkubectl get cluster-kubeconfig $NAME -o yaml | yq -y .spec.kubeconfig properties: - members: - description: Members list users that are part of a team. - items: - description: User specifies a human person. - properties: - email: - description: Email of the user. - type: string - firstName: - description: FirstName of the user. - type: string - id: - description: ID is the unique identifier of the user. - type: string - lastName: - description: LastName of the user. - type: string - required: - - email - - firstName - - id - - lastName - type: object - type: array + kubeconfig: + description: 'ClusterKubeconfigData stores the kubeconfig data ready to use kubectl or other local tooling\nIt is a simplified version of clientcmdapi.Config: https://pkg.go.dev/k8s.io/client-go/tools/clientcmd/api#Config' + properties: + apiVersion: + type: string + clusters: + items: + properties: + cluster: + properties: + certificate-authority-data: + format: byte + type: string + server: + type: string + type: object + name: + type: string + required: + - cluster + - name + type: object + type: array + contexts: + items: + properties: + context: + properties: + cluster: + type: string + namespace: + type: string + user: + type: string + required: + - cluster + - user + type: object + name: + type: string + required: + - name + type: object + type: array + current-context: + type: string + kind: + type: string + preferences: + type: object + users: + items: + properties: + name: + type: string + user: + properties: + auth-provider: + description: AuthProviderConfig holds the configuration for a specified auth provider. + properties: + config: + additionalProperties: + type: string + type: object + name: + type: string + required: + - name + type: object + client-certificate-data: + format: byte + type: string + client-key-data: + format: byte + type: string + type: object + required: + - name + type: object + type: array + type: object type: object status: - description: TeamMembershipStatus defines the observed state of TeamMembership properties: - lastSyncedTime: - description: LastSyncedTime is the information when was the last time the membership was synced - format: date-time - type: string - lastUpdateTime: - description: LastChangedTime is the information when was the last time the membership was actually changed - format: date-time - type: string statusConditions: - description: StatusConditions contain the different conditions that constitute the status of the TeamMembership. + description: A StatusConditions contains a list of conditions.\nOnly one condition of a given type may exist in the list. properties: conditions: items: @@ -145,12 +193,12 @@ components: type: object type: object type: object - Plugin: + PluginDefinition: xml: name: greenhouse.sap namespace: v1alpha1 - title: Plugin - description: Plugin is the Schema for the plugins API + title: PluginDefinition + description: PluginDefinition is the Schema for the PluginDefinitions API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' @@ -161,91 +209,19 @@ components: metadata: type: object spec: - description: PluginSpec defines the desired state of Plugin + description: PluginDefinitionSpec defines the desired state of PluginDefinitionSpec properties: - clusterName: - description: ClusterName is the name of the cluster the plugin is deployed to. If not set, the plugin is deployed to the greenhouse cluster. + description: + description: Description provides additional details of the pluginDefinition. type: string - disabled: - description: Disabled indicates that the plugin is administratively disabled. - type: boolean displayName: - description: DisplayName is an optional name for the Plugin to be displayed in the Greenhouse UI.\nThis is especially helpful to distinguish multiple instances of a PluginDefinition in the same context.\nDefaults to a normalized version of metadata.name. - type: string - optionValues: - description: Values are the values for a PluginDefinition instance. - items: - description: PluginOptionValue is the value for a PluginOption. - properties: - name: - description: Name of the values. - type: string - value: - description: Value is the actual value in plain text. - x-kubernetes-preserve-unknown-fields: true - valueFrom: - description: ValueFrom references a potentially confidential value in another source. - properties: - secret: - description: Secret references the secret containing the value. - properties: - key: - description: Key in the secret to select the value from. - type: string - name: - description: Name of the secret in the same namespace. - type: string - required: - - key - - name - type: object - type: object - required: - - name - type: object - type: array - pluginDefinition: - description: PluginDefinition is the name of the PluginDefinition this instance is for. - type: string - releaseNamespace: - description: ReleaseNamespace is the namespace in the remote cluster to which the backend is deployed.\nDefaults to the Greenhouse managed namespace if not set. + description: DisplayName provides a human-readable label for the pluginDefinition. type: string - required: - - disabled - - pluginDefinition - type: object - status: - description: PluginStatus defines the observed state of Plugin - properties: - description: - description: Description provides additional details of the plugin. + docMarkDownUrl: + description: DocMarkDownUrl specifies the URL to the markdown documentation file for this plugin.\nSource needs to allow all CORS origins. type: string - exposedServices: - additionalProperties: - description: Service references a Kubernetes service of a Plugin. - properties: - name: - description: Name is the name of the service in the target cluster. - type: string - namespace: - description: Namespace is the namespace of the service in the target cluster. - type: string - port: - description: Port is the port of the service. - format: int32 - type: integer - protocol: - description: Protocol is the protocol of the service. - type: string - required: - - name - - namespace - - port - type: object - description: ExposedServices provides an overview of the Plugins services that are centrally exposed.\nIt maps the exposed URL to the service found in the manifest. - type: object helmChart: - description: HelmChart contains a reference the helm chart used for the deployed pluginDefinition version. + description: HelmChart specifies where the Helm Chart for this pluginDefinition can be found. properties: name: description: Name of the HelmChart chart. @@ -261,8 +237,195 @@ components: - repository - version type: object - helmReleaseStatus: - description: HelmReleaseStatus reflects the status of the latest HelmChart release.\nThis is only configured if the pluginDefinition is backed by HelmChart. + icon: + description: 'Icon specifies the icon to be used for this plugin in the Greenhouse UI.\nIcons can be either:\n- A string representing a juno icon in camel case from this list: https://github.com/sapcc/juno/blob/main/libs/juno-ui-components/src/components/Icon/Icon.component.js#L6-L52\n- A publicly accessible image reference to a .png file. Will be displayed 100x100px' + type: string + options: + description: RequiredValues is a list of values required to create an instance of this PluginDefinition. + items: + properties: + default: + description: Default provides a default value for the option + x-kubernetes-preserve-unknown-fields: true + description: + description: Description provides a human-readable text for the value as shown in the UI. + type: string + displayName: + description: DisplayName provides a human-readable label for the configuration option + type: string + name: + description: Name/Key of the config option. + type: string + regex: + description: Regex specifies a match rule for validating configuration options. + type: string + required: + description: Required indicates that this config option is required + type: boolean + type: + description: Type of this configuration option. + enum: + - string + - secret + - bool + - int + - list + - map + type: string + required: + - name + - required + - type + type: object + type: array + uiApplication: + description: UIApplication specifies a reference to a UI application + properties: + name: + description: Name of the UI application. + type: string + url: + description: URL specifies the url to a built javascript asset.\nBy default, assets are loaded from the Juno asset server using the provided name and version. + type: string + version: + description: Version of the frontend application. + type: string + required: + - name + - version + type: object + version: + description: Version of this pluginDefinition + type: string + weight: + description: Weight configures the order in which Plugins are shown in the Greenhouse UI.\nDefaults to alphabetical sorting if not provided or on conflict. + format: int32 + type: integer + required: + - version + type: object + status: + description: PluginDefinitionStatus defines the observed state of PluginDefinition + type: object + type: object + Plugin: + xml: + name: greenhouse.sap + namespace: v1alpha1 + title: Plugin + description: Plugin is the Schema for the plugins API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: PluginSpec defines the desired state of Plugin + properties: + clusterName: + description: ClusterName is the name of the cluster the plugin is deployed to. If not set, the plugin is deployed to the greenhouse cluster. + type: string + disabled: + description: Disabled indicates that the plugin is administratively disabled. + type: boolean + displayName: + description: DisplayName is an optional name for the Plugin to be displayed in the Greenhouse UI.\nThis is especially helpful to distinguish multiple instances of a PluginDefinition in the same context.\nDefaults to a normalized version of metadata.name. + type: string + optionValues: + description: Values are the values for a PluginDefinition instance. + items: + description: PluginOptionValue is the value for a PluginOption. + properties: + name: + description: Name of the values. + type: string + value: + description: Value is the actual value in plain text. + x-kubernetes-preserve-unknown-fields: true + valueFrom: + description: ValueFrom references a potentially confidential value in another source. + properties: + secret: + description: Secret references the secret containing the value. + properties: + key: + description: Key in the secret to select the value from. + type: string + name: + description: Name of the secret in the same namespace. + type: string + required: + - key + - name + type: object + type: object + required: + - name + type: object + type: array + pluginDefinition: + description: PluginDefinition is the name of the PluginDefinition this instance is for. + type: string + releaseNamespace: + description: ReleaseNamespace is the namespace in the remote cluster to which the backend is deployed.\nDefaults to the Greenhouse managed namespace if not set. + type: string + required: + - disabled + - pluginDefinition + type: object + status: + description: PluginStatus defines the observed state of Plugin + properties: + description: + description: Description provides additional details of the plugin. + type: string + exposedServices: + additionalProperties: + description: Service references a Kubernetes service of a Plugin. + properties: + name: + description: Name is the name of the service in the target cluster. + type: string + namespace: + description: Namespace is the namespace of the service in the target cluster. + type: string + port: + description: Port is the port of the service. + format: int32 + type: integer + protocol: + description: Protocol is the protocol of the service. + type: string + required: + - name + - namespace + - port + type: object + description: ExposedServices provides an overview of the Plugins services that are centrally exposed.\nIt maps the exposed URL to the service found in the manifest. + type: object + helmChart: + description: HelmChart contains a reference the helm chart used for the deployed pluginDefinition version. + properties: + name: + description: Name of the HelmChart chart. + type: string + repository: + description: Repository of the HelmChart chart. + type: string + version: + description: Version of the HelmChart chart. + type: string + required: + - name + - repository + - version + type: object + helmReleaseStatus: + description: HelmReleaseStatus reflects the status of the latest HelmChart release.\nThis is only configured if the pluginDefinition is backed by HelmChart. properties: firstDeployed: description: FirstDeployed is the timestamp of the first deployment of the release. @@ -336,12 +499,12 @@ components: type: integer type: object type: object - Organization: + Team: xml: name: greenhouse.sap namespace: v1alpha1 - title: Organization - description: Organization is the Schema for the organizations API + title: Team + description: Team is the Schema for the teams API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' @@ -352,112 +515,46 @@ components: metadata: type: object spec: - description: OrganizationSpec defines the desired state of Organization + description: TeamSpec defines the desired state of Team properties: - authentication: - description: Authentication configures the organizations authentication mechanism. - properties: - oidc: - description: OIDConfig configures the OIDC provider. - properties: - clientIDReference: - description: ClientIDReference references the Kubernetes secret containing the client id. - properties: - key: - description: Key in the secret to select the value from. - type: string - name: - description: Name of the secret in the same namespace. - type: string - required: - - key - - name - type: object - clientSecretReference: - description: ClientSecretReference references the Kubernetes secret containing the client secret. - properties: - key: - description: Key in the secret to select the value from. - type: string - name: - description: Name of the secret in the same namespace. - type: string - required: - - key - - name - type: object - issuer: - description: Issuer is the URL of the identity service. - type: string - redirectURI: - description: RedirectURI is the redirect URI.\nIf none is specified, the Greenhouse ID proxy will be used. - type: string - required: - - clientIDReference - - clientSecretReference - - issuer - type: object - scim: - description: SCIMConfig configures the SCIM client. - properties: - baseURL: - description: URL to the SCIM server. - type: string - basicAuthPw: - description: Password to be used for basic authentication. - properties: - secret: - description: Secret references the secret containing the value. - properties: - key: - description: Key in the secret to select the value from. - type: string - name: - description: Name of the secret in the same namespace. - type: string - required: - - key - - name - type: object - type: object - basicAuthUser: - description: User to be used for basic authentication. - properties: - secret: - description: Secret references the secret containing the value. - properties: - key: - description: Key in the secret to select the value from. - type: string - name: - description: Name of the secret in the same namespace. - type: string - required: - - key - - name - type: object - type: object - required: - - baseURL - - basicAuthPw - - basicAuthUser - type: object - type: object - description: - description: Description provides additional details of the organization. - type: string - displayName: - description: DisplayName is an optional name for the organization to be displayed in the Greenhouse UI.\nDefaults to a normalized version of metadata.name. - type: string - mappedOrgAdminIdPGroup: - description: MappedOrgAdminIDPGroup is the IDP group ID identifying org admins - type: string - type: object - status: - description: OrganizationStatus defines the observed state of an Organization - properties: - statusConditions: - description: StatusConditions contain the different conditions that constitute the status of the Organization. + description: + description: Description provides additional details of the team. + type: string + joinUrl: + description: URL to join the IdP group. + type: string + mappedIdPGroup: + description: IdP group id matching team. + type: string + type: object + status: + description: TeamStatus defines the observed state of Team + properties: + members: + items: + description: User specifies a human person. + properties: + email: + description: Email of the user. + type: string + firstName: + description: FirstName of the user. + type: string + id: + description: ID is the unique identifier of the user. + type: string + lastName: + description: LastName of the user. + type: string + required: + - email + - firstName + - id + - lastName + type: object + type: array + statusConditions: + description: A StatusConditions contains a list of conditions.\nOnly one condition of a given type may exist in the list. properties: conditions: items: @@ -489,14 +586,16 @@ components: - type x-kubernetes-list-type: map type: object + required: + - statusConditions type: object type: object - PluginDefinition: + TeamMembership: xml: name: greenhouse.sap namespace: v1alpha1 - title: PluginDefinition - description: PluginDefinition is the Schema for the PluginDefinitions API + title: TeamMembership + description: TeamMembership is the Schema for the teammemberships API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' @@ -507,168 +606,102 @@ components: metadata: type: object spec: - description: PluginDefinitionSpec defines the desired state of PluginDefinitionSpec + description: TeamMembershipSpec defines the desired state of TeamMembership properties: - description: - description: Description provides additional details of the pluginDefinition. - type: string - displayName: - description: DisplayName provides a human-readable label for the pluginDefinition. - type: string - docMarkDownUrl: - description: DocMarkDownUrl specifies the URL to the markdown documentation file for this plugin.\nSource needs to allow all CORS origins. - type: string - helmChart: - description: HelmChart specifies where the Helm Chart for this pluginDefinition can be found. - properties: - name: - description: Name of the HelmChart chart. - type: string - repository: - description: Repository of the HelmChart chart. - type: string - version: - description: Version of the HelmChart chart. - type: string - required: - - name - - repository - - version - type: object - icon: - description: 'Icon specifies the icon to be used for this plugin in the Greenhouse UI.\nIcons can be either:\n- A string representing a juno icon in camel case from this list: https://github.com/sapcc/juno/blob/main/libs/juno-ui-components/src/components/Icon/Icon.component.js#L6-L52\n- A publicly accessible image reference to a .png file. Will be displayed 100x100px' - type: string - options: - description: RequiredValues is a list of values required to create an instance of this PluginDefinition. + members: + description: Members list users that are part of a team. items: + description: User specifies a human person. properties: - default: - description: Default provides a default value for the option - x-kubernetes-preserve-unknown-fields: true - description: - description: Description provides a human-readable text for the value as shown in the UI. - type: string - displayName: - description: DisplayName provides a human-readable label for the configuration option + email: + description: Email of the user. type: string - name: - description: Name/Key of the config option. + firstName: + description: FirstName of the user. type: string - regex: - description: Regex specifies a match rule for validating configuration options. + id: + description: ID is the unique identifier of the user. type: string - required: - description: Required indicates that this config option is required - type: boolean - type: - description: Type of this configuration option. - enum: - - string - - secret - - bool - - int - - list - - map + lastName: + description: LastName of the user. type: string required: - - name - - required - - type + - email + - firstName + - id + - lastName type: object type: array - uiApplication: - description: UIApplication specifies a reference to a UI application - properties: - name: - description: Name of the UI application. - type: string - url: - description: URL specifies the url to a built javascript asset.\nBy default, assets are loaded from the Juno asset server using the provided name and version. - type: string - version: - description: Version of the frontend application. - type: string - required: - - name - - version - type: object - version: - description: Version of this pluginDefinition - type: string - weight: - description: Weight configures the order in which Plugins are shown in the Greenhouse UI.\nDefaults to alphabetical sorting if not provided or on conflict. - format: int32 - type: integer - required: - - version type: object status: - description: PluginDefinitionStatus defines the observed state of PluginDefinition + description: TeamMembershipStatus defines the observed state of TeamMembership + properties: + lastSyncedTime: + description: LastSyncedTime is the information when was the last time the membership was synced + format: date-time + type: string + lastUpdateTime: + description: LastChangedTime is the information when was the last time the membership was actually changed + format: date-time + type: string + statusConditions: + description: StatusConditions contain the different conditions that constitute the status of the TeamMembership. + properties: + conditions: + items: + description: Condition contains additional information on the state of a resource. + properties: + lastTransitionTime: + description: LastTransitionTime is the last time the condition transitioned from one status to another. + format: date-time + type: string + message: + description: Message is an optional human readable message indicating details about the last transition. + type: string + reason: + description: Reason is a one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status of the condition. + type: string + type: + description: Type of the condition. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object type: object type: object - PluginPreset: + TeamRoleBinding: xml: name: greenhouse.sap namespace: v1alpha1 - title: PluginPreset - description: PluginPreset is the Schema for the PluginPresets API + title: TeamRoleBinding + description: TeamRoleBinding is the Schema for the rolebindings API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: PluginPresetSpec defines the desired state of PluginPreset - properties: - clusterOptionOverrides: - description: ClusterOptionOverrides define plugin option values to override by the PluginPreset - items: - description: ClusterOptionOverride defines which plugin option should be override in which cluster - properties: - clusterName: - type: string - overrides: - items: - description: PluginOptionValue is the value for a PluginOption. - properties: - name: - description: Name of the values. - type: string - value: - description: Value is the actual value in plain text. - x-kubernetes-preserve-unknown-fields: true - valueFrom: - description: ValueFrom references a potentially confidential value in another source. - properties: - secret: - description: Secret references the secret containing the value. - properties: - key: - description: Key in the secret to select the value from. - type: string - name: - description: Name of the secret in the same namespace. - type: string - required: - - key - - name - type: object - type: object - required: - - name - type: object - type: array - required: - - clusterName - - overrides - type: object - type: array + description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TeamRoleBindingSpec defines the desired state of a TeamRoleBinding + properties: + clusterName: + description: ClusterName is the name of the cluster the rbacv1 resources are created on. + type: string clusterSelector: - description: ClusterSelector is a label selector to select the clusters the plugin bundle should be deployed to. + description: ClusterSelector is a label selector to select the Clusters the TeamRoleBinding should be deployed to. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -700,69 +733,62 @@ components: type: object type: object x-kubernetes-map-type: atomic - plugin: - description: PluginSpec is the spec of the plugin to be deployed by the PluginPreset. - properties: - clusterName: - description: ClusterName is the name of the cluster the plugin is deployed to. If not set, the plugin is deployed to the greenhouse cluster. - type: string - disabled: - description: Disabled indicates that the plugin is administratively disabled. - type: boolean - displayName: - description: DisplayName is an optional name for the Plugin to be displayed in the Greenhouse UI.\nThis is especially helpful to distinguish multiple instances of a PluginDefinition in the same context.\nDefaults to a normalized version of metadata.name. - type: string - optionValues: - description: Values are the values for a PluginDefinition instance. - items: - description: PluginOptionValue is the value for a PluginOption. + namespaces: + description: Namespaces is a list of namespaces in the Greenhouse Clusters to apply the RoleBinding to.\nIf empty, a ClusterRoleBinding will be created on the remote cluster, otherwise a RoleBinding per namespace. + items: + type: string + type: array + teamRef: + description: TeamRef references a Greenhouse Team by name + type: string + teamRoleRef: + description: TeamRoleRef references a Greenhouse TeamRole by name + type: string + type: object + status: + description: TeamRoleBindingStatus defines the observed state of the TeamRoleBinding + properties: + clusters: + description: PropagationStatus is the list of clusters the TeamRoleBinding is applied to + items: + description: PropagationStatus defines the observed state of the TeamRoleBinding's associated rbacv1 resources on a Cluster + properties: + clusterName: + description: ClusterName is the name of the cluster the rbacv1 resources are created on. + type: string + condition: + description: Condition is the overall Status of the rbacv1 resources created on the cluster properties: - name: - description: Name of the values. + lastTransitionTime: + description: LastTransitionTime is the last time the condition transitioned from one status to another. + format: date-time + type: string + message: + description: Message is an optional human readable message indicating details about the last transition. + type: string + reason: + description: Reason is a one-word, CamelCase reason for the condition's last transition. + type: string + status: + description: Status of the condition. + type: string + type: + description: Type of the condition. type: string - value: - description: Value is the actual value in plain text. - x-kubernetes-preserve-unknown-fields: true - valueFrom: - description: ValueFrom references a potentially confidential value in another source. - properties: - secret: - description: Secret references the secret containing the value. - properties: - key: - description: Key in the secret to select the value from. - type: string - name: - description: Name of the secret in the same namespace. - type: string - required: - - key - - name - type: object - type: object required: - - name + - lastTransitionTime + - status + - type type: object - type: array - pluginDefinition: - description: PluginDefinition is the name of the PluginDefinition this instance is for. - type: string - releaseNamespace: - description: ReleaseNamespace is the namespace in the remote cluster to which the backend is deployed.\nDefaults to the Greenhouse managed namespace if not set. - type: string - required: - - disabled - - pluginDefinition - type: object - required: - - clusterSelector - - plugin - type: object - status: - description: PluginPresetStatus defines the observed state of PluginPreset - properties: + required: + - clusterName + type: object + type: array + x-kubernetes-list-map-keys: + - clusterName + x-kubernetes-list-type: map statusConditions: - description: StatusConditions contain the different conditions that constitute the status of the PluginPreset. + description: StatusConditions contain the different conditions that constitute the status of the TeamRoleBinding. properties: conditions: items: @@ -796,12 +822,12 @@ components: type: object type: object type: object - ClusterKubeconfig: + Organization: xml: name: greenhouse.sap namespace: v1alpha1 - title: ClusterKubeconfig - description: ClusterKubeconfig is the Schema for the clusterkubeconfigs API\nObjectMeta.OwnerReferences is used to link the ClusterKubeconfig to the Cluster\nObjectMeta.Generation is used to detect changes in the ClusterKubeconfig and sync local kubeconfig files\nObjectMeta.Name is designed to be the same with the Cluster name + title: Organization + description: Organization is the Schema for the organizations API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' @@ -812,94 +838,112 @@ components: metadata: type: object spec: - description: ClusterKubeconfigSpec stores the kubeconfig data for the cluster\nThe idea is to use kubeconfig data locally with minimum effort (with local tools or plain kubectl):\nkubectl get cluster-kubeconfig $NAME -o yaml | yq -y .spec.kubeconfig + description: OrganizationSpec defines the desired state of Organization properties: - kubeconfig: - description: 'ClusterKubeconfigData stores the kubeconfig data ready to use kubectl or other local tooling\nIt is a simplified version of clientcmdapi.Config: https://pkg.go.dev/k8s.io/client-go/tools/clientcmd/api#Config' + authentication: + description: Authentication configures the organizations authentication mechanism. properties: - apiVersion: - type: string - clusters: - items: - properties: - cluster: - properties: - certificate-authority-data: - format: byte - type: string - server: - type: string - type: object - name: - type: string - required: - - cluster - - name - type: object - type: array - contexts: - items: - properties: - context: - properties: - cluster: - type: string - namespace: - type: string - user: - type: string - required: - - cluster - - user - type: object - name: - type: string - required: - - name - type: object - type: array - current-context: - type: string - kind: - type: string - preferences: + oidc: + description: OIDConfig configures the OIDC provider. + properties: + clientIDReference: + description: ClientIDReference references the Kubernetes secret containing the client id. + properties: + key: + description: Key in the secret to select the value from. + type: string + name: + description: Name of the secret in the same namespace. + type: string + required: + - key + - name + type: object + clientSecretReference: + description: ClientSecretReference references the Kubernetes secret containing the client secret. + properties: + key: + description: Key in the secret to select the value from. + type: string + name: + description: Name of the secret in the same namespace. + type: string + required: + - key + - name + type: object + issuer: + description: Issuer is the URL of the identity service. + type: string + redirectURI: + description: RedirectURI is the redirect URI.\nIf none is specified, the Greenhouse ID proxy will be used. + type: string + required: + - clientIDReference + - clientSecretReference + - issuer + type: object + scim: + description: SCIMConfig configures the SCIM client. + properties: + baseURL: + description: URL to the SCIM server. + type: string + basicAuthPw: + description: Password to be used for basic authentication. + properties: + secret: + description: Secret references the secret containing the value. + properties: + key: + description: Key in the secret to select the value from. + type: string + name: + description: Name of the secret in the same namespace. + type: string + required: + - key + - name + type: object + type: object + basicAuthUser: + description: User to be used for basic authentication. + properties: + secret: + description: Secret references the secret containing the value. + properties: + key: + description: Key in the secret to select the value from. + type: string + name: + description: Name of the secret in the same namespace. + type: string + required: + - key + - name + type: object + type: object + required: + - baseURL + - basicAuthPw + - basicAuthUser type: object - users: - items: - properties: - name: - type: string - user: - properties: - auth-provider: - description: AuthProviderConfig holds the configuration for a specified auth provider. - properties: - config: - additionalProperties: - type: string - type: object - name: - type: string - required: - - name - type: object - client-certificate-data: - format: byte - type: string - client-key-data: - format: byte - type: string - type: object - required: - - name - type: object - type: array type: object + description: + description: Description provides additional details of the organization. + type: string + displayName: + description: DisplayName is an optional name for the organization to be displayed in the Greenhouse UI.\nDefaults to a normalized version of metadata.name. + type: string + mappedOrgAdminIdPGroup: + description: MappedOrgAdminIDPGroup is the IDP group ID identifying org admins + type: string type: object status: + description: OrganizationStatus defines the observed state of an Organization properties: statusConditions: - description: A StatusConditions contains a list of conditions.\nOnly one condition of a given type may exist in the list. + description: StatusConditions contain the different conditions that constitute the status of the Organization. properties: conditions: items: @@ -933,12 +977,12 @@ components: type: object type: object type: object - TeamRoleBinding: + PluginPreset: xml: name: greenhouse.sap namespace: v1alpha1 - title: TeamRoleBinding - description: TeamRoleBinding is the Schema for the rolebindings API + title: PluginPreset + description: PluginPreset is the Schema for the PluginPresets API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' @@ -949,13 +993,53 @@ components: metadata: type: object spec: - description: TeamRoleBindingSpec defines the desired state of a TeamRoleBinding + description: PluginPresetSpec defines the desired state of PluginPreset properties: - clusterName: - description: ClusterName is the name of the cluster the rbacv1 resources are created on. - type: string + clusterOptionOverrides: + description: ClusterOptionOverrides define plugin option values to override by the PluginPreset + items: + description: ClusterOptionOverride defines which plugin option should be override in which cluster + properties: + clusterName: + type: string + overrides: + items: + description: PluginOptionValue is the value for a PluginOption. + properties: + name: + description: Name of the values. + type: string + value: + description: Value is the actual value in plain text. + x-kubernetes-preserve-unknown-fields: true + valueFrom: + description: ValueFrom references a potentially confidential value in another source. + properties: + secret: + description: Secret references the secret containing the value. + properties: + key: + description: Key in the secret to select the value from. + type: string + name: + description: Name of the secret in the same namespace. + type: string + required: + - key + - name + type: object + type: object + required: + - name + type: object + type: array + required: + - clusterName + - overrides + type: object + type: array clusterSelector: - description: ClusterSelector is a label selector to select the Clusters the TeamRoleBinding should be deployed to. + description: ClusterSelector is a label selector to select the clusters the plugin bundle should be deployed to. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. @@ -987,151 +1071,69 @@ components: type: object type: object x-kubernetes-map-type: atomic - namespaces: - description: Namespaces is a list of namespaces in the Greenhouse Clusters to apply the RoleBinding to.\nIf empty, a ClusterRoleBinding will be created on the remote cluster, otherwise a RoleBinding per namespace. - items: - type: string - type: array - teamRef: - description: TeamRef references a Greenhouse Team by name - type: string - teamRoleRef: - description: TeamRoleRef references a Greenhouse TeamRole by name - type: string - type: object - status: - description: TeamRoleBindingStatus defines the observed state of the TeamRoleBinding - properties: - clusters: - description: PropagationStatus is the list of clusters the TeamRoleBinding is applied to - items: - description: PropagationStatus defines the observed state of the TeamRoleBinding's associated rbacv1 resources on a Cluster - properties: - clusterName: - description: ClusterName is the name of the cluster the rbacv1 resources are created on. - type: string - condition: - description: Condition is the overall Status of the rbacv1 resources created on the cluster - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition transitioned from one status to another. - format: date-time - type: string - message: - description: Message is an optional human readable message indicating details about the last transition. - type: string - reason: - description: Reason is a one-word, CamelCase reason for the condition's last transition. - type: string - status: - description: Status of the condition. - type: string - type: - description: Type of the condition. - type: string - required: - - lastTransitionTime - - status - - type - type: object - required: - - clusterName - type: object - type: array - x-kubernetes-list-map-keys: - - clusterName - x-kubernetes-list-type: map - statusConditions: - description: StatusConditions contain the different conditions that constitute the status of the TeamRoleBinding. + plugin: + description: PluginSpec is the spec of the plugin to be deployed by the PluginPreset. properties: - conditions: + clusterName: + description: ClusterName is the name of the cluster the plugin is deployed to. If not set, the plugin is deployed to the greenhouse cluster. + type: string + disabled: + description: Disabled indicates that the plugin is administratively disabled. + type: boolean + displayName: + description: DisplayName is an optional name for the Plugin to be displayed in the Greenhouse UI.\nThis is especially helpful to distinguish multiple instances of a PluginDefinition in the same context.\nDefaults to a normalized version of metadata.name. + type: string + optionValues: + description: Values are the values for a PluginDefinition instance. items: - description: Condition contains additional information on the state of a resource. + description: PluginOptionValue is the value for a PluginOption. properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition transitioned from one status to another. - format: date-time - type: string - message: - description: Message is an optional human readable message indicating details about the last transition. - type: string - reason: - description: Reason is a one-word, CamelCase reason for the condition's last transition. - type: string - status: - description: Status of the condition. - type: string - type: - description: Type of the condition. + name: + description: Name of the values. type: string + value: + description: Value is the actual value in plain text. + x-kubernetes-preserve-unknown-fields: true + valueFrom: + description: ValueFrom references a potentially confidential value in another source. + properties: + secret: + description: Secret references the secret containing the value. + properties: + key: + description: Key in the secret to select the value from. + type: string + name: + description: Name of the secret in the same namespace. + type: string + required: + - key + - name + type: object + type: object required: - - lastTransitionTime - - status - - type + - name type: object type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - type: object - type: object - Team: - xml: - name: greenhouse.sap - namespace: v1alpha1 - title: Team - description: Team is the Schema for the teams API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TeamSpec defines the desired state of Team - properties: - description: - description: Description provides additional details of the team. - type: string - joinUrl: - description: URL to join the IdP group. - type: string - mappedIdPGroup: - description: IdP group id matching team. - type: string + pluginDefinition: + description: PluginDefinition is the name of the PluginDefinition this instance is for. + type: string + releaseNamespace: + description: ReleaseNamespace is the namespace in the remote cluster to which the backend is deployed.\nDefaults to the Greenhouse managed namespace if not set. + type: string + required: + - disabled + - pluginDefinition + type: object + required: + - clusterSelector + - plugin type: object status: - description: TeamStatus defines the observed state of Team + description: PluginPresetStatus defines the observed state of PluginPreset properties: - members: - items: - description: User specifies a human person. - properties: - email: - description: Email of the user. - type: string - firstName: - description: FirstName of the user. - type: string - id: - description: ID is the unique identifier of the user. - type: string - lastName: - description: LastName of the user. - type: string - required: - - email - - firstName - - id - - lastName - type: object - type: array statusConditions: - description: A StatusConditions contains a list of conditions.\nOnly one condition of a given type may exist in the list. + description: StatusConditions contain the different conditions that constitute the status of the PluginPreset. properties: conditions: items: @@ -1163,8 +1165,114 @@ components: - type x-kubernetes-list-type: map type: object - required: - - statusConditions + type: object + type: object + TeamRole: + xml: + name: greenhouse.sap + namespace: v1alpha1 + title: TeamRole + description: TeamRole is the Schema for the TeamRoles API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: TeamRoleSpec defines the desired state of a TeamRole + properties: + aggregationRule: + description: AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole on the remote cluster + properties: + clusterRoleSelectors: + description: ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules.\nIf any of the selectors match, then the ClusterRole's permissions will be added + items: + description: A label selector is a label query over a set of resources. The result of matchLabels and\nmatchExpressions are ANDed. An empty label selector matches all objects. A null\nlabel selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that\nrelates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values.\nValid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn,\nthe values array must be non-empty. If the operator is Exists or DoesNotExist,\nthe values array must be empty. This array is replaced during a strategic\nmerge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is "key", the\noperator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + type: object + labels: + additionalProperties: + type: string + description: Labels are applied to the ClusterRole created on the remote cluster.\nThis allows using TeamRoles as part of AggregationRules by other TeamRoles + type: object + rules: + description: Rules is a list of rbacv1.PolicyRules used on a managed RBAC (Cluster)Role + items: + description: PolicyRule holds information that describes a policy rule, but does not contain information\nabout who the rule applies to or which namespace the rule applies to. + properties: + apiGroups: + description: APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of\nthe enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups. + items: + type: string + type: array + x-kubernetes-list-type: atomic + nonResourceURLs: + description: NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path\nSince non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding.\nRules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. + items: + type: string + type: array + x-kubernetes-list-type: atomic + resourceNames: + description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + resources: + description: Resources is a list of resources this rule applies to. '*' represents all resources. + items: + type: string + type: array + x-kubernetes-list-type: atomic + verbs: + description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - verbs + type: object + type: array + type: object + status: + description: TeamRoleStatus defines the observed state of a TeamRole type: object type: object Cluster: @@ -1291,111 +1399,3 @@ components: type: object type: object type: object - TeamRole: - xml: - name: greenhouse.sap - namespace: v1alpha1 - title: TeamRole - description: TeamRole is the Schema for the TeamRoles API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: TeamRoleSpec defines the desired state of a TeamRole - properties: - aggregationRule: - description: AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole on the remote cluster - properties: - clusterRoleSelectors: - description: ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules.\nIf any of the selectors match, then the ClusterRole's permissions will be added - items: - description: A label selector is a label query over a set of resources. The result of matchLabels and\nmatchExpressions are ANDed. An empty label selector matches all objects. A null\nlabel selector matches no objects. - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that\nrelates the key and values. - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values.\nValid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn,\nthe values array must be non-empty. If the operator is Exists or DoesNotExist,\nthe values array must be empty. This array is replaced during a strategic\nmerge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is "key", the\noperator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - type: object - labels: - additionalProperties: - type: string - description: Labels are applied to the ClusterRole created on the remote cluster.\nThis allows using TeamRoles as part of AggregationRules by other TeamRoles - type: object - rules: - description: Rules is a list of rbacv1.PolicyRules used on a managed RBAC (Cluster)Role - items: - description: PolicyRule holds information that describes a policy rule, but does not contain information\nabout who the rule applies to or which namespace the rule applies to. - properties: - apiGroups: - description: APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of\nthe enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups. - items: - type: string - type: array - x-kubernetes-list-type: atomic - nonResourceURLs: - description: NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path\nSince non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding.\nRules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. - items: - type: string - type: array - x-kubernetes-list-type: atomic - resourceNames: - description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. - items: - type: string - type: array - x-kubernetes-list-type: atomic - resources: - description: Resources is a list of resources this rule applies to. '*' represents all resources. - items: - type: string - type: array - x-kubernetes-list-type: atomic - verbs: - description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - verbs - type: object - type: array - type: object - status: - description: TeamRoleStatus defines the observed state of a TeamRole - type: object - type: object From c03309e3f8e49c21d17c58746bab777291f64fed Mon Sep 17 00:00:00 2001 From: Akshay Iyyadurai Balasundaram Date: Wed, 29 Jan 2025 13:40:34 +0100 Subject: [PATCH 4/7] refactor(tests): clean up import statements in proxy_test.go Signed-off-by: Akshay Iyyadurai Balasundaram --- cmd/service-proxy/proxy_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd/service-proxy/proxy_test.go b/cmd/service-proxy/proxy_test.go index 19c0fe510..a62c47945 100644 --- a/cmd/service-proxy/proxy_test.go +++ b/cmd/service-proxy/proxy_test.go @@ -10,16 +10,15 @@ import ( "net/url" "testing" + greenhouseapis "github.com/cloudoperators/greenhouse/pkg/apis" + greenhousev1alpha1 "github.com/cloudoperators/greenhouse/pkg/apis/greenhouse/v1alpha1" + "github.com/cloudoperators/greenhouse/pkg/test" + "github.com/go-logr/logr" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - greenhouseapis "github.com/cloudoperators/greenhouse/pkg/apis" - greenhousev1alpha1 "github.com/cloudoperators/greenhouse/pkg/apis/greenhouse/v1alpha1" - "github.com/cloudoperators/greenhouse/pkg/test" - "github.com/go-logr/logr" ) // TestRewrite tests the rewrite function of the proxy manager. From 97fabc2c0859c019c85cb64e08d131be3c0fa85d Mon Sep 17 00:00:00 2001 From: Akshay Iyyadurai Balasundaram Date: Wed, 29 Jan 2025 13:47:43 +0100 Subject: [PATCH 5/7] refactor(service-proxy): reorder import statements in proxy_test.go Signed-off-by: Akshay Iyyadurai Balasundaram --- cmd/service-proxy/proxy_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/service-proxy/proxy_test.go b/cmd/service-proxy/proxy_test.go index a62c47945..4686d5d9d 100644 --- a/cmd/service-proxy/proxy_test.go +++ b/cmd/service-proxy/proxy_test.go @@ -10,15 +10,16 @@ import ( "net/url" "testing" - greenhouseapis "github.com/cloudoperators/greenhouse/pkg/apis" - greenhousev1alpha1 "github.com/cloudoperators/greenhouse/pkg/apis/greenhouse/v1alpha1" - "github.com/cloudoperators/greenhouse/pkg/test" "github.com/go-logr/logr" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + greenhouseapis "github.com/cloudoperators/greenhouse/pkg/apis" + greenhousev1alpha1 "github.com/cloudoperators/greenhouse/pkg/apis/greenhouse/v1alpha1" + "github.com/cloudoperators/greenhouse/pkg/test" ) // TestRewrite tests the rewrite function of the proxy manager. From 7adddf6a42c1ad616447908c57b07f4bca14601f Mon Sep 17 00:00:00 2001 From: Akshay Iyyadurai Balasundaram Date: Thu, 30 Jan 2025 09:27:18 +0100 Subject: [PATCH 6/7] fix(service-proxy): inject logger into context in defer and update test URLs Signed-off-by: Akshay Iyyadurai Balasundaram --- cmd/service-proxy/proxy.go | 7 +++++-- cmd/service-proxy/proxy_test.go | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/service-proxy/proxy.go b/cmd/service-proxy/proxy.go index e600ad845..07d535697 100644 --- a/cmd/service-proxy/proxy.go +++ b/cmd/service-proxy/proxy.go @@ -206,11 +206,15 @@ func (pm *ProxyManager) rewrite(req *httputil.ProxyRequest) { "incomingMethod", req.In.Method, ) + // inject current logger into context before returning + defer func() { + req.Out = req.Out.WithContext(log.IntoContext(req.Out.Context(), l)) + }() + // Extract cluster from the incoming request host cluster, err := common.ExtractCluster(req.In.Host) if err != nil { l.Error(err, "Failed to extract cluster from host", "host", req.In.Host) - req.Out = req.Out.WithContext(log.IntoContext(req.Out.Context(), l)) return } @@ -218,7 +222,6 @@ func (pm *ProxyManager) rewrite(req *httputil.ProxyRequest) { route, ok := pm.GetClusterRoute(cluster, "https://"+req.In.Host) if !ok { l.Info("No route found for cluster and URL", "cluster", cluster, "incomingRequestURL", req.In.URL.String()) - req.Out = req.Out.WithContext(log.IntoContext(req.Out.Context(), l)) return } upstreamServiceRouteURL := route.url diff --git a/cmd/service-proxy/proxy_test.go b/cmd/service-proxy/proxy_test.go index 4686d5d9d..f2da5ca7d 100644 --- a/cmd/service-proxy/proxy_test.go +++ b/cmd/service-proxy/proxy_test.go @@ -27,7 +27,7 @@ import ( // If checks if he url is properly rewritten and the request context contains the cluster name // and a logger with the correct values. func TestRewrite(t *testing.T) { - proxyURL, err := url.Parse("https://api.blueprints.greenhouse.shoot.canary.k8s-hana.ondemand.com/api/v1/namespaces/kube-monitoring/services/test-service:8080") + proxyURL, err := url.Parse("https://api.test-api-server.com/api/v1/namespaces/kube-monitoring/services/test-service:8080") if err != nil { t.Fatal("failed to parse proxy URL") } @@ -41,19 +41,19 @@ func TestRewrite(t *testing.T) { { name: "valid host with path", url: "https://cluster--1234567.organisation.basedomain/dashboard", - expectedupstreamServiceRouteURL: "https://api.blueprints.greenhouse.shoot.canary.k8s-hana.ondemand.com/api/v1/namespaces/kube-monitoring/services/test-service:8080/dashboard", + expectedupstreamServiceRouteURL: "https://api.test-api-server.com/api/v1/namespaces/kube-monitoring/services/test-service:8080/dashboard", contextVal: "cluster", }, { name: "valid host with deeper path", url: "https://cluster--1234567.organisation.basedomain/api/resource", - expectedupstreamServiceRouteURL: "https://api.blueprints.greenhouse.shoot.canary.k8s-hana.ondemand.com/api/v1/namespaces/kube-monitoring/services/test-service:8080/api/resource", + expectedupstreamServiceRouteURL: "https://api.test-api-server.com/api/v1/namespaces/kube-monitoring/services/test-service:8080/api/resource", contextVal: "cluster", }, { name: "valid host with already prefixed path", url: "https://cluster--1234567.organisation.basedomain/api/v1/namespaces/kube-monitoring/services/test-service:8080/existing-path", - expectedupstreamServiceRouteURL: "https://api.blueprints.greenhouse.shoot.canary.k8s-hana.ondemand.com/api/v1/namespaces/kube-monitoring/services/test-service:8080/existing-path", + expectedupstreamServiceRouteURL: "https://api.test-api-server.com/api/v1/namespaces/kube-monitoring/services/test-service:8080/existing-path", contextVal: "cluster", }, { From d3d03e766ea2e0ac24236e97a1087f73618c0382 Mon Sep 17 00:00:00 2001 From: Akshay Iyyadurai Balasundaram Date: Thu, 30 Jan 2025 09:39:15 +0100 Subject: [PATCH 7/7] chore(service-proxy): go fmt Signed-off-by: Akshay Iyyadurai Balasundaram --- cmd/service-proxy/proxy.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/service-proxy/proxy.go b/cmd/service-proxy/proxy.go index 07d535697..d53d55740 100644 --- a/cmd/service-proxy/proxy.go +++ b/cmd/service-proxy/proxy.go @@ -206,11 +206,11 @@ func (pm *ProxyManager) rewrite(req *httputil.ProxyRequest) { "incomingMethod", req.In.Method, ) - // inject current logger into context before returning - defer func() { - req.Out = req.Out.WithContext(log.IntoContext(req.Out.Context(), l)) - }() - + // inject current logger into context before returning + defer func() { + req.Out = req.Out.WithContext(log.IntoContext(req.Out.Context(), l)) + }() + // Extract cluster from the incoming request host cluster, err := common.ExtractCluster(req.In.Host) if err != nil {