From 625f35d66f6ea7acefb7d3fb5872ee2f46b86139 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 6 Nov 2025 16:23:39 +0100 Subject: [PATCH] [dashboard] sync with upstream & enhancements (#1603) Signed-off-by: Andrei Kvapil - Move patches to upstream: `namespaces` and `hide inside` - Introduce flatMap logic - Remove `tenantsecretstables` resource - Extend dashboard-controller to specify `multilineString` for any string without enum in spec (previusly it was for all strings) ```release-note [dashboard] sync with upstream & enhancements ``` * **New Features** * Enhanced OpenAPI form handling: string fields now better support multiline input. * **Improvements** * Secrets UI and API alignment: secrets display and data keys updated for consistency. * Form generation improved for nested objects and arrays. * Deployment defaults adjusted (logger flags normalized; inside feature hidden via env). * **Removed** * Removed the "Inside" header menu item and the legacy secrets-table API/resource. --- .../dashboard/customformsoverride.go | 105 +++++- .../dashboard/customformsoverride_test.go | 155 ++++++++ internal/controller/dashboard/factory.go | 4 +- .../controller/dashboard/static_helpers.go | 11 +- .../controller/dashboard/static_refactored.go | 9 +- packages/apps/tenant/templates/tenant.yaml | 4 - .../images/openapi-ui-k8s-bff/Dockerfile | 6 +- .../dashboard/images/openapi-ui/Dockerfile | 10 +- .../openapi-ui/patches/namespaces.diff | 89 ----- .../patches/remove-inside-link.diff | 15 - .../system/dashboard/templates/configmap.yaml | 8 +- packages/system/dashboard/templates/web.yaml | 22 +- packages/system/dashboard/values.yaml | 6 +- pkg/apis/core/v1alpha1/register.go | 4 +- .../core/v1alpha1/tenantsecretstable_types.go | 34 -- .../core/v1alpha1/zz_generated.deepcopy.go | 76 ---- pkg/apiserver/apiserver.go | 4 - pkg/generated/openapi/zz_generated.openapi.go | 123 ------- pkg/registry/core/tenantsecretstable/rest.go | 335 ------------------ 19 files changed, 305 insertions(+), 715 deletions(-) create mode 100644 internal/controller/dashboard/customformsoverride_test.go delete mode 100644 packages/system/dashboard/images/openapi-ui/openapi-ui/patches/namespaces.diff delete mode 100644 packages/system/dashboard/images/openapi-ui/openapi-ui/patches/remove-inside-link.diff delete mode 100644 pkg/apis/core/v1alpha1/tenantsecretstable_types.go delete mode 100644 pkg/registry/core/tenantsecretstable/rest.go diff --git a/internal/controller/dashboard/customformsoverride.go b/internal/controller/dashboard/customformsoverride.go index ae637fa1..f88202bc 100644 --- a/internal/controller/dashboard/customformsoverride.go +++ b/internal/controller/dashboard/customformsoverride.go @@ -11,6 +11,7 @@ import ( apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" ) // ensureCustomFormsOverride creates or updates a CustomFormsOverride resource for the given CRD @@ -45,15 +46,24 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph } } + // Build schema with multilineString for string fields without enum + l := log.FromContext(ctx) + schema, err := buildMultilineStringSchema(crd.Spec.Application.OpenAPISchema) + if err != nil { + // If schema parsing fails, log the error and use an empty schema + l.Error(err, "failed to build multiline string schema, using empty schema", "crd", crd.Name) + schema = map[string]any{} + } + spec := map[string]any{ "customizationId": customizationID, "hidden": hidden, "sort": sort, - "schema": map[string]any{}, // {} + "schema": schema, "strategy": "merge", } - _, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error { + _, err = controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error { if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil { return err } @@ -73,3 +83,94 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph }) return err } + +// buildMultilineStringSchema parses OpenAPI schema and creates schema with multilineString +// for all string fields inside spec that don't have enum +func buildMultilineStringSchema(openAPISchema string) (map[string]any, error) { + if openAPISchema == "" { + return map[string]any{}, nil + } + + var root map[string]any + if err := json.Unmarshal([]byte(openAPISchema), &root); err != nil { + return nil, fmt.Errorf("cannot parse openAPISchema: %w", err) + } + + props, _ := root["properties"].(map[string]any) + if props == nil { + return map[string]any{}, nil + } + + schema := map[string]any{ + "properties": map[string]any{}, + } + + // Process spec properties recursively + processSpecProperties(props, schema["properties"].(map[string]any)) + + return schema, nil +} + +// processSpecProperties recursively processes spec properties and adds multilineString type +// for string fields without enum +func processSpecProperties(props map[string]any, schemaProps map[string]any) { + for pname, raw := range props { + sub, ok := raw.(map[string]any) + if !ok { + continue + } + + typ, _ := sub["type"].(string) + + switch typ { + case "string": + // Check if this string field has enum + if !hasEnum(sub) { + // Add multilineString type for this field + if schemaProps[pname] == nil { + schemaProps[pname] = map[string]any{} + } + fieldSchema := schemaProps[pname].(map[string]any) + fieldSchema["type"] = "multilineString" + } + case "object": + // Recursively process nested objects + if childProps, ok := sub["properties"].(map[string]any); ok { + fieldSchema, ok := schemaProps[pname].(map[string]any) + if !ok { + fieldSchema = map[string]any{} + schemaProps[pname] = fieldSchema + } + nestedSchemaProps, ok := fieldSchema["properties"].(map[string]any) + if !ok { + nestedSchemaProps = map[string]any{} + fieldSchema["properties"] = nestedSchemaProps + } + processSpecProperties(childProps, nestedSchemaProps) + } + case "array": + // Check if array items are objects with properties + if items, ok := sub["items"].(map[string]any); ok { + if itemProps, ok := items["properties"].(map[string]any); ok { + // Create array item schema + fieldSchema, ok := schemaProps[pname].(map[string]any) + if !ok { + fieldSchema = map[string]any{} + schemaProps[pname] = fieldSchema + } + itemSchema, ok := fieldSchema["items"].(map[string]any) + if !ok { + itemSchema = map[string]any{} + fieldSchema["items"] = itemSchema + } + itemSchemaProps, ok := itemSchema["properties"].(map[string]any) + if !ok { + itemSchemaProps = map[string]any{} + itemSchema["properties"] = itemSchemaProps + } + processSpecProperties(itemProps, itemSchemaProps) + } + } + } + } +} diff --git a/internal/controller/dashboard/customformsoverride_test.go b/internal/controller/dashboard/customformsoverride_test.go new file mode 100644 index 00000000..2766bf17 --- /dev/null +++ b/internal/controller/dashboard/customformsoverride_test.go @@ -0,0 +1,155 @@ +package dashboard + +import ( + "encoding/json" + "testing" +) + +func TestBuildMultilineStringSchema(t *testing.T) { + // Test OpenAPI schema with various field types + openAPISchema := `{ + "properties": { + "simpleString": { + "type": "string", + "description": "A simple string field" + }, + "stringWithEnum": { + "type": "string", + "enum": ["option1", "option2"], + "description": "String with enum should be skipped" + }, + "numberField": { + "type": "number", + "description": "Number field should be skipped" + }, + "nestedObject": { + "type": "object", + "properties": { + "nestedString": { + "type": "string", + "description": "Nested string should get multilineString" + }, + "nestedStringWithEnum": { + "type": "string", + "enum": ["a", "b"], + "description": "Nested string with enum should be skipped" + } + } + }, + "arrayOfObjects": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemString": { + "type": "string", + "description": "String in array item" + } + } + } + } + } + }` + + schema, err := buildMultilineStringSchema(openAPISchema) + if err != nil { + t.Fatalf("buildMultilineStringSchema failed: %v", err) + } + + // Marshal to JSON for easier inspection + schemaJSON, err := json.MarshalIndent(schema, "", " ") + if err != nil { + t.Fatalf("Failed to marshal schema: %v", err) + } + + t.Logf("Generated schema:\n%s", schemaJSON) + + // Verify that simpleString has multilineString type + props, ok := schema["properties"].(map[string]any) + if !ok { + t.Fatal("schema.properties is not a map") + } + + // Check simpleString + simpleString, ok := props["simpleString"].(map[string]any) + if !ok { + t.Fatal("simpleString not found in properties") + } + if simpleString["type"] != "multilineString" { + t.Errorf("simpleString should have type multilineString, got %v", simpleString["type"]) + } + + // Check stringWithEnum should not be present (or should not have multilineString) + if stringWithEnum, ok := props["stringWithEnum"].(map[string]any); ok { + if stringWithEnum["type"] == "multilineString" { + t.Error("stringWithEnum should not have multilineString type") + } + } + + // Check numberField should not be present + if numberField, ok := props["numberField"].(map[string]any); ok { + if numberField["type"] != nil { + t.Error("numberField should not have any type override") + } + } + + // Check nested object + nestedObject, ok := props["nestedObject"].(map[string]any) + if !ok { + t.Fatal("nestedObject not found in properties") + } + nestedProps, ok := nestedObject["properties"].(map[string]any) + if !ok { + t.Fatal("nestedObject.properties is not a map") + } + + // Check nestedString + nestedString, ok := nestedProps["nestedString"].(map[string]any) + if !ok { + t.Fatal("nestedString not found in nestedObject.properties") + } + if nestedString["type"] != "multilineString" { + t.Errorf("nestedString should have type multilineString, got %v", nestedString["type"]) + } + + // Check array of objects + arrayOfObjects, ok := props["arrayOfObjects"].(map[string]any) + if !ok { + t.Fatal("arrayOfObjects not found in properties") + } + items, ok := arrayOfObjects["items"].(map[string]any) + if !ok { + t.Fatal("arrayOfObjects.items is not a map") + } + itemProps, ok := items["properties"].(map[string]any) + if !ok { + t.Fatal("arrayOfObjects.items.properties is not a map") + } + itemString, ok := itemProps["itemString"].(map[string]any) + if !ok { + t.Fatal("itemString not found in arrayOfObjects.items.properties") + } + if itemString["type"] != "multilineString" { + t.Errorf("itemString should have type multilineString, got %v", itemString["type"]) + } +} + +func TestBuildMultilineStringSchemaEmpty(t *testing.T) { + schema, err := buildMultilineStringSchema("") + if err != nil { + t.Fatalf("buildMultilineStringSchema failed on empty string: %v", err) + } + if len(schema) != 0 { + t.Errorf("Expected empty schema for empty input, got %v", schema) + } +} + +func TestBuildMultilineStringSchemaInvalidJSON(t *testing.T) { + schema, err := buildMultilineStringSchema("{invalid json") + if err == nil { + t.Error("Expected error for invalid JSON") + } + if schema != nil { + t.Errorf("Expected nil schema for invalid JSON, got %v", schema) + } +} diff --git a/internal/controller/dashboard/factory.go b/internal/controller/dashboard/factory.go index 6f3e8f4c..c23bac4a 100644 --- a/internal/controller/dashboard/factory.go +++ b/internal/controller/dashboard/factory.go @@ -293,10 +293,10 @@ func secretsTab(kind string) map[string]any { "type": "EnrichedTable", "data": map[string]any{ "id": "secrets-table", - "fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecretstables", + "fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecrets", "clusterNamePartOfUrl": "{2}", "baseprefix": "/openapi-ui", - "customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables", + "customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecrets", "pathToItems": []any{"items"}, "labelsSelector": map[string]any{ "apps.cozystack.io/application.group": "apps.cozystack.io", diff --git a/internal/controller/dashboard/static_helpers.go b/internal/controller/dashboard/static_helpers.go index 5affc1c1..4c8aae30 100644 --- a/internal/controller/dashboard/static_helpers.go +++ b/internal/controller/dashboard/static_helpers.go @@ -122,7 +122,7 @@ func createCustomColumnsOverride(id string, additionalPrinterColumns []any) *das } } - if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables" { + if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecrets" { data["additionalPrinterColumnsTrimLengths"] = []any{ map[string]any{ "key": "Name", @@ -1046,6 +1046,15 @@ func createConverterBytesColumn(name, jsonPath string) map[string]any { } } +// createFlatMapColumn creates a flatMap column that expands a map into separate rows +func createFlatMapColumn(name, jsonPath string) map[string]any { + return map[string]any{ + "name": name, + "type": "flatMap", + "jsonPath": jsonPath, + } +} + // ---------------- Factory UI helper functions ---------------- // labelsEditor creates a Labels editor component diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go index 9ad923e7..31db0377 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -173,11 +173,12 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid createStringColumn("OBSERVED", ".status.observedReplicas"), }), - // Factory details v1alpha1 core cozystack io tenantsecretstables - createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecretstables", []any{ + // Factory details v1alpha1 core cozystack io tenantsecrets + createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecrets", []any{ createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"), - createStringColumn("Key", ".data.key"), - createSecretBase64Column("Value", ".data.value"), + createFlatMapColumn("Data", ".data"), + createStringColumn("Key", "_flatMapData_Key"), + createSecretBase64Column("Value", "_flatMapData_Value"), createTimestampColumn("Created", ".metadata.creationTimestamp"), }), diff --git a/packages/apps/tenant/templates/tenant.yaml b/packages/apps/tenant/templates/tenant.yaml index 9845b644..571a989d 100644 --- a/packages/apps/tenant/templates/tenant.yaml +++ b/packages/apps/tenant/templates/tenant.yaml @@ -35,7 +35,6 @@ rules: resources: - tenantmodules - tenantsecrets - - tenantsecretstables verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 @@ -193,7 +192,6 @@ rules: resources: - tenantmodules - tenantsecrets - - tenantsecretstables verbs: ["get", "list", "watch"] --- kind: RoleBinding @@ -292,7 +290,6 @@ rules: resources: - tenantmodules - tenantsecrets - - tenantsecretstables verbs: ["get", "list", "watch"] --- kind: RoleBinding @@ -367,7 +364,6 @@ rules: resources: - tenantmodules - tenantsecrets - - tenantsecretstables verbs: ["get", "list", "watch"] --- kind: RoleBinding diff --git a/packages/system/dashboard/images/openapi-ui-k8s-bff/Dockerfile b/packages/system/dashboard/images/openapi-ui-k8s-bff/Dockerfile index d8447e7a..51444ad0 100644 --- a/packages/system/dashboard/images/openapi-ui-k8s-bff/Dockerfile +++ b/packages/system/dashboard/images/openapi-ui-k8s-bff/Dockerfile @@ -1,15 +1,11 @@ # imported from https://github.com/cozystack/openapi-ui-k8s-bff ARG NODE_VERSION=20.18.1 FROM node:${NODE_VERSION}-alpine AS builder -RUN apk add git WORKDIR /src -ARG COMMIT_REF=88531ed6881b4ce4808e56c00905951d7ba8031c +ARG COMMIT_REF=ba56271739505284aee569f914fc90e6a9c670da RUN wget -O- https://github.com/PRO-Robotech/openapi-ui-k8s-bff/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1 -COPY patches /patches -RUN git apply /patches/*.diff - ENV PATH=/src/node_modules/.bin:$PATH RUN npm install RUN npm run build diff --git a/packages/system/dashboard/images/openapi-ui/Dockerfile b/packages/system/dashboard/images/openapi-ui/Dockerfile index af41cb65..a33be863 100644 --- a/packages/system/dashboard/images/openapi-ui/Dockerfile +++ b/packages/system/dashboard/images/openapi-ui/Dockerfile @@ -5,7 +5,7 @@ ARG NODE_VERSION=20.18.1 FROM node:${NODE_VERSION}-alpine AS openapi-k8s-toolkit-builder RUN apk add git WORKDIR /src -ARG COMMIT=e5f16b45de19f892de269cc4ef27e74aa62f4c92 +ARG COMMIT=7bd5380c6c4606640dd3bac68bf9dce469470518 RUN wget -O- https://github.com/cozystack/openapi-k8s-toolkit/archive/${COMMIT}.tar.gz | tar -xzvf- --strip-components=1 COPY openapi-k8s-toolkit/patches /patches @@ -19,14 +19,14 @@ RUN npm run build # openapi-ui # imported from https://github.com/cozystack/openapi-ui FROM node:${NODE_VERSION}-alpine AS builder -RUN apk add git +#RUN apk add git WORKDIR /src -ARG COMMIT_REF=9ce4367657f49c0032d8016b1d9491f8abbd2b15 +ARG COMMIT_REF=0c3629b2ce8545e81f7ece4d65372a188c802dfc RUN wget -O- https://github.com/PRO-Robotech/openapi-ui/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1 -COPY openapi-ui/patches /patches -RUN git apply /patches/*.diff +#COPY openapi-ui/patches /patches +#RUN git apply /patches/*.diff ENV PATH=/src/node_modules/.bin:$PATH diff --git a/packages/system/dashboard/images/openapi-ui/openapi-ui/patches/namespaces.diff b/packages/system/dashboard/images/openapi-ui/openapi-ui/patches/namespaces.diff deleted file mode 100644 index b76c86d3..00000000 --- a/packages/system/dashboard/images/openapi-ui/openapi-ui/patches/namespaces.diff +++ /dev/null @@ -1,89 +0,0 @@ -diff --git a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx -index b6fb99f..965bac0 100644 ---- a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx -+++ b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx -@@ -1,11 +1,16 @@ - import React, { FC, useState } from 'react' - import { Button, Alert, Spin, Typography } from 'antd' --import { filterSelectOptions, Spacer, useBuiltinResources } from '@prorobotech/openapi-k8s-toolkit' -+import { filterSelectOptions, Spacer, useApiResources } from '@prorobotech/openapi-k8s-toolkit' - import { useNavigate } from 'react-router-dom' - import { useSelector, useDispatch } from 'react-redux' - import { RootState } from 'store/store' - import { setCluster } from 'store/cluster/cluster/cluster' - import { Styled } from './styled' -+import { -+ BASE_PROJECTS_API_GROUP, -+ BASE_PROJECTS_VERSION, -+ BASE_PROJECTS_RESOURCE_NAME, -+} from 'constants/customizationApiGroupAndVersion' - - export const ListInsideClusterAndNs: FC = () => { - const clusterList = useSelector((state: RootState) => state.clusterList.clusterList) -@@ -17,9 +22,11 @@ export const ListInsideClusterAndNs: FC = () => { - const [selectedCluster, setSelectedCluster] = useState() - const [selectedNamespace, setSelectedNamespace] = useState() - -- const namespacesData = useBuiltinResources({ -+ const namespacesData = useApiResources({ - clusterName: selectedCluster || '', -- typeName: 'namespaces', -+ apiGroup: BASE_PROJECTS_API_GROUP, -+ apiVersion: BASE_PROJECTS_VERSION, -+ typeName: BASE_PROJECTS_RESOURCE_NAME, - limit: null, - isEnabled: selectedCluster !== undefined, - }) -diff --git a/src/hooks/useNavSelectorInside.ts b/src/hooks/useNavSelectorInside.ts -index 5736e2b..1ec0f71 100644 ---- a/src/hooks/useNavSelectorInside.ts -+++ b/src/hooks/useNavSelectorInside.ts -@@ -1,6 +1,11 @@ --import { TClusterList, TSingleResource, useBuiltinResources } from '@prorobotech/openapi-k8s-toolkit' -+import { TClusterList, TSingleResource, useApiResources } from '@prorobotech/openapi-k8s-toolkit' - import { useSelector } from 'react-redux' - import { RootState } from 'store/store' -+import { -+ BASE_PROJECTS_API_GROUP, -+ BASE_PROJECTS_VERSION, -+ BASE_PROJECTS_RESOURCE_NAME, -+} from 'constants/customizationApiGroupAndVersion' - - const mappedClusterToOptionInSidebar = ({ name }: TClusterList[number]): { value: string; label: string } => ({ - value: name, -@@ -15,9 +20,11 @@ const mappedNamespaceToOptionInSidebar = ({ metadata }: TSingleResource): { valu - export const useNavSelectorInside = (clusterName?: string) => { - const clusterList = useSelector((state: RootState) => state.clusterList.clusterList) - -- const { data: namespaces } = useBuiltinResources({ -+ const { data: namespaces } = useApiResources({ - clusterName: clusterName || '', -- typeName: 'namespaces', -+ apiGroup: BASE_PROJECTS_API_GROUP, -+ apiVersion: BASE_PROJECTS_VERSION, -+ typeName: BASE_PROJECTS_RESOURCE_NAME, - limit: null, - isEnabled: Boolean(clusterName), - }) -diff --git a/src/utils/getBacklink.ts b/src/utils/getBacklink.ts -index a862354..f24e2bc 100644 ---- a/src/utils/getBacklink.ts -+++ b/src/utils/getBacklink.ts -@@ -28,7 +28,7 @@ export const getFormsBackLink = ({ - } - - if (namespacesMode) { -- return `${baseprefix}/${clusterName}/builtin-table/namespaces` -+ return `${baseprefix}/${clusterName}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces` - } - - if (possibleProject) { -@@ -64,7 +64,7 @@ export const getTablesBackLink = ({ - } - - if (namespacesMode) { -- return `${baseprefix}/${clusterName}/builtin-table/namespaces` -+ return `${baseprefix}/${clusterName}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces` - } - - if (possibleProject) { diff --git a/packages/system/dashboard/images/openapi-ui/openapi-ui/patches/remove-inside-link.diff b/packages/system/dashboard/images/openapi-ui/openapi-ui/patches/remove-inside-link.diff deleted file mode 100644 index b131b53d..00000000 --- a/packages/system/dashboard/images/openapi-ui/openapi-ui/patches/remove-inside-link.diff +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/src/components/organisms/Header/organisms/User/User.tsx b/src/components/organisms/Header/organisms/User/User.tsx -index efe7ac3..80b715c 100644 ---- a/src/components/organisms/Header/organisms/User/User.tsx -+++ b/src/components/organisms/Header/organisms/User/User.tsx -@@ -23,10 +23,6 @@ export const User: FC = () => { - // key: '1', - // label: , - // }, -- { -- key: '2', -- label:
navigate(`${baseprefix}/inside/clusters`)}>Inside
, -- }, - { - key: '3', - label: ( diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index 36554996..96550a35 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,9 +1,9 @@ {{- $brandingConfig:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} -{{- $tenantText := "v0.37.5" }} +{{- $tenantText := "latest" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} -{{- $logoText := "false" }} +{{- $logoText := "" }} {{- $logoSvg := "PHN2ZyB3aWR0aD0iMTUwIiBoZWlnaHQ9IjMwIiB2aWV3Qm94PSIwIDAgMTUwIDMwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxwYXRoIGQ9Ik0xMzMuMzMxMDkgMjIuMzczOTg3VjQuNzk1Mjc2OWgyLjA0NDUyVjEyLjc3Mzg5aC4wNDk5bDguNTI3NzEtNy45NzkxNjcyaDIuNjE4MjdsLTkuNzc0NjQgOS4xMDExNTgyLjAyNDktMS4wOTcwNTkgMTAuMjk4MjQgOS41NzQ4ODloLTIuNjkyNzlsLTkuMDAxNjktOC4yNzgzNjNoLS4wNDk5djguMjc4MzYzem0tOS4xNzIzNi4yMjQzOTljLTEuNzI4NjkuMC0zLjIwODA1LS4zNjU2ODYtNC40MzgxLTEuMDk3MDU5LTEuMjMwNTktLjczMTM3My0yLjE3ODA0LTEuNzcwMjU0LTIuODQyOTMtMy4xMTY2NDUtLjY0ODI3LTEuMzQ2NjY3LS45NzIzOS0yLjk1MDk3OC0uOTcyMzktNC44MTI2NTQuMC0xLjg2MTY3Ny4zMjQxMi0zLjQ1NzM5OS45NzIzOS00Ljc4NzQ0NS42NjQ4OS0xLjM0NjM5MDYgMS42MTIzNC0yLjM4NTI3MjUgMi44NDI2Ni0zLjExNjkyMjQgMS4yMzAwMy0uNzMxMzcyOSAyLjcwOTQxLTEuMDk3MDU5MiA0LjQzODM3LTEuMDk3MDU5MiAxLjIxMzQyLjAgMi4zMzU0MS4xOTExNTQzIDMuMzY2MjYuNTczNDYyOCAxLjAzMDU3LjM4MjMwODUgMS44OTQ5My45MzkxNDkxIDIuNTkzMDUgMS42NzA1MjE5bC0uNzk3ODYgMS42NzA1MjE5Yy0uNzY0NjItLjcxNDc1LTEuNTYyNDgtMS4yMzAwMzYtMi4zOTM1OS0xLjU0NTg1NjEtLjgxNDQ3LS4zMzI0NDIxLTEuNzIwMzgtLjQ5ODY2MzItMi43MTc3MS0uNDk4NjYzMi0xLjk3ODU5LjAtMy40OTEyLjYyMzMyOS00LjUzODM4IDEuODY5OTg4My0xLjA0NzIxIDEuMjQ2NjU3LTEuNTcwOCAzLjAwMDU2Ni0xLjU3MDggNS4yNjE0NTEuMCAyLjI2MDYwNi41MjM1OSA0LjAyMjU0OSAxLjU3MDggNS4yODYxMDggMS4wNDcxOCAxLjI0NjY1NyAyLjU1OTc5IDEuODY5OTg2IDQuNTM4MDkgMS44Njk5ODYuOTk3MzQuMCAxLjkwMzI0LS4xNTc5MDkgMi43MTc3My0uNDczNzMuODMxMzgtLjMzMjQ0MiAxLjYyOTI0LS44NTYwMzggMi4zOTM4Ni0xLjU3MDc4OWwuNzk3ODYgMS42NzA1MjJjLS42OTgxMi43MTQ3NTEtMS41NjI0OCAxLjI3MTg2OS0yLjU5MzA1IDEuNjcwNzk5LTEuMDMwNTguMzgyMzA5LTIuMTUyNTcuNTczNDYyLTMuMzY1OTcuNTczNDYyek05Ni45ODQ2MzEgMjIuMzczOTg3IDEwNC43Mzk0IDQuNzk0OTk5OWgxLjc0NTMzbDcuNzU0NzYgMTcuNTc4OTg3MWgtMi4xMTkzMmwtMi4xNjkxOS01LjAxMTg0MS45OTczMy41MjM1OTVoLTEwLjcyMjA5bDEuMDIyMjctLjUyMzU5NS0yLjE0NDI1NCA1LjAxMTg0MXptOC42MDI0OTktMTUuMTg1NDAzNC00LjAxNDI0IDkuNDUwNTAwNC0uNTk4NC0uNDczNzNoOS4yMjU1NWwtLjU0ODUzLjQ3MzczLTQuMDE0MjUtOS40NTA1MDA0ek04OS44OTM5MjEgMjIuMzczOTg3VjYuNTY1NTMxNkg4My41MTAyMDJWNC43OTUyNzY5aDE0LjgzNjMzVjYuNTY1NTMxNkg5MS45NjMzNjZWMjIuMzc0MjY1eiIgZmlsbD17dG9rZW4uY29sb3JUZXh0fT48L3BhdGg+CiAgPHBhdGggZD0ibTY3Ljg1NDM4NSA0Ljc3MzExNDJoMTQuMDgwODd2MS43NjAwMDQyaC0xNC4wODA4N3ptMCAxNS44NDA4Njk4aDE0LjA4MDg3djEuNzYwMDAzaC0xNC4wODA4N3ptMTQuMDgwODctNy45MjA0MzVoLTE0LjA4MDg3djEuNzYwMDA1aDE0LjA4MDg3eiIgZmlsbD17dG9rZW4uY29sb3JUZXh0fT48L3BhdGg+CiAgPHBhdGggZD0ibTU3LjY2NDQ3MyAyMi4zNzM5ODd2LTkuMTAxMTU4bC40NDg4MDQgMS40MjExOTEtNy4xODEzMDktOS44OTkwMjAxaDIuMzkzODYxbDUuNjYwMTA5IDcuODI5NTY3MWgtLjUyMzYwNmw1LjY2MDM5MS03LjgyOTU2NzFoMi4zMTg3ODdsLTcuMTU2MzczIDkuODk4NzQyMS40MjQxNC0xLjQyMTE4OXY5LjEwMTE1OHptLTIwLjE4ODEwMi4wVjIwLjg1MzA2NUw0OC4xNDg1OTYgNS43NjczOTMydi43OTc4NjEzSDM3LjQ3NjM3MVY0Ljc5NDk5OTlINTAuMDY4NDVWNi4zMTU5MjI4TDM5LjM5NjUwMSAyMS4zNzY2NjF2LS43NzI5MjdoMTEuMDIxMjl2MS43NzAyNTN6bS0xMC4zOTYyOTguMjI0Mzk5Yy0xLjIxMzQxNC4wLTIuMzE5MDYyLS4yMDc3NzYtMy4zMTYzODktLjYyMzMyOC0uOTk3MzI3LS40MzIxNzUtMS44NDUwNTUtMS4wMzg4ODItMi41NDMxODQtMS44MjAxMjEtLjY5ODQwNi0uNzgxMjM5LTEuMjM4NjI0LTEuNzI4Ny0xLjYyMDkzMy0yLjg0MjY1OC0uMzY1Njg2LTEuMTEzNjgyLS41NDg1MjktMi4zNjAzMzktLjU0ODUyOS0zLjc0MDI1MS4wLTEuMzk2MjU3LjE4Mjg0My0yLjY0MjkxNy41NDg1MjktMy43NDAyNTIuMzgyMzA5LTEuMTEzNjgyLjkyMjUyNy0yLjA1MjgzMDkgMS42MjA2NTYtMi44MTc0NDc1LjY5ODEyOS0uNzgxMjM5MiAxLjUzNzU0NS0xLjM3OTkxMjEgMi41MTgyNS0xLjc5NTQ2NDguOTk3NjA0LS40MzIxNzQ5IDIuMTExMjg3LS42NDgyNjIzIDMuMzQxNi0uNjQ4MjYyMyAxLjI0NjY1Ny4wIDIuMzYwMzM5LjIwNzc3NjMgMy4zNDEwNDQuNjIzMzI5MS45OTczMjcuNDE1NTUyNyAxLjg0NTA1NSAxLjAxMzk0ODYgMi41NDM0NTkgMS43OTUxODc3LjcxNDc1MS43ODEyMzk4IDEuMjU0OTY5IDEuNzI4Njk5OCAxLjYyMDY1NSAyLjg0MjY1NzguMzgyMzEgMS4wOTcwNTkuNTczNDY0IDIuMzM1NDA2LjU3MzQ2NCAzLjcxNTA0Mi4wIDEuMzk2NTMzLS4xOTExNTQgMi42NTE3NzktLjU3MzQ2NCAzLjc2NTQ2MS0uMzgyMzA4IDEuMTEzNjgyLS45MjI1MjYgMi4wNjExNDItMS42MjA2NTUgMi44NDIzODFzLTEuNTQ1ODU2IDEuMzg3OTQ2LTIuNTQzMTgzIDEuODIwMzk4Yy0uOTgwNzA0LjQxNTU1Mi0yLjA5NDY2My42MjMzMjgtMy4zNDEzMi42MjMzMjh6bTAtMS44MjAxMmMxLjI2MzI3OS4wIDIuMzI3MDk0LS4yODI1NzYgMy4xOTE0NDMtLjg0NzcyOC44ODA5NzMtLjU2NTE1MiAxLjU1NDE2OS0xLjM4Nzk0NiAyLjAxOTU4OC0yLjQ2ODY2LjQ2NTY5Ni0xLjA4MDQzNy42OTg0MDYtMi4zNzY5NjEuNjk4NDA2LTMuODg5ODUuMC0xLjUyOTIzNC0uMjMyNzEtMi44MjU3NTgtLjY5ODEyOS0zLjg4OTg1MS0uNDY1NDE5LTEuMDYzODE1LTEuMTM4NjE0LTEuODc4Mjk3OC0yLjAxOTU4Ny0yLjQ0MzQ1LS44NjQzNS0uNTY1MTUxOC0xLjkyODQ0Mi0uODQ3NzI3Ni0zLjE5MTcyMS0uODQ3NzI3Ni0xLjIzMDAzOC4wLTIuMjg1ODE4LjI4MjU3NTgtMy4xNjY3OTEuODQ3NzI3Ni0uODY0MzUuNTY1MTUyMi0xLjUyOTIzNCAxLjM4Nzk0Ni0xLjk5NDY1MyAyLjQ2ODM4My0uNDY1NDE5IDEuMDYzODE1LS42OTgxMjkgMi4zNTIwMjktLjY5ODEyOSAzLjg2NDY0MS4wIDEuNTEyODg5LjIzMjcxIDIuODA5NjkuNjk4MTI5IDMuODkwMTI3LjQ2NTQxOSAxLjA2MzgxNSAxLjEzMDMwMyAxLjg4NjYwOSAxLjk5NDY1MyAyLjQ2ODM4My44ODA5NzMuNTY1MTUxIDEuOTM2NDc4Ljg0NzcyNyAzLjE2NjUxMy44NDc3Mjd6bS0xNi4wNDE3MjQgMS44MjAxMmMtMS43Mjg2OTgxLjAtMy4yMDgzNDE3LS4zNjU2ODYtNC40Mzg2NTYxLTEuMDk3MDU5QzUuMzY5NjU3MSAyMC43Njk5NTQgNC40MjIxOTY2IDE5LjczMTA3MyAzLjc1NzMxMzEgMTguMzg0NjgyYy0uNjQ4MjYzLTEuMzQ2NjY3LS45NzIzOTM2LTIuOTUwOTc4LS45NzIzOTM2LTQuODEyNjU0LjAtMS44NjE2NzcuMzI0MTMwNi0zLjQ1NzM5OS45NzIzOTM2LTQuNzg3NDQ1LjY2NDg4MzUtMS4zNDYzOTA2IDEuNjEyMzQ0LTIuMzg1MjcyNSAyLjg0MjM3OTgtMy4xMTY5MjI0QzcuODI5NzI5OCA0LjkzNjI4NzcgOS4zMDkzNzM0IDQuNTcwNjAxNCAxMS4wMzgzNDkgNC41NzA2MDE0YzEuMjEzNDE0LjAgMi4zMzU0MDguMTkxMTU0MyAzLjM2NTk3OC41NzM0NjI4IDEuMDMwNTcyLjM4MjMwODUgMS44OTQ5MjIuOTM5MTQ5MSAyLjU5MzMyNiAxLjY3MDUyMTlMMTYuMTk5NzkxIDguNDg1MTA4QzE1LjQzNTE3NSA3Ljc3MDM1OCAxNC42MzczMTMgNy4yNTUwNzIgMTMuODA2MjA5IDYuOTM5MjUxOSAxMi45OTE0NDcgNi42MDY4MDk4IDEyLjA4NTU0MyA2LjQ0MDU4ODcgMTEuMDg4MjE2IDYuNDQwNTg4N2MtMS45NzgwMzA0LjAtMy40OTA5MTg4LjYyMzMyOS00LjUzODM4OTIgMS44Njk5ODgzLTEuMDQ3MTkyNyAxLjI0NjY1Ny0xLjU3MDc4ODYgMy4wMDA1NjYtMS41NzA3ODg2IDUuMjYxNDUxLjAgMi4yNjA2MDYuNTIzNTk1OSA0LjAyMjU0OSAxLjU3MDc4ODYgNS4yODYxMDggMS4wNDcxOTI5IDEuMjQ2NjU3IDIuNTYwMDgyMyAxLjg2OTk4NiA0LjUzODM4OTIgMS44Njk5ODYuOTk3MzI3LjAgMS45MDMyMzEtLjE1NzkwOSAyLjcxNzcxNS0uNDczNzMuODMxMTA2LS4zMzI0NDIgMS42Mjg5NjgtLjg1NjAzOCAyLjM5MzU4NC0xLjU3MDc4OWwuNzk4MTM4IDEuNjcwNTIyYy0uNjk4MTI4LjcxNDc1MS0xLjU2MjQ3OCAxLjI3MTg2OS0yLjU5MzA0OCAxLjY3MDc5OS0xLjAzMDU3Mi4zODIzMDktMi4xNTI4NDIuNTczNDYyLTMuMzY2MjU2LjU3MzQ2MnoiIGZpbGw9e3Rva2VuLmNvbG9yVGV4dH0+PC9wYXRoPgo8L3N2Zz4K" }} {{- $iconSvg := "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgd2lkdGg9IjkxIgogICBoZWlnaHQ9IjkxIgogICB2aWV3Qm94PSIwIDAgNjguMjUwMDI0IDY4LjI1MDAyNCIKICAgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQgbWVldCIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0ic3ZnODI2IgogICBzb2RpcG9kaTpkb2NuYW1lPSJmYXZpY29uLnN2ZyIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4xLjEgKGMzMDg0ZWYsIDIwMjEtMDktMjIpIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM4MzAiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJuYW1lZHZpZXc4MjgiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZWNoZWNrZXJib2FyZD0iMCIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgaW5rc2NhcGU6em9vbT0iMC43NzAzNTc0MSIKICAgICBpbmtzY2FwZTpjeD0iNDM2LjgxMDIzIgogICAgIGlua3NjYXBlOmN5PSI1NDEuOTU2MjMiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxNzIwIgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjEzODciCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjE3MjAiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjI1IgogICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjAiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnODI2IiAvPgogIDxyZWN0CiAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC42MTc5MDUiCiAgICAgaWQ9InJlY3Q5MzgiCiAgICAgd2lkdGg9IjY4LjI1MDAyMyIKICAgICBoZWlnaHQ9IjY4LjI1MDAyMyIKICAgICB4PSIwIgogICAgIHk9Ii0xLjc3NjM1NjhlLTE1IiAvPgogIDxwYXRoCiAgICAgZmlsbC1ydWxlPSJldmVub2RkIgogICAgIGNsaXAtcnVsZT0iZXZlbm9kZCIKICAgICBkPSJtIDE2LjYwMTU5OCwxMy45MjY5MSBjIDAsLTEuMjgwMTE1IDEuMDE1MDUxLC0yLjMxNzg2MSAyLjI2NzQyNCwtMi4zMTc4NjEgaCAzMS43NDM5MzcgYyAxLjI1MjM3OCwwIDIuMjY3NDIsMS4wMzc3NDYgMi4yNjc0MiwyLjMxNzg2MSB2IDAgYyAwLDEuMjgwMTMzIC0xLjAxNTA0MiwyLjMxNzg3IC0yLjI2NzQyLDIuMzE3ODcgSCAxOC44NjkwMjIgYyAtMS4yNTIzNzMsMCAtMi4yNjc0MjQsLTEuMDM3NzM3IC0yLjI2NzQyNCwtMi4zMTc4NyB6IG0gMCw0MS43MjE1NzIgYyAwLC0xLjI4MDA4MSAxLjAxNTA1MSwtMi4zMTc4NjUgMi4yNjc0MjQsLTIuMzE3ODY1IGggMzEuNzQzOTM3IGMgMS4yNTIzNzgsMCAyLjI2NzQyLDEuMDM3Nzg0IDIuMjY3NDIsMi4zMTc4NjUgdiAwIGMgMCwxLjI4MDE1OCAtMS4wMTUwNDIsMi4zMTc4NjYgLTIuMjY3NDIsMi4zMTc4NjYgSCAxOC44NjkwMjIgYyAtMS4yNTIzNzMsMCAtMi4yNjc0MjQsLTEuMDM3NzA4IC0yLjI2NzQyNCwtMi4zMTc4NjYgeiBtIDM2LjI3ODc4MSwtMjAuODYwOCBjIDAsLTEuMjgwMDggLTEuMDE1MDQyLC0yLjMxNzg2NSAtMi4yNjc0MiwtMi4zMTc4NjUgSCAxOC44NjkwMjIgYyAtMS4yNTIzNzMsMCAtMi4yNjc0MjQsMS4wMzc3ODUgLTIuMjY3NDI0LDIuMzE3ODY1IHYgMCBjIDAsMS4yODAxNTggMS4wMTUwNTEsMi4zMTc4NjYgMi4yNjc0MjQsMi4zMTc4NjYgaCAzMS43NDM5MzcgYyAxLjI1MjM3OCwwIDIuMjY3NDIsLTEuMDM3NzA4IDIuMjY3NDIsLTIuMzE3ODY2IHoiCiAgICAgZmlsbD0iI2ZmZmZmZiIKICAgICBpZD0icGF0aDg0MCIKICAgICBzdHlsZT0ic3Ryb2tlLXdpZHRoOjAuNzY0MTYzIiAvPgo8L3N2Zz4K" }} @@ -16,9 +16,9 @@ metadata: app.kubernetes.io/instance: incloud-web app.kubernetes.io/name: web data: - TENANT_TEXT: {{ $brandingConfig | dig "data" "tenantText" $tenantText | quote }} + CUSTOM_TENANT_TEXT: {{ $brandingConfig | dig "data" "tenantText" $tenantText | quote }} FOOTER_TEXT: {{ $brandingConfig | dig "data" "footerText" $footerText | quote }} TITLE_TEXT: {{ $brandingConfig | dig "data" "titleText" $titleText | quote }} LOGO_TEXT: {{ $brandingConfig | dig "data" "logoText" $logoText | quote }} - LOGO_SVG: {{ $brandingConfig | dig "data" "logoSvg" $logoSvg | quote }} + CUSTOM_LOGO_SVG: {{ $brandingConfig | dig "data" "logoSvg" $logoSvg | quote }} ICON_SVG: {{ $brandingConfig | dig "data" "iconSvg" $iconSvg | quote }} diff --git a/packages/system/dashboard/templates/web.yaml b/packages/system/dashboard/templates/web.yaml index 8a12d3f5..b647c743 100644 --- a/packages/system/dashboard/templates/web.yaml +++ b/packages/system/dashboard/templates/web.yaml @@ -42,10 +42,12 @@ spec: value: dashboard.cozystack.io - name: BASE_API_VERSION value: v1alpha1 + - name: BASE_NAMESPACE_FULL_PATH + value: "/apis/core.cozystack.io/v1alpha1/tenantnamespaces" - name: LOGGER - value: "TRUE" + value: "true" - name: LOGGER_WITH_HEADERS - value: "TRUE" + value: "false" - name: PORT value: "64231" image: {{ .Values.openapiUIK8sBff.image | quote }} @@ -92,6 +94,8 @@ spec: - env: - name: BASEPREFIX value: /openapi-ui + - name: HIDE_INSIDE + value: "true" - name: CUSTOMIZATION_API_GROUP value: dashboard.cozystack.io - name: CUSTOMIZATION_API_VERSION @@ -122,6 +126,12 @@ spec: value: tenantnamespaces - name: PROJECTS_VERSION value: v1alpha1 + - name: CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP + value: core.cozystack.io + - name: CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION + value: v1alpha1 + - name: CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME + value: tenantnamespaces - name: USE_NAMESPACE_NAV value: "true" - name: LOGIN_URL @@ -140,21 +150,21 @@ spec: configMapKeyRef: name: incloud-web-dashboard-config key: TITLE_TEXT - - name: TENANT_TEXT + - name: CUSTOM_TENANT_TEXT valueFrom: configMapKeyRef: name: incloud-web-dashboard-config - key: TENANT_TEXT + key: CUSTOM_TENANT_TEXT - name: LOGO_TEXT valueFrom: configMapKeyRef: name: incloud-web-dashboard-config key: LOGO_TEXT - - name: LOGO_SVG + - name: CUSTOM_LOGO_SVG valueFrom: configMapKeyRef: name: incloud-web-dashboard-config - key: LOGO_SVG + key: CUSTOM_LOGO_SVG - name: ICON_SVG valueFrom: configMapKeyRef: diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index 79e3d58b..71501fe0 100644 --- a/packages/system/dashboard/values.yaml +++ b/packages/system/dashboard/values.yaml @@ -1,6 +1,6 @@ openapiUI: - image: ghcr.io/cozystack/cozystack/openapi-ui:v0.37.5@sha256:5c66e9aebbf6b435b75f1607ab800028a22d0e99a1acc5cf405f07581ce414f8 + image: ghcr.io/cozystack/cozystack/openapi-ui:latest@sha256:77991f2482c0026d082582b22a8ffb191f3ba6fc948b2f125ef9b1081538f865 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.37.5@sha256:194ff92c584a950f22067220436e6d8705e614422227e9cac7e889a704b709de + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:latest@sha256:8386f0747266726afb2b30db662092d66b0af0370e3becd8bee9684125fa9cc9 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v0.37.5@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b + image: ghcr.io/cozystack/cozystack/token-proxy:latest@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b diff --git a/pkg/apis/core/v1alpha1/register.go b/pkg/apis/core/v1alpha1/register.go index 39976e39..84923d29 100644 --- a/pkg/apis/core/v1alpha1/register.go +++ b/pkg/apis/core/v1alpha1/register.go @@ -59,11 +59,9 @@ func RegisterStaticTypes(scheme *runtime.Scheme) { &TenantNamespaceList{}, &TenantSecret{}, &TenantSecretList{}, - &TenantSecretsTable{}, - &TenantSecretsTableList{}, &TenantModule{}, &TenantModuleList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - klog.V(1).Info("Registered static kinds: TenantNamespace, TenantSecret, TenantSecretsTable, TenantModule") + klog.V(1).Info("Registered static kinds: TenantNamespace, TenantSecret, TenantModule") } diff --git a/pkg/apis/core/v1alpha1/tenantsecretstable_types.go b/pkg/apis/core/v1alpha1/tenantsecretstable_types.go deleted file mode 100644 index 94119696..00000000 --- a/pkg/apis/core/v1alpha1/tenantsecretstable_types.go +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -package v1alpha1 - -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - -// TenantSecretEntry represents a single key from a Secret's data. -type TenantSecretEntry struct { - Name string `json:"name,omitempty"` - Key string `json:"key,omitempty"` - Value string `json:"value,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// TenantSecretsTable is a virtual, namespaced resource that exposes every key -// of Secrets labelled cozystack.io/ui=true as a separate object. -type TenantSecretsTable struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Data TenantSecretEntry `json:"data,omitempty"` -} - -// DeepCopy methods are generated by deepcopy-gen - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type TenantSecretsTableList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []TenantSecretsTable `json:"items"` -} - -// DeepCopy methods are generated by deepcopy-gen diff --git a/pkg/apis/core/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/core/v1alpha1/zz_generated.deepcopy.go index 54a7f1e6..a19b1d1a 100644 --- a/pkg/apis/core/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/core/v1alpha1/zz_generated.deepcopy.go @@ -216,22 +216,6 @@ func (in *TenantSecret) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TenantSecretEntry) DeepCopyInto(out *TenantSecretEntry) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSecretEntry. -func (in *TenantSecretEntry) DeepCopy() *TenantSecretEntry { - if in == nil { - return nil - } - out := new(TenantSecretEntry) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TenantSecretList) DeepCopyInto(out *TenantSecretList) { *out = *in @@ -264,63 +248,3 @@ func (in *TenantSecretList) DeepCopyObject() runtime.Object { } return nil } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TenantSecretsTable) DeepCopyInto(out *TenantSecretsTable) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Data = in.Data - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSecretsTable. -func (in *TenantSecretsTable) DeepCopy() *TenantSecretsTable { - if in == nil { - return nil - } - out := new(TenantSecretsTable) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TenantSecretsTable) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TenantSecretsTableList) DeepCopyInto(out *TenantSecretsTableList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]TenantSecretsTable, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSecretsTableList. -func (in *TenantSecretsTableList) DeepCopy() *TenantSecretsTableList { - if in == nil { - return nil - } - out := new(TenantSecretsTableList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TenantSecretsTableList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index d5814ee7..ba5cee5b 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -44,7 +44,6 @@ import ( tenantmodulestorage "github.com/cozystack/cozystack/pkg/registry/core/tenantmodule" tenantnamespacestorage "github.com/cozystack/cozystack/pkg/registry/core/tenantnamespace" tenantsecretstorage "github.com/cozystack/cozystack/pkg/registry/core/tenantsecret" - tenantsecretstablestorage "github.com/cozystack/cozystack/pkg/registry/core/tenantsecretstable" ) var ( @@ -177,9 +176,6 @@ func (c completedConfig) New() (*CozyServer, error) { coreV1alpha1Storage["tenantsecrets"] = cozyregistry.RESTInPeace( tenantsecretstorage.NewREST(cli, watchCli), ) - coreV1alpha1Storage["tenantsecretstables"] = cozyregistry.RESTInPeace( - tenantsecretstablestorage.NewREST(cli, watchCli), - ) coreV1alpha1Storage["tenantmodules"] = cozyregistry.RESTInPeace( tenantmodulestorage.NewREST(cli, watchCli), ) diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index a88f259f..4202bbac 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -39,10 +39,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantNamespace": schema_pkg_apis_core_v1alpha1_TenantNamespace(ref), "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantNamespaceList": schema_pkg_apis_core_v1alpha1_TenantNamespaceList(ref), "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecret": schema_pkg_apis_core_v1alpha1_TenantSecret(ref), - "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretEntry": schema_pkg_apis_core_v1alpha1_TenantSecretEntry(ref), "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretList": schema_pkg_apis_core_v1alpha1_TenantSecretList(ref), - "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretsTable": schema_pkg_apis_core_v1alpha1_TenantSecretsTable(ref), - "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretsTableList": schema_pkg_apis_core_v1alpha1_TenantSecretsTableList(ref), "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionRequest": schema_pkg_apis_apiextensions_v1_ConversionRequest(ref), "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionResponse": schema_pkg_apis_apiextensions_v1_ConversionResponse(ref), "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionReview": schema_pkg_apis_apiextensions_v1_ConversionReview(ref), @@ -557,37 +554,6 @@ func schema_pkg_apis_core_v1alpha1_TenantSecret(ref common.ReferenceCallback) co } } -func schema_pkg_apis_core_v1alpha1_TenantSecretEntry(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "TenantSecretEntry represents a single key from a Secret's data.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "key": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "value": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - } -} - func schema_pkg_apis_core_v1alpha1_TenantSecretList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -636,95 +602,6 @@ func schema_pkg_apis_core_v1alpha1_TenantSecretList(ref common.ReferenceCallback } } -func schema_pkg_apis_core_v1alpha1_TenantSecretsTable(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "TenantSecretsTable is a virtual, namespaced resource that exposes every key of Secrets labelled cozystack.io/ui=true as a separate object.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "data": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretEntry"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretEntry", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_core_v1alpha1_TenantSecretsTableList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretsTable"), - }, - }, - }, - }, - }, - }, - Required: []string{"items"}, - }, - }, - Dependencies: []string{ - "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1.TenantSecretsTable", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - func schema_pkg_apis_apiextensions_v1_ConversionRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/registry/core/tenantsecretstable/rest.go b/pkg/registry/core/tenantsecretstable/rest.go deleted file mode 100644 index de2604ca..00000000 --- a/pkg/registry/core/tenantsecretstable/rest.go +++ /dev/null @@ -1,335 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// TenantSecretsTable registry – namespaced, read-only flattened view over -// Secrets labelled "internal.cozystack.io/tenantresource=true". Each data key is a separate object. - -package tenantsecretstable - -import ( - "context" - "encoding/base64" - "fmt" - "net/http" - "sort" - "time" - - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/selection" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - - corev1alpha1 "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1" -) - -const ( - tsLabelKey = corev1alpha1.TenantResourceLabelKey - tsLabelValue = corev1alpha1.TenantResourceLabelValue - kindObj = "TenantSecretsTable" - kindObjList = "TenantSecretsTableList" - singularName = "tenantsecretstable" - resourcePlural = "tenantsecretstables" -) - -type REST struct { - c client.Client - w client.WithWatch - gvr schema.GroupVersionResource -} - -func NewREST(c client.Client, w client.WithWatch) *REST { - return &REST{ - c: c, - w: w, - gvr: schema.GroupVersionResource{ - Group: corev1alpha1.GroupName, - Version: "v1alpha1", - Resource: resourcePlural, - }, - } -} - -var ( - _ rest.Getter = &REST{} - _ rest.Lister = &REST{} - _ rest.Watcher = &REST{} - _ rest.TableConvertor = &REST{} - _ rest.Scoper = &REST{} - _ rest.SingularNameProvider = &REST{} - _ rest.Storage = &REST{} -) - -func (*REST) NamespaceScoped() bool { return true } -func (*REST) New() runtime.Object { return &corev1alpha1.TenantSecretsTable{} } -func (*REST) NewList() runtime.Object { - return &corev1alpha1.TenantSecretsTableList{} -} -func (*REST) Kind() string { return kindObj } -func (r *REST) GroupVersionKind(_ schema.GroupVersion) schema.GroupVersionKind { - return r.gvr.GroupVersion().WithKind(kindObj) -} -func (*REST) GetSingularName() string { return singularName } -func (*REST) Destroy() {} - -func nsFrom(ctx context.Context) (string, error) { - ns, ok := request.NamespaceFrom(ctx) - if !ok { - return "", fmt.Errorf("namespace required") - } - return ns, nil -} - -// ----------------------- -// Get/List -// ----------------------- - -func (r *REST) Get(ctx context.Context, name string, opts *metav1.GetOptions) (runtime.Object, error) { - ns, err := nsFrom(ctx) - if err != nil { - return nil, err - } - - // We need to identify secret name and key. Iterate secrets in namespace with tenant secret label - // and return the matching composed object. - list := &corev1.SecretList{} - err = r.c.List(ctx, list, - &client.ListOptions{ - Namespace: ns, - Raw: &metav1.ListOptions{ - LabelSelector: labels.Set{tsLabelKey: tsLabelValue}.AsSelector().String(), - }, - }) - if err != nil { - return nil, err - } - for i := range list.Items { - sec := &list.Items[i] - for k, v := range sec.Data { - composed := composedName(sec.Name, k) - if composed == name { - return secretKeyToObj(sec, k, v), nil - } - } - } - return nil, apierrors.NewNotFound(r.gvr.GroupResource(), name) -} - -func (r *REST) List(ctx context.Context, opts *metainternal.ListOptions) (runtime.Object, error) { - ns, err := nsFrom(ctx) - if err != nil { - return nil, err - } - - sel := labels.NewSelector() - req, _ := labels.NewRequirement(tsLabelKey, selection.Equals, []string{tsLabelValue}) - sel = sel.Add(*req) - if opts.LabelSelector != nil { - if reqs, _ := opts.LabelSelector.Requirements(); len(reqs) > 0 { - sel = sel.Add(reqs...) - } - } - fieldSel := "" - if opts.FieldSelector != nil { - fieldSel = opts.FieldSelector.String() - } - - list := &corev1.SecretList{} - err = r.c.List(ctx, list, - &client.ListOptions{ - Namespace: ns, - Raw: &metav1.ListOptions{ - LabelSelector: labels.Set{tsLabelKey: tsLabelValue}.AsSelector().String(), - FieldSelector: fieldSel, - }, - }) - if err != nil { - return nil, err - } - - out := &corev1alpha1.TenantSecretsTableList{ - TypeMeta: metav1.TypeMeta{APIVersion: corev1alpha1.SchemeGroupVersion.String(), Kind: kindObjList}, - ListMeta: list.ListMeta, - } - - for i := range list.Items { - sec := &list.Items[i] - // Ensure stable ordering of keys - keys := make([]string, 0, len(sec.Data)) - for k := range sec.Data { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - v := sec.Data[k] - o := secretKeyToObj(sec, k, v) - out.Items = append(out.Items, *o) - } - } - - sort.Slice(out.Items, func(i, j int) bool { return out.Items[i].Name < out.Items[j].Name }) - return out, nil -} - -// ----------------------- -// Watch -// ----------------------- - -func (r *REST) Watch(ctx context.Context, opts *metainternal.ListOptions) (watch.Interface, error) { - ns, err := nsFrom(ctx) - if err != nil { - return nil, err - } - - secList := &corev1.SecretList{} - ls := labels.Set{tsLabelKey: tsLabelValue}.AsSelector().String() - base, err := r.w.Watch(ctx, secList, &client.ListOptions{Namespace: ns, Raw: &metav1.ListOptions{ - Watch: true, - LabelSelector: ls, - ResourceVersion: opts.ResourceVersion, - }}) - if err != nil { - return nil, err - } - - ch := make(chan watch.Event) - proxy := watch.NewProxyWatcher(ch) - - go func() { - defer proxy.Stop() - for ev := range base.ResultChan() { - sec, ok := ev.Object.(*corev1.Secret) - if !ok || sec == nil { - continue - } - // Emit an event per key - for k, v := range sec.Data { - obj := secretKeyToObj(sec, k, v) - ch <- watch.Event{Type: ev.Type, Object: obj} - } - } - }() - - return proxy, nil -} - -// ----------------------- -// TableConvertor -// ----------------------- - -func (r *REST) ConvertToTable(_ context.Context, obj runtime.Object, _ runtime.Object) (*metav1.Table, error) { - now := time.Now() - row := func(o *corev1alpha1.TenantSecretsTable) metav1.TableRow { - return metav1.TableRow{ - Cells: []interface{}{o.Name, o.Data.Name, o.Data.Key, humanAge(o.CreationTimestamp.Time, now)}, - Object: runtime.RawExtension{Object: o}, - } - } - tbl := &metav1.Table{ - TypeMeta: metav1.TypeMeta{APIVersion: "meta.k8s.io/v1", Kind: "Table"}, - ColumnDefinitions: []metav1.TableColumnDefinition{ - {Name: "NAME", Type: "string"}, - {Name: "SECRET", Type: "string"}, - {Name: "KEY", Type: "string"}, - {Name: "AGE", Type: "string"}, - }, - } - switch v := obj.(type) { - case *corev1alpha1.TenantSecretsTableList: - for i := range v.Items { - tbl.Rows = append(tbl.Rows, row(&v.Items[i])) - } - tbl.ListMeta.ResourceVersion = v.ListMeta.ResourceVersion - case *corev1alpha1.TenantSecretsTable: - tbl.Rows = append(tbl.Rows, row(v)) - tbl.ListMeta.ResourceVersion = v.ResourceVersion - default: - return nil, notAcceptable{r.gvr.GroupResource(), fmt.Sprintf("unexpected %T", obj)} - } - return tbl, nil -} - -// ----------------------- -// Helpers -// ----------------------- - -func composedName(secretName, key string) string { - return secretName + "-" + key -} - -func humanAge(t time.Time, now time.Time) string { - d := now.Sub(t) - // simple human duration - if d.Hours() >= 24 { - return fmt.Sprintf("%dd", int(d.Hours()/24)) - } - if d.Hours() >= 1 { - return fmt.Sprintf("%dh", int(d.Hours())) - } - if d.Minutes() >= 1 { - return fmt.Sprintf("%dm", int(d.Minutes())) - } - return fmt.Sprintf("%ds", int(d.Seconds())) -} - -func secretKeyToObj(sec *corev1.Secret, key string, val []byte) *corev1alpha1.TenantSecretsTable { - return &corev1alpha1.TenantSecretsTable{ - TypeMeta: metav1.TypeMeta{APIVersion: corev1alpha1.SchemeGroupVersion.String(), Kind: kindObj}, - ObjectMeta: metav1.ObjectMeta{ - Name: sec.Name, - Namespace: sec.Namespace, - UID: sec.UID, - ResourceVersion: sec.ResourceVersion, - CreationTimestamp: sec.CreationTimestamp, - Labels: filterUserLabels(sec.Labels), - Annotations: sec.Annotations, - }, - Data: corev1alpha1.TenantSecretEntry{ - Name: sec.Name, - Key: key, - Value: toBase64String(val), - }, - } -} - -func filterUserLabels(m map[string]string) map[string]string { - if m == nil { - return nil - } - out := make(map[string]string, len(m)) - for k, v := range m { - if k == tsLabelKey { - continue - } - out[k] = v - } - return out -} - -func toBase64String(b []byte) string { - const enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - // Minimal base64 encoder to avoid extra deps; for readability we could use stdlib encoding/base64 - // but keeping inline is fine; however using stdlib is clearer. - // Using stdlib: - return base64.StdEncoding.EncodeToString(b) -} - -type notAcceptable struct { - resource schema.GroupResource - message string -} - -func (e notAcceptable) Error() string { return e.message } -func (e notAcceptable) Status() metav1.Status { - return metav1.Status{ - Status: metav1.StatusFailure, - Code: http.StatusNotAcceptable, - Reason: metav1.StatusReason("NotAcceptable"), - Message: e.message, - } -}