From 30e5b71e3f95aa51c65803e471bd52268450ba78 Mon Sep 17 00:00:00 2001 From: Nikita <166552198+nbykov0@users.noreply.github.com> Date: Tue, 2 Dec 2025 12:55:29 +0300 Subject: [PATCH 01/45] [system:monitoring-agents] rename coredns metrics service (#1676) ## What this PR does Renames coredns metrics service ### Release note ```release-note Renamed coredns metrics service not to interfere with coredns service used for name resolution in tenant k8s clusters. ``` --- .../system/monitoring-agents/templates/coredns-scrape.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/system/monitoring-agents/templates/coredns-scrape.yaml b/packages/system/monitoring-agents/templates/coredns-scrape.yaml index 9c7253eb..223d553f 100644 --- a/packages/system/monitoring-agents/templates/coredns-scrape.yaml +++ b/packages/system/monitoring-agents/templates/coredns-scrape.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: - name: coredns + name: coredns-metrics namespace: kube-system labels: app: coredns @@ -19,7 +19,7 @@ spec: apiVersion: operator.victoriametrics.com/v1beta1 kind: VMServiceScrape metadata: - name: coredns + name: coredns-metrics namespace: cozy-monitoring spec: selector: From af56c105c2e2a0b6ba9c5e521db3f9f22954e116 Mon Sep 17 00:00:00 2001 From: Nikita <166552198+nbykov0@users.noreply.github.com> Date: Tue, 2 Dec 2025 14:48:34 +0300 Subject: [PATCH 02/45] [system:coredns] update coredns app labels to match Talos coredns labels (#1675) ## What this PR does Updates coredns app labels to match Talos coredns labels ### Release note ```release-note Coredns app labels updated to match Talos coredns labels. ``` --- packages/system/coredns/values.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/system/coredns/values.yaml b/packages/system/coredns/values.yaml index b52efda2..a1061a1e 100644 --- a/packages/system/coredns/values.yaml +++ b/packages/system/coredns/values.yaml @@ -3,3 +3,6 @@ coredns: repository: registry.k8s.io/coredns/coredns tag: v1.12.4 replicaCount: 2 + k8sAppLabelOverride: kube-dns + service: + name: kube-dns From 35907dd474c3460a87c21673909e0aacab1433e2 Mon Sep 17 00:00:00 2001 From: Nikita <166552198+nbykov0@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:10:26 +0300 Subject: [PATCH 03/45] [core:installer] Address buildx warnings (#1682) ## What this PR does Buildx is worried about Dockerfile syntax, this pr fixes it. ``` - FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 1) - FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 16) ``` ### Release note ```release-note Buildx warnings addressed. ``` --- packages/core/installer/images/cozystack/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/installer/images/cozystack/Dockerfile b/packages/core/installer/images/cozystack/Dockerfile index 7327698e..c964c027 100644 --- a/packages/core/installer/images/cozystack/Dockerfile +++ b/packages/core/installer/images/cozystack/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24-alpine as k8s-await-election-builder +FROM golang:1.24-alpine AS k8s-await-election-builder ARG K8S_AWAIT_ELECTION_GITREPO=https://github.com/LINBIT/k8s-await-election ARG K8S_AWAIT_ELECTION_VERSION=0.4.1 @@ -13,7 +13,7 @@ RUN git clone ${K8S_AWAIT_ELECTION_GITREPO} /usr/local/go/k8s-await-election/ \ && make \ && mv ./out/k8s-await-election-${TARGETARCH} /k8s-await-election -FROM golang:1.24-alpine as builder +FROM golang:1.24-alpine AS builder ARG TARGETOS ARG TARGETARCH From 2e22a6579eb0564647951409855a107d13ea0669 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:19:17 +0000 Subject: [PATCH 04/45] Prepare release v0.38.3 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- packages/apps/http-cache/images/nginx-cache.tag | 2 +- packages/apps/kubernetes/images/cluster-autoscaler.tag | 2 +- packages/apps/kubernetes/images/kubevirt-csi-driver.tag | 2 +- packages/core/installer/values.yaml | 2 +- packages/core/testing/values.yaml | 2 +- packages/extra/bootbox/images/matchbox.tag | 2 +- packages/extra/seaweedfs/images/objectstorage-sidecar.tag | 2 +- packages/system/bucket/images/s3manager.tag | 2 +- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 4 ++-- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/values.yaml | 2 +- packages/system/kubeovn/values.yaml | 2 +- packages/system/kubevirt-csi-node/values.yaml | 2 +- packages/system/lineage-controller-webhook/values.yaml | 2 +- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/apps/http-cache/images/nginx-cache.tag b/packages/apps/http-cache/images/nginx-cache.tag index 185dcc66..a5e4ca7b 100644 --- a/packages/apps/http-cache/images/nginx-cache.tag +++ b/packages/apps/http-cache/images/nginx-cache.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:e0a07082bb6fc6aeaae2315f335386f1705a646c72f9e0af512aebbca5cb2b15 +ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:b7633717cd7449c0042ae92d8ca9b36e4d69566561f5c7d44e21058e7d05c6d5 diff --git a/packages/apps/kubernetes/images/cluster-autoscaler.tag b/packages/apps/kubernetes/images/cluster-autoscaler.tag index d5ab33d1..56fcb024 100644 --- a/packages/apps/kubernetes/images/cluster-autoscaler.tag +++ b/packages/apps/kubernetes/images/cluster-autoscaler.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:2d39989846c3579dd020b9f6c77e6e314cc81aa344eaac0f6d633e723c17196d +ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:e1c0eb54d0ddadc8c7d72cee4f4282d56c957bf4ff0f62ef37db6b38dd0e8a01 diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index 80d3d2e6..3ac81184 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:d5c836ba33cf5dbed7e6f866784f668f80ffe69179e7c75847b680111984eefb +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:16d58e3dde89301000322dbd48f41f6269e614ead0a4833d9285f29d3b81d9c9 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index dbb7b846..69e4a7eb 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,2 +1,2 @@ cozystack: - image: ghcr.io/cozystack/cozystack/installer:v0.38.2@sha256:9ff92b655de6f9bea3cba4cd42dcffabd9aace6966dcfb1cc02dda2420ea4a15 + image: ghcr.io/cozystack/cozystack/installer:v0.38.3@sha256:4a6f0342ab48c206946f0d283072bf03f92a768bcbca525c66fc70cfa9fe1878 diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index de6bb330..9a05b875 100755 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.38.2@sha256:84be9e42bc2c04b0765c8b89e0a9728c49ebf4676a92522b007af96ae9aec68d + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.38.3@sha256:1edbc507b15dcdb26edd01cc00f2381f4c194c2653076ce95d056d7b41e1f363 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 2e63d30b..e7260e79 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v0.38.2@sha256:9cd7f46fcae119a3f8e35b428b018d0cb6da7b0cdd2ce764cc9fbf6dcd903f27 +ghcr.io/cozystack/cozystack/matchbox:v0.38.3@sha256:055da78b34e3e7f40716a5d73a7ffe2aa2e52b4c786f1a94a3365b42838f5f85 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index ec59113f..f426312c 100644 --- a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag +++ b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.2@sha256:ff3281fe53a97d2cd5cd94bd4c4d8ff08189508729869bb39b3f60c80da5f919 +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.3@sha256:60c979f9a81d560173f175bb04082b19a7e948fb3250e9d4acb560570fda68c9 diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index 1e12e68b..0bcbba4a 100644 --- a/packages/system/bucket/images/s3manager.tag +++ b/packages/system/bucket/images/s3manager.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:3825c9b4b6238f88f1b0de73bd18866a7e5f83f178d28fe2830f3bf24efb187d +ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:db7a293a3e37cb26a7428c387cbbaa7dd9b2adebd7f5d4e9c8fa945711313f47 diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 8bf0a803..310cdb06 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,5 +1,5 @@ cozystackAPI: - image: ghcr.io/cozystack/cozystack/cozystack-api:v0.38.2@sha256:d17f1c59658731e5a2063c3db348adbc03b5cd31720052016b68449164cf2f14 + image: ghcr.io/cozystack/cozystack/cozystack-api:v0.38.3@sha256:123d8afa32bf2935fcffafdfa52366f799bbef2f8ecae5251925c212bdbe3236 localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 1b365a43..2d69bde3 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,6 +1,6 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.38.2@sha256:468b2eccbc0aa00bd3d72d56624a46e6ba178fa279cdd19248af74d32ea7d319 + image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.38.3@sha256:ea9627e173e2d981cdf2d2106cb5bb153c0ae2be63ff8825cbe6f9b6e966825c debug: false disableTelemetry: false - cozystackVersion: "v0.38.2" + cozystackVersion: "v0.38.3" cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index bbf71610..e29ae562 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} -{{- $tenantText := "v0.38.2" }} +{{- $tenantText := "v0.38.3" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index fed8149b..e3c37a38 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.38.2@sha256:5aafb6c864c5523418d021a9fe5b514990d36972b6f1de9c34a1cd41f9d8bf7e + image: ghcr.io/cozystack/cozystack/openapi-ui:v0.38.3@sha256:eabe3d9620450f48bdc7e4dc2797d4d0d1affa652f3c750aba0fab504adb3617 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.38.2@sha256:7ffd8ae7b9da73fec7ae61a71c9c821a718d89a1b1df0197e09fda57678e1220 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.38.3@sha256:7646364cda96f3b6da421f28293db0256ae5dbce0a8912bf076eef3349968af2 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v0.38.2@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b + image: ghcr.io/cozystack/cozystack/token-proxy:v0.38.3@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index b5b77213..5360e3ae 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v0.38.2@sha256:13741b8f6dfede3ea0fd16d8bbebae810bc19254a81d7e5a139535efa17eabff + tag: v0.38.3@sha256:fa4ea889f29f50b48790c942d2f9b67d19c8643091047c0e6a33675fb3cd4127 repository: ghcr.io/cozystack/cozystack/kamaji resources: limits: @@ -13,4 +13,4 @@ kamaji: cpu: 100m memory: 100Mi extraArgs: - - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.38.2@sha256:13741b8f6dfede3ea0fd16d8bbebae810bc19254a81d7e5a139535efa17eabff + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.38.3@sha256:fa4ea889f29f50b48790c942d2f9b67d19c8643091047c0e6a33675fb3cd4127 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index 2e6fc195..43bfe135 100644 --- a/packages/system/kubeovn-plunger/values.yaml +++ b/packages/system/kubeovn-plunger/values.yaml @@ -1,4 +1,4 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.38.2@sha256:76c8af24cbec0261718c13c0150aa81c238a956626d4fd7baa8970b47fb3a6f0 +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.38.3@sha256:1c87e85c4c0aefbc1476637427c109b60cb43041d61e3ff572e17ef439c25e8b ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 9304c59d..360748e9 100644 --- a/packages/system/kubeovn-webhook/values.yaml +++ b/packages/system/kubeovn-webhook/values.yaml @@ -1,3 +1,3 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.38.2@sha256:8e67b2971f8c079a8b0636be1d091a9545d6cb653d745ff222a5966f56f903bd +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.38.3@sha256:6333b0bce5767f9c0e60534b44eed439495c8058059e1bea0106c5c0a69f6be4 diff --git a/packages/system/kubeovn/values.yaml b/packages/system/kubeovn/values.yaml index 72d62674..ebd16380 100644 --- a/packages/system/kubeovn/values.yaml +++ b/packages/system/kubeovn/values.yaml @@ -65,4 +65,4 @@ global: images: kubeovn: repository: kubeovn - tag: v1.14.11@sha256:8e6cf216687b4a80c35fa7c60bb4d511dd6aaaaf19d1ec53321dfef98d343f51 + tag: v1.14.11@sha256:6fd9d5fe756ccc25528751ecd0800994fdbe9212f60ac0e592510072ec8ddb55 diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index 8d2a7d45..e6c3dbb5 100644 --- a/packages/system/kubevirt-csi-node/values.yaml +++ b/packages/system/kubevirt-csi-node/values.yaml @@ -1,3 +1,3 @@ storageClass: replicated csiDriver: - image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:d5c836ba33cf5dbed7e6f866784f668f80ffe69179e7c75847b680111984eefb + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:16d58e3dde89301000322dbd48f41f6269e614ead0a4833d9285f29d3b81d9c9 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index ab5ccbbf..2fdd5c5d 100644 --- a/packages/system/lineage-controller-webhook/values.yaml +++ b/packages/system/lineage-controller-webhook/values.yaml @@ -1,5 +1,5 @@ lineageControllerWebhook: - image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.38.2@sha256:a5c750a0f46e8e25329b3ee2110d5dfb077c73e473195f1ed768d28d6f43902c + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.38.3@sha256:20261c17e77d34daa08569e1f34613bc27ed5b147415a6a7292505970a5b30b3 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index 7a5b5c22..54547a4c 100644 --- a/packages/system/objectstorage-controller/values.yaml +++ b/packages/system/objectstorage-controller/values.yaml @@ -1,3 +1,3 @@ objectstorage: controller: - image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.38.2@sha256:7d37495cce46d30d4613ecfacaa7b7f140e7ea8f3dbcc3e8c976e271de6cc71b" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.38.3@sha256:ee601d96a14e60dfbf3bf6c700688b797ba28c83db779068a1e0535b12b7daf8" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index afe8283d..de7a0f92 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -124,7 +124,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.2@sha256:ff3281fe53a97d2cd5cd94bd4c4d8ff08189508729869bb39b3f60c80da5f919" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.3@sha256:60c979f9a81d560173f175bb04082b19a7e948fb3250e9d4acb560570fda68c9" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From f1a75ab86407023eee19648e9cd461708727355a Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sat, 6 Dec 2025 19:56:46 +0100 Subject: [PATCH 05/45] [linstor] Update piraeus-operator v2.10.2 Signed-off-by: Andrei Kvapil (cherry picked from commit 58dd1f5881d563af67cf4791f837f0c8467e7db1) --- .../charts/piraeus/Chart.yaml | 4 +-- .../charts/piraeus/templates/config.yaml | 8 ++--- .../charts/piraeus/templates/crds.yaml | 35 +++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/system/piraeus-operator/charts/piraeus/Chart.yaml b/packages/system/piraeus-operator/charts/piraeus/Chart.yaml index 38bd15db..777722f3 100644 --- a/packages/system/piraeus-operator/charts/piraeus/Chart.yaml +++ b/packages/system/piraeus-operator/charts/piraeus/Chart.yaml @@ -3,8 +3,8 @@ name: piraeus description: | The Piraeus Operator manages software defined storage clusters using LINSTOR in Kubernetes. type: application -version: 2.10.1 -appVersion: "v2.10.1" +version: 2.10.2 +appVersion: "v2.10.2" maintainers: - name: Piraeus Datastore url: https://piraeus.io diff --git a/packages/system/piraeus-operator/charts/piraeus/templates/config.yaml b/packages/system/piraeus-operator/charts/piraeus/templates/config.yaml index a57dffd3..2e6963f0 100644 --- a/packages/system/piraeus-operator/charts/piraeus/templates/config.yaml +++ b/packages/system/piraeus-operator/charts/piraeus/templates/config.yaml @@ -23,10 +23,10 @@ data: tag: v1.32.3 image: piraeus-server linstor-csi: - tag: v1.10.2 + tag: v1.10.3 image: piraeus-csi nfs-server: - tag: v1.10.2 + tag: v1.10.3 image: piraeus-csi-nfs-server drbd-reactor: tag: v1.10.0 @@ -44,7 +44,7 @@ data: tag: v1.3.0 image: linstor-affinity-controller drbd-module-loader: - tag: v9.2.15 + tag: v9.2.16 # The special "match" attribute is used to select an image based on the node's reported OS. # The operator will first check the k8s node's ".status.nodeInfo.osImage" field, and compare it against the list # here. If one matches, that specific image name will be used instead of the fallback image. @@ -99,7 +99,7 @@ data: tag: v2.17.0 image: livenessprobe csi-provisioner: - tag: v6.0.0 + tag: v6.1.0 image: csi-provisioner csi-snapshotter: tag: v8.4.0 diff --git a/packages/system/piraeus-operator/charts/piraeus/templates/crds.yaml b/packages/system/piraeus-operator/charts/piraeus/templates/crds.yaml index 1821cb61..5bd9212c 100644 --- a/packages/system/piraeus-operator/charts/piraeus/templates/crds.yaml +++ b/packages/system/piraeus-operator/charts/piraeus/templates/crds.yaml @@ -993,6 +993,24 @@ spec: - Retain - Delete type: string + evacuationStrategy: + description: EvacuationStrategy configures the evacuation of volumes + from a Satellite when DeletionPolicy "Evacuate" is used. + nullable: true + properties: + attachedVolumeReattachTimeout: + default: 5m + description: |- + AttachedVolumeReattachTimeout configures how long evacuation waits for attached volumes to reattach on + different nodes. Setting this to 0 disable this evacuation step. + type: string + unattachedVolumeAttachTimeout: + default: 5m + description: |- + UnattachedVolumeAttachTimeout configures how long evacuation waits for unattached volumes to attach on + different nodes. Setting this to 0 disable this evacuation step. + type: string + type: object internalTLS: description: |- InternalTLS configures secure communication for the LINSTOR Satellite. @@ -1683,6 +1701,23 @@ spec: - Retain - Delete type: string + evacuationStrategy: + description: EvacuationStrategy configures the evacuation of volumes + from a Satellite when DeletionPolicy "Evacuate" is used. + properties: + attachedVolumeReattachTimeout: + default: 5m + description: |- + AttachedVolumeReattachTimeout configures how long evacuation waits for attached volumes to reattach on + different nodes. Setting this to 0 disable this evacuation step. + type: string + unattachedVolumeAttachTimeout: + default: 5m + description: |- + UnattachedVolumeAttachTimeout configures how long evacuation waits for unattached volumes to attach on + different nodes. Setting this to 0 disable this evacuation step. + type: string + type: object internalTLS: description: |- InternalTLS configures secure communication for the LINSTOR Satellite. From 891195018f3b895dee637b5c87f15b68c804795f Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 8 Dec 2025 23:27:24 +0100 Subject: [PATCH 06/45] [dashboard] Fix CustomFormsOverride schema to nest properties under spec.properties Signed-off-by: Andrei Kvapil (cherry picked from commit 578a810413c1521ff3f0e0b70c3da80ed31d851b) --- .../dashboard/customformsoverride.go | 20 +++- .../dashboard/customformsoverride_test.go | 94 +++++++++++-------- 2 files changed, 74 insertions(+), 40 deletions(-) diff --git a/internal/controller/dashboard/customformsoverride.go b/internal/controller/dashboard/customformsoverride.go index f88202bc..2b0daa08 100644 --- a/internal/controller/dashboard/customformsoverride.go +++ b/internal/controller/dashboard/customformsoverride.go @@ -105,8 +105,26 @@ func buildMultilineStringSchema(openAPISchema string) (map[string]any, error) { "properties": map[string]any{}, } + // Check if there's a spec property + specProp, ok := props["spec"].(map[string]any) + if !ok { + return map[string]any{}, nil + } + + specProps, ok := specProp["properties"].(map[string]any) + if !ok { + return map[string]any{}, nil + } + + // Create spec.properties structure in schema + schemaProps := schema["properties"].(map[string]any) + specSchema := map[string]any{ + "properties": map[string]any{}, + } + schemaProps["spec"] = specSchema + // Process spec properties recursively - processSpecProperties(props, schema["properties"].(map[string]any)) + processSpecProperties(specProps, specSchema["properties"].(map[string]any)) return schema, nil } diff --git a/internal/controller/dashboard/customformsoverride_test.go b/internal/controller/dashboard/customformsoverride_test.go index 2766bf17..9f7babe9 100644 --- a/internal/controller/dashboard/customformsoverride_test.go +++ b/internal/controller/dashboard/customformsoverride_test.go @@ -9,41 +9,46 @@ 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": { + "spec": { "type": "object", "properties": { - "nestedString": { + "simpleString": { "type": "string", - "description": "Nested string should get multilineString" + "description": "A simple string field" }, - "nestedStringWithEnum": { + "stringWithEnum": { "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" + "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" + } + } } } } @@ -70,33 +75,44 @@ func TestBuildMultilineStringSchema(t *testing.T) { t.Fatal("schema.properties is not a map") } - // Check simpleString - simpleString, ok := props["simpleString"].(map[string]any) + // Check spec property exists + spec, ok := props["spec"].(map[string]any) if !ok { - t.Fatal("simpleString not found in properties") + t.Fatal("spec not found in properties") + } + + specProps, ok := spec["properties"].(map[string]any) + if !ok { + t.Fatal("spec.properties is not a map") + } + + // Check simpleString + simpleString, ok := specProps["simpleString"].(map[string]any) + if !ok { + t.Fatal("simpleString not found in spec.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, ok := specProps["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, ok := specProps["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) + nestedObject, ok := specProps["nestedObject"].(map[string]any) if !ok { - t.Fatal("nestedObject not found in properties") + t.Fatal("nestedObject not found in spec.properties") } nestedProps, ok := nestedObject["properties"].(map[string]any) if !ok { @@ -113,9 +129,9 @@ func TestBuildMultilineStringSchema(t *testing.T) { } // Check array of objects - arrayOfObjects, ok := props["arrayOfObjects"].(map[string]any) + arrayOfObjects, ok := specProps["arrayOfObjects"].(map[string]any) if !ok { - t.Fatal("arrayOfObjects not found in properties") + t.Fatal("arrayOfObjects not found in spec.properties") } items, ok := arrayOfObjects["items"].(map[string]any) if !ok { From 548b2c0ed3dc9decb19ecab2682b53bc76157fd5 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 5 Dec 2025 17:18:01 +0100 Subject: [PATCH 07/45] [virtual-machine] Improve check for resizing job Signed-off-by: Andrei Kvapil (cherry picked from commit 0bab8950266b30476824feb1ef72b91007f5e8ff) --- .../virtual-machine/templates/vm-update-hook.yaml | 6 +++++- .../apps/vm-disk/templates/pvc-resize-hook.yaml | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/apps/virtual-machine/templates/vm-update-hook.yaml b/packages/apps/virtual-machine/templates/vm-update-hook.yaml index 74f76cc5..1197c027 100644 --- a/packages/apps/virtual-machine/templates/vm-update-hook.yaml +++ b/packages/apps/virtual-machine/templates/vm-update-hook.yaml @@ -27,7 +27,11 @@ {{- if and $existingPVC $desiredStorage -}} {{- $currentStorage := $existingPVC.spec.resources.requests.storage | toString -}} {{- if not (eq $currentStorage $desiredStorage) -}} - {{- $needResizePVC = true -}} + {{- $oldSize := (include "cozy-lib.resources.toFloat" $currentStorage) | float64 -}} + {{- $newSize := (include "cozy-lib.resources.toFloat" $desiredStorage) | float64 -}} + {{- if gt $newSize $oldSize -}} + {{- $needResizePVC = true -}} + {{- end -}} {{- end -}} {{- end -}} diff --git a/packages/apps/vm-disk/templates/pvc-resize-hook.yaml b/packages/apps/vm-disk/templates/pvc-resize-hook.yaml index 2619ed15..2454c599 100644 --- a/packages/apps/vm-disk/templates/pvc-resize-hook.yaml +++ b/packages/apps/vm-disk/templates/pvc-resize-hook.yaml @@ -1,5 +1,17 @@ {{- $existingPVC := lookup "v1" "PersistentVolumeClaim" .Release.Namespace .Release.Name }} -{{- if and $existingPVC (ne ($existingPVC.spec.resources.requests.storage | toString) .Values.storage) -}} +{{- $shouldResize := false -}} +{{- if and $existingPVC .Values.storage -}} + {{- $currentStorage := $existingPVC.spec.resources.requests.storage | toString -}} + {{- if ne $currentStorage .Values.storage -}} + {{- $oldSize := (include "cozy-lib.resources.toFloat" $currentStorage) | float64 -}} + {{- $newSize := (include "cozy-lib.resources.toFloat" .Values.storage) | float64 -}} + {{- if gt $newSize $oldSize -}} + {{- $shouldResize = true -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- if $shouldResize -}} apiVersion: batch/v1 kind: Job metadata: @@ -23,6 +35,7 @@ spec: command: ["sh", "-xec"] args: - | + echo "Resizing PVC to {{ .Values.storage }}..." kubectl patch pvc {{ .Release.Name }} -p '{"spec":{"resources":{"requests":{"storage":"{{ .Values.storage }}"}}}}' --- apiVersion: v1 From 03328dc4e4389e84e4c8a1db4c576b57c040da1d Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:23:14 +0000 Subject: [PATCH 08/45] Prepare release v0.38.4 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- packages/apps/http-cache/images/nginx-cache.tag | 2 +- packages/apps/kubernetes/images/kubevirt-csi-driver.tag | 2 +- packages/core/installer/values.yaml | 2 +- packages/core/testing/values.yaml | 2 +- packages/extra/bootbox/images/matchbox.tag | 2 +- packages/extra/seaweedfs/images/objectstorage-sidecar.tag | 2 +- packages/system/bucket/images/s3manager.tag | 2 +- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 4 ++-- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/values.yaml | 2 +- packages/system/kubeovn/values.yaml | 2 +- packages/system/kubevirt-csi-node/values.yaml | 2 +- packages/system/lineage-controller-webhook/values.yaml | 2 +- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 19 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/apps/http-cache/images/nginx-cache.tag b/packages/apps/http-cache/images/nginx-cache.tag index a5e4ca7b..0970e11c 100644 --- a/packages/apps/http-cache/images/nginx-cache.tag +++ b/packages/apps/http-cache/images/nginx-cache.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:b7633717cd7449c0042ae92d8ca9b36e4d69566561f5c7d44e21058e7d05c6d5 +ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index 3ac81184..61e9f2ba 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:16d58e3dde89301000322dbd48f41f6269e614ead0a4833d9285f29d3b81d9c9 +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:47396dac04a24f824f82892c90efa160539e1e0f8ac697f5dc8ecf397e1ac413 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 69e4a7eb..e1a56418 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,2 +1,2 @@ cozystack: - image: ghcr.io/cozystack/cozystack/installer:v0.38.3@sha256:4a6f0342ab48c206946f0d283072bf03f92a768bcbca525c66fc70cfa9fe1878 + image: ghcr.io/cozystack/cozystack/installer:v0.38.4@sha256:2530f78cd456e0b0619d8db607c4d47127bbdd36b99f82a4029758a106169f09 diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index 9a05b875..97c640db 100755 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.38.3@sha256:1edbc507b15dcdb26edd01cc00f2381f4c194c2653076ce95d056d7b41e1f363 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.38.4@sha256:8284585dfdc337d6720f44b82417b42dc9321661025a8c046e085004d4573597 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index e7260e79..48db300d 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v0.38.3@sha256:055da78b34e3e7f40716a5d73a7ffe2aa2e52b4c786f1a94a3365b42838f5f85 +ghcr.io/cozystack/cozystack/matchbox:v0.38.4@sha256:8398c5546dedfcd9bf68a6eef165e423b2f0bcb10a21f7e76b7ae4a6beb579e2 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index f426312c..c302bef2 100644 --- a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag +++ b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.3@sha256:60c979f9a81d560173f175bb04082b19a7e948fb3250e9d4acb560570fda68c9 +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.4@sha256:5136a36dddb14d2b95ceefbf8cc4b0cf6411b2ec9ae6898173b24c57790cc857 diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index 0bcbba4a..ec7741c2 100644 --- a/packages/system/bucket/images/s3manager.tag +++ b/packages/system/bucket/images/s3manager.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:db7a293a3e37cb26a7428c387cbbaa7dd9b2adebd7f5d4e9c8fa945711313f47 +ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:b58f5b192d1f47026c19ad51a857e31c256a9ff46c24a5fbbfcfe4f61981b8eb diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 310cdb06..920222b6 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,5 +1,5 @@ cozystackAPI: - image: ghcr.io/cozystack/cozystack/cozystack-api:v0.38.3@sha256:123d8afa32bf2935fcffafdfa52366f799bbef2f8ecae5251925c212bdbe3236 + image: ghcr.io/cozystack/cozystack/cozystack-api:v0.38.4@sha256:58dba7b8b9915ece3a5785b6178536377fce56dc55451f2736415cdeab9185ff localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 2d69bde3..d1446268 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,6 +1,6 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.38.3@sha256:ea9627e173e2d981cdf2d2106cb5bb153c0ae2be63ff8825cbe6f9b6e966825c + image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.38.4@sha256:a5cbe6b939ce10125b975e0395c9bd4a0c880aabe93dae832afc9334d33b7f63 debug: false disableTelemetry: false - cozystackVersion: "v0.38.3" + cozystackVersion: "v0.38.4" cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index e29ae562..6219aa0e 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} -{{- $tenantText := "v0.38.3" }} +{{- $tenantText := "v0.38.4" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index e3c37a38..52983589 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.38.3@sha256:eabe3d9620450f48bdc7e4dc2797d4d0d1affa652f3c750aba0fab504adb3617 + image: ghcr.io/cozystack/cozystack/openapi-ui:v0.38.4@sha256:aae9aea2aa9aea905f0dea051104bff75f05bf554ed450ae013225359ae4f316 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.38.3@sha256:7646364cda96f3b6da421f28293db0256ae5dbce0a8912bf076eef3349968af2 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.38.4@sha256:2845688891e0cf9513768aa468ff4de5b6b40e58cc763bacbfe2b417ebad1bdf tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v0.38.3@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b + image: ghcr.io/cozystack/cozystack/token-proxy:v0.38.4@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index 5360e3ae..40d85177 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v0.38.3@sha256:fa4ea889f29f50b48790c942d2f9b67d19c8643091047c0e6a33675fb3cd4127 + tag: v0.38.4@sha256:458b8ae75e845e08c3c3d20f8af0ea1d7f8933739609d19f266fbf280417b764 repository: ghcr.io/cozystack/cozystack/kamaji resources: limits: @@ -13,4 +13,4 @@ kamaji: cpu: 100m memory: 100Mi extraArgs: - - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.38.3@sha256:fa4ea889f29f50b48790c942d2f9b67d19c8643091047c0e6a33675fb3cd4127 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.38.4@sha256:458b8ae75e845e08c3c3d20f8af0ea1d7f8933739609d19f266fbf280417b764 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index 43bfe135..9dc9d594 100644 --- a/packages/system/kubeovn-plunger/values.yaml +++ b/packages/system/kubeovn-plunger/values.yaml @@ -1,4 +1,4 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.38.3@sha256:1c87e85c4c0aefbc1476637427c109b60cb43041d61e3ff572e17ef439c25e8b +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.38.4@sha256:e8bd1f93e38b7b7d124deddd0c2498f7df0fbe89b613d12a0fbf3e47220f9053 ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 360748e9..83bbfd1a 100644 --- a/packages/system/kubeovn-webhook/values.yaml +++ b/packages/system/kubeovn-webhook/values.yaml @@ -1,3 +1,3 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.38.3@sha256:6333b0bce5767f9c0e60534b44eed439495c8058059e1bea0106c5c0a69f6be4 +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.38.4@sha256:cb39773a626f01da04cb4e818faa9460a7d193e045f5c80f2c2e9116d18ef3c0 diff --git a/packages/system/kubeovn/values.yaml b/packages/system/kubeovn/values.yaml index ebd16380..7de48124 100644 --- a/packages/system/kubeovn/values.yaml +++ b/packages/system/kubeovn/values.yaml @@ -65,4 +65,4 @@ global: images: kubeovn: repository: kubeovn - tag: v1.14.11@sha256:6fd9d5fe756ccc25528751ecd0800994fdbe9212f60ac0e592510072ec8ddb55 + tag: v1.14.11@sha256:e277ee347814af85528851cf3d604a65ab9fb2e833b173e658490c35e00596b0 diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index e6c3dbb5..6ff9991a 100644 --- a/packages/system/kubevirt-csi-node/values.yaml +++ b/packages/system/kubevirt-csi-node/values.yaml @@ -1,3 +1,3 @@ storageClass: replicated csiDriver: - image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:16d58e3dde89301000322dbd48f41f6269e614ead0a4833d9285f29d3b81d9c9 + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:47396dac04a24f824f82892c90efa160539e1e0f8ac697f5dc8ecf397e1ac413 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 2fdd5c5d..80db7f86 100644 --- a/packages/system/lineage-controller-webhook/values.yaml +++ b/packages/system/lineage-controller-webhook/values.yaml @@ -1,5 +1,5 @@ lineageControllerWebhook: - image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.38.3@sha256:20261c17e77d34daa08569e1f34613bc27ed5b147415a6a7292505970a5b30b3 + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.38.4@sha256:557e6cee3dfce7ec9e69b404806c6b3c3766fa523cb3dc1cbd7155c24af2cc96 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index 54547a4c..b902622e 100644 --- a/packages/system/objectstorage-controller/values.yaml +++ b/packages/system/objectstorage-controller/values.yaml @@ -1,3 +1,3 @@ objectstorage: controller: - image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.38.3@sha256:ee601d96a14e60dfbf3bf6c700688b797ba28c83db779068a1e0535b12b7daf8" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.38.4@sha256:91ed0ba4de7958708f77427d2c752dece969236c13927c17033d792700e9c6f4" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index de7a0f92..52ab1551 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -124,7 +124,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.3@sha256:60c979f9a81d560173f175bb04082b19a7e948fb3250e9d4acb560570fda68c9" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.4@sha256:5136a36dddb14d2b95ceefbf8cc4b0cf6411b2ec9ae6898173b24c57790cc857" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From 1491535b354618008bd894f419174674bfdb84d1 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 1 Dec 2025 20:00:53 +0100 Subject: [PATCH 09/45] [apps] Refactor apiserver to use typed objects and fix UnstructuredList GVK This commit refactors the apiserver REST handlers to use typed objects (appsv1alpha1.Application) instead of unstructured.Unstructured, eliminating the need for runtime conversions and simplifying the codebase. Additionally, it fixes an issue where UnstructuredList objects were using the first registered kind from typeToGVK instead of the kind from the object's field when multiple kinds are registered with the same Go type. This is a more comprehensive fix for the problem addressed in https://github.com/cozystack/cozystack/pull/1630, which was reverted in https://github.com/cozystack/cozystack/pull/1677. The fix includes the upstream fix from kubernetes/kubernetes#135537, which enables short-circuit path for UnstructuredList similar to regular Unstructured objects, using GVK from the object field instead of typeToGVK. Changes: - Refactored rest.go handlers to use typed Application objects - Removed unstructured.Unstructured conversions - Fixed UnstructuredList GVK handling - Updated dependencies in go.mod/go.sum - Added e2e test for OpenAPI validation - Updated Dockerfile Signed-off-by: Andrei Kvapil (cherry picked from commit ca29fc855ab333781211c92fa648ad2efb6a53e1) --- go.mod | 11 +- go.sum | 4 +- hack/e2e-test-openapi.bats | 19 +++ pkg/registry/apps/application/rest.go | 178 +++++-------------------- pkg/registry/core/tenantmodule/rest.go | 101 +++++--------- 5 files changed, 95 insertions(+), 218 deletions(-) diff --git a/go.mod b/go.mod index 041aa96a..a843d2f3 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,15 @@ go 1.23.0 require ( github.com/fluxcd/helm-controller/api v1.1.0 + github.com/go-logr/logr v1.4.2 + github.com/go-logr/zapr v1.3.0 github.com/google/gofuzz v1.2.0 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 + github.com/prometheus/client_golang v1.19.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 + go.uber.org/zap v1.27.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.31.2 k8s.io/apiextensions-apiserver v0.31.2 @@ -44,9 +48,7 @@ require ( github.com/fluxcd/pkg/apis/meta v1.6.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect @@ -74,7 +76,6 @@ require ( github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -94,7 +95,6 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.33.0 // indirect @@ -119,3 +119,6 @@ require ( sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) + +// See: issues.k8s.io/135537 +replace k8s.io/apimachinery => github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614 diff --git a/go.sum b/go.sum index a1090f31..a32e6b3b 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614 h1:jH9elECUvhiIs3IMv3oS5k1JgCLVsSK6oU4dmq5gyW8= +github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -291,8 +293,6 @@ k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= -k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= -k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= diff --git a/hack/e2e-test-openapi.bats b/hack/e2e-test-openapi.bats index d94cb422..88ed734b 100644 --- a/hack/e2e-test-openapi.bats +++ b/hack/e2e-test-openapi.bats @@ -21,14 +21,33 @@ } @test "Test kinds" { + val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.kind') + if [ "$val" != "TenantList" ]; then + echo "Expected kind to be TenantList, got $val" + exit 1 + fi val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.items[0].kind') if [ "$val" != "Tenant" ]; then echo "Expected kind to be Tenant, got $val" exit 1 fi + val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.kind') + if [ "$val" != "IngressList" ]; then + echo "Expected kind to be IngressList, got $val" + exit 1 + fi val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.items[0].kind') if [ "$val" != "Ingress" ]; then echo "Expected kind to be Ingress, got $val" exit 1 fi } + +@test "Create and delete namespace" { + kubectl create ns cozy-test-create-and-delete-namespace --dry-run=client -o yaml | kubectl apply -f - + if ! kubectl delete ns cozy-test-create-and-delete-namespace; then + echo "Failed to delete namespace" + kubectl describe ns cozy-test-create-and-delete-namespace + exit 1 + fi +} diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index 03de7c08..704cd709 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -28,7 +28,6 @@ import ( helmv2 "github.com/fluxcd/helm-controller/api/v2" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" fields "k8s.io/apimachinery/pkg/fields" labels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -142,17 +141,9 @@ func (r *REST) GetSingularName() string { // Create handles the creation of a new Application by converting it to a HelmRelease func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { // Assert the object is of type Application - us, ok := obj.(*unstructured.Unstructured) + app, ok := obj.(*appsv1alpha1.Application) if !ok { - return nil, fmt.Errorf("expected unstructured.Unstructured object, got %T", obj) - } - - app := &appsv1alpha1.Application{} - - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(us.Object, app); err != nil { - errMsg := fmt.Sprintf("returned unstructured.Unstructured object was not an Application") - klog.Errorf(errMsg) - return nil, fmt.Errorf(errMsg) + return nil, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", obj) } // Convert Application to HelmRelease @@ -186,15 +177,8 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation klog.V(6).Infof("Successfully created and converted HelmRelease %s to Application", helmRelease.GetName()) - // Convert Application to unstructured format - unstructuredApp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&convertedApp) - if err != nil { - klog.Errorf("Failed to convert Application to unstructured for resource %s: %v", convertedApp.GetName(), err) - return nil, fmt.Errorf("failed to convert Application to unstructured: %v", err) - } - - klog.V(6).Infof("Successfully retrieved and converted resource %s of type %s to unstructured", convertedApp.GetName(), r.gvr.Resource) - return &unstructured.Unstructured{Object: unstructuredApp}, nil + klog.V(6).Infof("Successfully retrieved and converted resource %s of type %s", convertedApp.GetName(), r.gvr.Resource) + return &convertedApp, nil } // Get retrieves an Application by converting the corresponding HelmRelease @@ -238,25 +222,8 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) return nil, fmt.Errorf("conversion error: %v", err) } - // Explicitly set apiVersion and kind for Application - convertedApp.TypeMeta = metav1.TypeMeta{ - APIVersion: "apps.cozystack.io/v1alpha1", - Kind: r.kindName, - } - - // Convert Application to unstructured format - unstructuredApp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&convertedApp) - if err != nil { - klog.Errorf("Failed to convert Application to unstructured for resource %s: %v", name, err) - return nil, fmt.Errorf("failed to convert Application to unstructured: %v", err) - } - - // Explicitly set apiVersion and kind in unstructured object - unstructuredApp["apiVersion"] = "apps.cozystack.io/v1alpha1" - unstructuredApp["kind"] = r.kindName - - klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s to unstructured", name, r.gvr.Resource) - return &unstructured.Unstructured{Object: unstructuredApp}, nil + klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s", name, r.gvr.Resource) + return &convertedApp, nil } // List retrieves a list of Applications by converting HelmReleases @@ -339,8 +306,8 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption return nil, err } - // Initialize unstructured items array - items := make([]unstructured.Unstructured, 0) + // Initialize Application items array + items := make([]appsv1alpha1.Application, 0, len(hrList.Items)) // Iterate over HelmReleases and convert to Applications for i := range hrList.Items { @@ -387,17 +354,11 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption } } - // Convert Application to unstructured - unstructuredApp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&app) - if err != nil { - klog.Errorf("Error converting Application %s to unstructured: %v", app.Name, err) - continue - } - items = append(items, unstructured.Unstructured{Object: unstructuredApp}) + items = append(items, app) } - // Explicitly set apiVersion and kind in unstructured object - appList := r.NewList().(*unstructured.UnstructuredList) + // Create ApplicationList with proper kind + appList := r.NewList().(*appsv1alpha1.ApplicationList) appList.SetResourceVersion(hrList.GetResourceVersion()) appList.Items = items @@ -447,16 +408,9 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje } // Assert the new object is of type Application - us, ok := newObj.(*unstructured.Unstructured) + app, ok := newObj.(*appsv1alpha1.Application) if !ok { - errMsg := fmt.Sprintf("expected unstructured.Unstructured object, got %T", newObj) - klog.Errorf(errMsg) - return nil, false, fmt.Errorf(errMsg) - } - app := &appsv1alpha1.Application{} - - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(us.Object, app); err != nil { - errMsg := fmt.Sprintf("returned unstructured.Unstructured object was not an Application") + errMsg := fmt.Sprintf("expected *appsv1alpha1.Application object, got %T", newObj) klog.Errorf(errMsg) return nil, false, fmt.Errorf(errMsg) } @@ -517,24 +471,9 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje klog.V(6).Infof("Successfully updated and converted HelmRelease %s to Application", helmRelease.GetName()) - // Explicitly set apiVersion and kind for Application - convertedApp.TypeMeta = metav1.TypeMeta{ - APIVersion: "apps.cozystack.io/v1alpha1", - Kind: r.kindName, - } + klog.V(6).Infof("Returning updated Application object: %+v", convertedApp) - // Convert Application to unstructured format - unstructuredApp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&convertedApp) - if err != nil { - klog.Errorf("Failed to convert Application to unstructured for resource %s: %v", convertedApp.GetName(), err) - return nil, false, fmt.Errorf("failed to convert Application to unstructured: %v", err) - } - obj := &unstructured.Unstructured{Object: unstructuredApp} - obj.SetGroupVersionKind(r.gvk) - - klog.V(6).Infof("Returning patched Application object: %+v", unstructuredApp) - - return obj, false, nil + return &convertedApp, false, nil } // Delete removes an Application by deleting the corresponding HelmRelease @@ -728,19 +667,10 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio } } - // Convert Application to unstructured - unstructuredApp, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&app) - if err != nil { - klog.Errorf("Failed to convert Application to unstructured: %v", err) - continue - } - obj := &unstructured.Unstructured{Object: unstructuredApp} - obj.SetGroupVersionKind(r.gvk) - // Create watch event with Application object appEvent := watch.Event{ Type: event.Type, - Object: obj, + Object: &app, } // Send event to custom watcher @@ -766,8 +696,8 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio // Helper function to get HelmRelease name from object func helmReleaseName(obj runtime.Object) string { - if u, ok := obj.(*unstructured.Unstructured); ok { - return u.GetName() + if app, ok := obj.(*appsv1alpha1.Application); ok { + return app.GetName() } return "" } @@ -1059,56 +989,6 @@ func (r *REST) ConvertToTable(ctx context.Context, object runtime.Object, tableO case *appsv1alpha1.Application: table = r.buildTableFromApplication(*obj) table.ListMeta.ResourceVersion = obj.GetResourceVersion() - case *unstructured.UnstructuredList: - apps := make([]appsv1alpha1.Application, 0, len(obj.Items)) - for _, u := range obj.Items { - var a appsv1alpha1.Application - err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &a) - if err != nil { - klog.Errorf("Failed to convert Unstructured to Application: %v", err) - continue - } - apps = append(apps, a) - } - table = r.buildTableFromApplications(apps) - table.ListMeta.ResourceVersion = obj.GetResourceVersion() - case *unstructured.Unstructured: - var apps []appsv1alpha1.Application - for { - var items interface{} - var ok bool - var objects []unstructured.Unstructured - if items, ok = obj.Object["items"]; !ok { - break - } - if objects, ok = items.([]unstructured.Unstructured); !ok { - break - } - apps = make([]appsv1alpha1.Application, 0, len(objects)) - var a appsv1alpha1.Application - for i := range objects { - err := runtime.DefaultUnstructuredConverter.FromUnstructured(objects[i].Object, &a) - if err != nil { - klog.Errorf("Failed to convert Unstructured to Application: %v", err) - continue - } - apps = append(apps, a) - } - break - } - if apps != nil { - table = r.buildTableFromApplications(apps) - table.ListMeta.ResourceVersion = obj.GetResourceVersion() - break - } - var app appsv1alpha1.Application - err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &app) - if err != nil { - klog.Errorf("Failed to convert Unstructured to Application: %v", err) - return nil, fmt.Errorf("failed to convert Unstructured to Application: %v", err) - } - table = r.buildTableFromApplication(app) - table.ListMeta.ResourceVersion = obj.GetResourceVersion() default: resource := schema.GroupResource{} if info, ok := request.RequestInfoFrom(ctx); ok { @@ -1147,10 +1027,11 @@ func (r *REST) buildTableFromApplications(apps []appsv1alpha1.Application) metav } now := time.Now() - for _, app := range apps { + for i := range apps { + app := &apps[i] row := metav1.TableRow{ Cells: []interface{}{app.GetName(), getReadyStatus(app.Status.Conditions), computeAge(app.GetCreationTimestamp().Time, now), getVersion(app.Status.Version)}, - Object: runtime.RawExtension{Object: &app}, + Object: runtime.RawExtension{Object: app}, } table.Rows = append(table.Rows, row) } @@ -1171,9 +1052,10 @@ func (r *REST) buildTableFromApplication(app appsv1alpha1.Application) metav1.Ta } now := time.Now() + a := app row := metav1.TableRow{ Cells: []interface{}{app.GetName(), getReadyStatus(app.Status.Conditions), computeAge(app.GetCreationTimestamp().Time, now), getVersion(app.Status.Version)}, - Object: runtime.RawExtension{Object: &app}, + Object: runtime.RawExtension{Object: &a}, } table.Rows = append(table.Rows, row) @@ -1237,15 +1119,21 @@ func (r *REST) Destroy() { // New creates a new instance of Application func (r *REST) New() runtime.Object { - obj := &unstructured.Unstructured{} - obj.SetGroupVersionKind(r.gvk) + obj := &appsv1alpha1.Application{} + obj.TypeMeta = metav1.TypeMeta{ + APIVersion: r.gvk.GroupVersion().String(), + Kind: r.kindName, + } return obj } // NewList returns an empty list of Application objects func (r *REST) NewList() runtime.Object { - obj := &unstructured.UnstructuredList{} - obj.SetGroupVersionKind(r.gvk.GroupVersion().WithKind(r.kindName + "List")) + obj := &appsv1alpha1.ApplicationList{} + obj.TypeMeta = metav1.TypeMeta{ + APIVersion: r.gvk.GroupVersion().String(), + Kind: r.kindName + "List", + } return obj } diff --git a/pkg/registry/core/tenantmodule/rest.go b/pkg/registry/core/tenantmodule/rest.go index 852a0b64..8ca27dde 100644 --- a/pkg/registry/core/tenantmodule/rest.go +++ b/pkg/registry/core/tenantmodule/rest.go @@ -26,7 +26,6 @@ import ( helmv2 "github.com/fluxcd/helm-controller/api/v2" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" fields "k8s.io/apimachinery/pkg/fields" labels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -147,25 +146,8 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) return nil, fmt.Errorf("conversion error: %v", err) } - // Explicitly set apiVersion and kind for TenantModule - convertedModule.TypeMeta = metav1.TypeMeta{ - APIVersion: "core.cozystack.io/v1alpha1", - Kind: r.kindName, - } - - // Convert TenantModule to unstructured format - unstructuredModule, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&convertedModule) - if err != nil { - klog.Errorf("Failed to convert TenantModule to unstructured for resource %s: %v", name, err) - return nil, fmt.Errorf("failed to convert TenantModule to unstructured: %v", err) - } - - // Explicitly set apiVersion and kind in unstructured object - unstructuredModule["apiVersion"] = "core.cozystack.io/v1alpha1" - unstructuredModule["kind"] = r.kindName - - klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s to unstructured", name, r.gvr.Resource) - return &unstructured.Unstructured{Object: unstructuredModule}, nil + klog.V(6).Infof("Successfully retrieved and converted resource %s of kind %s", name, r.gvr.Resource) + return &convertedModule, nil } // List retrieves a list of TenantModules by converting HelmReleases @@ -245,8 +227,8 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption return nil, err } - // Initialize unstructured items array - items := make([]unstructured.Unstructured, 0) + // Initialize TenantModule items array + items := make([]corev1alpha1.TenantModule, 0, len(hrList.Items)) // Iterate over HelmReleases and convert to TenantModules for i := range hrList.Items { @@ -294,19 +276,15 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption } } - // Convert TenantModule to unstructured - unstructuredModule, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&module) - if err != nil { - klog.Errorf("Error converting TenantModule %s to unstructured: %v", module.Name, err) - continue - } - items = append(items, unstructured.Unstructured{Object: unstructuredModule}) + items = append(items, module) } - // Explicitly set apiVersion and kind in unstructured object - moduleList := &unstructured.UnstructuredList{} - moduleList.SetAPIVersion("core.cozystack.io/v1alpha1") - moduleList.SetKind(r.kindName + "List") + // Create TenantModuleList with proper kind + moduleList := &corev1alpha1.TenantModuleList{} + moduleList.TypeMeta = metav1.TypeMeta{ + APIVersion: "core.cozystack.io/v1alpha1", + Kind: r.kindName + "List", + } moduleList.SetResourceVersion(hrList.GetResourceVersion()) moduleList.Items = items @@ -455,17 +433,10 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio } } - // Convert TenantModule to unstructured - unstructuredModule, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&module) - if err != nil { - klog.Errorf("Failed to convert TenantModule to unstructured: %v", err) - continue - } - // Create watch event with TenantModule object moduleEvent := watch.Event{ Type: event.Type, - Object: &unstructured.Unstructured{Object: unstructuredModule}, + Object: &module, } // Send event to custom watcher @@ -620,27 +591,11 @@ func (r *REST) ConvertToTable(ctx context.Context, object runtime.Object, tableO var table metav1.Table switch obj := object.(type) { - case *unstructured.UnstructuredList: - modules := make([]corev1alpha1.TenantModule, 0, len(obj.Items)) - for _, u := range obj.Items { - var m corev1alpha1.TenantModule - err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &m) - if err != nil { - klog.Errorf("Failed to convert Unstructured to TenantModule: %v", err) - continue - } - modules = append(modules, m) - } - table = r.buildTableFromTenantModules(modules) - table.ListMeta.ResourceVersion = obj.GetResourceVersion() - case *unstructured.Unstructured: - var module corev1alpha1.TenantModule - err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), &module) - if err != nil { - klog.Errorf("Failed to convert Unstructured to TenantModule: %v", err) - return nil, fmt.Errorf("failed to convert Unstructured to TenantModule: %v", err) - } - table = r.buildTableFromTenantModule(module) + case *corev1alpha1.TenantModuleList: + table = r.buildTableFromTenantModules(obj.Items) + table.ListMeta.ResourceVersion = obj.ListMeta.ResourceVersion + case *corev1alpha1.TenantModule: + table = r.buildTableFromTenantModule(*obj) table.ListMeta.ResourceVersion = obj.GetResourceVersion() default: resource := schema.GroupResource{} @@ -680,10 +635,11 @@ func (r *REST) buildTableFromTenantModules(modules []corev1alpha1.TenantModule) } now := time.Now() - for _, module := range modules { + for i := range modules { + module := &modules[i] row := metav1.TableRow{ Cells: []interface{}{module.GetName(), getReadyStatus(module.Status.Conditions), computeAge(module.GetCreationTimestamp().Time, now), getVersion(module.Status.Version)}, - Object: runtime.RawExtension{Object: &module}, + Object: runtime.RawExtension{Object: module}, } table.Rows = append(table.Rows, row) } @@ -704,9 +660,10 @@ func (r *REST) buildTableFromTenantModule(module corev1alpha1.TenantModule) meta } now := time.Now() + m := module row := metav1.TableRow{ Cells: []interface{}{module.GetName(), getReadyStatus(module.Status.Conditions), computeAge(module.GetCreationTimestamp().Time, now), getVersion(module.Status.Version)}, - Object: runtime.RawExtension{Object: &module}, + Object: runtime.RawExtension{Object: &m}, } table.Rows = append(table.Rows, row) @@ -751,12 +708,22 @@ func (r *REST) Destroy() { // New creates a new instance of TenantModule func (r *REST) New() runtime.Object { - return &corev1alpha1.TenantModule{} + obj := &corev1alpha1.TenantModule{} + obj.TypeMeta = metav1.TypeMeta{ + APIVersion: r.gvk.GroupVersion().String(), + Kind: r.kindName, + } + return obj } // NewList returns an empty list of TenantModule objects func (r *REST) NewList() runtime.Object { - return &corev1alpha1.TenantModuleList{} + obj := &corev1alpha1.TenantModuleList{} + obj.TypeMeta = metav1.TypeMeta{ + APIVersion: r.gvk.GroupVersion().String(), + Kind: r.kindName + "List", + } + return obj } // Kind returns the resource kind used for API discovery From 79165ca2f9497733552db6a89296d96d14fec531 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 15 Dec 2025 08:40:59 +0100 Subject: [PATCH 10/45] Update SeaweedFS v4.02 Signed-off-by: Andrei Kvapil (cherry picked from commit 1805be3c488ab4e33dae9a95f9583c9d266cd19b) --- packages/system/seaweedfs/Makefile | 1 - .../seaweedfs/charts/seaweedfs/Chart.yaml | 4 +- .../all-in-one/all-in-one-deployment.yaml | 116 +++++++---- .../templates/all-in-one/all-in-one-pvc.yaml | 25 ++- .../all-in-one/all-in-one-service.yml | 18 +- .../seaweedfs/templates/cert/ca-cert.yaml | 8 +- .../templates/filer/filer-ingress.yaml | 13 +- .../templates/filer/filer-statefulset.yaml | 21 +- .../templates/master/master-statefulset.yaml | 12 +- .../seaweedfs/templates/s3/s3-deployment.yaml | 7 +- .../seaweedfs/templates/s3/s3-ingress.yaml | 16 +- .../shared/post-install-bucket-hook.yaml | 71 +++++-- .../templates/volume/volume-statefulset.yaml | 4 +- .../seaweedfs/charts/seaweedfs/values.yaml | 187 ++++++++++++------ .../seaweedfs/images/seaweedfs/Dockerfile | 2 +- .../seaweedfs/patches/long-term-ca.diff | 13 -- 16 files changed, 333 insertions(+), 185 deletions(-) delete mode 100644 packages/system/seaweedfs/patches/long-term-ca.diff diff --git a/packages/system/seaweedfs/Makefile b/packages/system/seaweedfs/Makefile index 2344f6b5..9d1f47cd 100644 --- a/packages/system/seaweedfs/Makefile +++ b/packages/system/seaweedfs/Makefile @@ -12,7 +12,6 @@ update: sed -i.bak "/ARG VERSION/ s|=.*|=$${version}|g" images/seaweedfs/Dockerfile && \ rm -f images/seaweedfs/Dockerfile.bak patch --no-backup-if-mismatch -p4 < patches/resize-api-server-annotation.diff - patch --no-backup-if-mismatch -p4 < patches/long-term-ca.diff #patch --no-backup-if-mismatch -p4 < patches/retention-policy-delete.yaml image: diff --git a/packages/system/seaweedfs/charts/seaweedfs/Chart.yaml b/packages/system/seaweedfs/charts/seaweedfs/Chart.yaml index c595d65e..6ea31115 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/Chart.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v1 description: SeaweedFS name: seaweedfs -appVersion: "3.99" +appVersion: "4.02" # Dev note: Trigger a helm chart release by `git tag -a helm-` -version: 4.0.399 +version: 4.0.402 diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-deployment.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-deployment.yaml index 8700a8a6..f6237bb7 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-deployment.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-deployment.yaml @@ -15,9 +15,9 @@ metadata: {{- toYaml .Values.allInOne.annotations | nindent 4 }} {{- end }} spec: - replicas: 1 + replicas: {{ .Values.allInOne.replicas | default 1 }} strategy: - type: Recreate + type: {{ .Values.allInOne.updateStrategy.type | default "Recreate" }} selector: matchLabels: app.kubernetes.io/name: {{ template "seaweedfs.name" . }} @@ -130,12 +130,23 @@ spec: value: {{ include "seaweedfs.cluster.masterAddress" . | quote }} - name: {{ $clusterFilerKey }} value: {{ include "seaweedfs.cluster.filerAddress" . | quote }} + {{- if .Values.allInOne.secretExtraEnvironmentVars }} + {{- range $key, $value := .Values.allInOne.secretExtraEnvironmentVars }} + - name: {{ $key }} + valueFrom: + {{ toYaml $value | nindent 16 }} + {{- end }} + {{- end }} command: - "/bin/sh" - "-ec" - | /usr/bin/weed \ + {{- if .Values.allInOne.loggingOverrideLevel }} + -v={{ .Values.allInOne.loggingOverrideLevel }} \ + {{- else }} -v={{ .Values.global.loggingLevel }} \ + {{- end }} server \ -dir=/data \ -master \ @@ -191,6 +202,9 @@ spec: {{- else if .Values.master.metricsPort }} -metricsPort={{ .Values.master.metricsPort }} \ {{- end }} + {{- if .Values.allInOne.metricsIp }} + -metricsIp={{ .Values.allInOne.metricsIp }} \ + {{- end }} -filer \ -filer.port={{ .Values.filer.port }} \ {{- if .Values.filer.disableDirListing }} @@ -219,61 +233,75 @@ spec: {{- end }} {{- if .Values.allInOne.s3.enabled }} -s3 \ - -s3.port={{ .Values.s3.port }} \ - {{- if .Values.s3.domainName }} - -s3.domainName={{ .Values.s3.domainName }} \ + -s3.port={{ .Values.allInOne.s3.port | default .Values.s3.port }} \ + {{- $domainName := .Values.allInOne.s3.domainName | default .Values.s3.domainName }} + {{- if $domainName }} + -s3.domainName={{ $domainName }} \ {{- end }} {{- if .Values.global.enableSecurity }} - {{- if .Values.s3.httpsPort }} - -s3.port.https={{ .Values.s3.httpsPort }} \ + {{- $httpsPort := .Values.allInOne.s3.httpsPort | default .Values.s3.httpsPort }} + {{- if $httpsPort }} + -s3.port.https={{ $httpsPort }} \ {{- end }} -s3.cert.file=/usr/local/share/ca-certificates/client/tls.crt \ -s3.key.file=/usr/local/share/ca-certificates/client/tls.key \ {{- end }} - {{- if eq (typeOf .Values.s3.allowEmptyFolder) "bool" }} - -s3.allowEmptyFolder={{ .Values.s3.allowEmptyFolder }} \ - {{- end }} - {{- if .Values.s3.enableAuth }} + {{- if or .Values.allInOne.s3.enableAuth .Values.s3.enableAuth .Values.filer.s3.enableAuth }} -s3.config=/etc/sw/s3/seaweedfs_s3_config \ {{- end }} - {{- if .Values.s3.auditLogConfig }} + {{- $auditLogConfig := .Values.allInOne.s3.auditLogConfig | default .Values.s3.auditLogConfig }} + {{- if $auditLogConfig }} -s3.auditLogConfig=/etc/sw/s3/s3_auditLogConfig.json \ {{- end }} {{- end }} {{- if .Values.allInOne.sftp.enabled }} -sftp \ - -sftp.port={{ .Values.sftp.port }} \ - {{- if .Values.sftp.sshPrivateKey }} - -sftp.sshPrivateKey={{ .Values.sftp.sshPrivateKey }} \ + -sftp.port={{ .Values.allInOne.sftp.port | default .Values.sftp.port }} \ + {{- $sshPrivateKey := .Values.allInOne.sftp.sshPrivateKey | default .Values.sftp.sshPrivateKey }} + {{- if $sshPrivateKey }} + -sftp.sshPrivateKey={{ $sshPrivateKey }} \ {{- end }} - {{- if .Values.sftp.hostKeysFolder }} - -sftp.hostKeysFolder={{ .Values.sftp.hostKeysFolder }} \ + {{- $hostKeysFolder := .Values.allInOne.sftp.hostKeysFolder | default .Values.sftp.hostKeysFolder }} + {{- if $hostKeysFolder }} + -sftp.hostKeysFolder={{ $hostKeysFolder }} \ {{- end }} - {{- if .Values.sftp.authMethods }} - -sftp.authMethods={{ .Values.sftp.authMethods }} \ + {{- $authMethods := .Values.allInOne.sftp.authMethods | default .Values.sftp.authMethods }} + {{- if $authMethods }} + -sftp.authMethods={{ $authMethods }} \ {{- end }} - {{- if .Values.sftp.maxAuthTries }} - -sftp.maxAuthTries={{ .Values.sftp.maxAuthTries }} \ + {{- $maxAuthTries := .Values.allInOne.sftp.maxAuthTries | default .Values.sftp.maxAuthTries }} + {{- if $maxAuthTries }} + -sftp.maxAuthTries={{ $maxAuthTries }} \ {{- end }} - {{- if .Values.sftp.bannerMessage }} - -sftp.bannerMessage="{{ .Values.sftp.bannerMessage }}" \ + {{- $bannerMessage := .Values.allInOne.sftp.bannerMessage | default .Values.sftp.bannerMessage }} + {{- if $bannerMessage }} + -sftp.bannerMessage="{{ $bannerMessage }}" \ {{- end }} - {{- if .Values.sftp.loginGraceTime }} - -sftp.loginGraceTime={{ .Values.sftp.loginGraceTime }} \ + {{- $loginGraceTime := .Values.allInOne.sftp.loginGraceTime | default .Values.sftp.loginGraceTime }} + {{- if $loginGraceTime }} + -sftp.loginGraceTime={{ $loginGraceTime }} \ {{- end }} - {{- if .Values.sftp.clientAliveInterval }} - -sftp.clientAliveInterval={{ .Values.sftp.clientAliveInterval }} \ + {{- $clientAliveInterval := .Values.allInOne.sftp.clientAliveInterval | default .Values.sftp.clientAliveInterval }} + {{- if $clientAliveInterval }} + -sftp.clientAliveInterval={{ $clientAliveInterval }} \ {{- end }} - {{- if .Values.sftp.clientAliveCountMax }} - -sftp.clientAliveCountMax={{ .Values.sftp.clientAliveCountMax }} \ + {{- $clientAliveCountMax := .Values.allInOne.sftp.clientAliveCountMax | default .Values.sftp.clientAliveCountMax }} + {{- if $clientAliveCountMax }} + -sftp.clientAliveCountMax={{ $clientAliveCountMax }} \ {{- end }} + {{- if or .Values.allInOne.sftp.enableAuth .Values.sftp.enableAuth }} -sftp.userStoreFile=/etc/sw/sftp/seaweedfs_sftp_config \ {{- end }} + {{- end }} + {{- $extraArgsCount := len .Values.allInOne.extraArgs }} + {{- range $i, $arg := .Values.allInOne.extraArgs }} + {{ $arg | quote }}{{ if ne (add1 $i) $extraArgsCount }} \{{ end }} + {{- end }} volumeMounts: - name: data mountPath: /data - {{- if and .Values.allInOne.s3.enabled (or .Values.s3.enableAuth .Values.filer.s3.enableAuth) }} + {{- if and .Values.allInOne.s3.enabled (or .Values.allInOne.s3.enableAuth .Values.s3.enableAuth .Values.filer.s3.enableAuth) }} - name: config-s3-users mountPath: /etc/sw/s3 readOnly: true @@ -282,10 +310,12 @@ spec: - name: config-ssh mountPath: /etc/sw/ssh readOnly: true + {{- if or .Values.allInOne.sftp.enableAuth .Values.sftp.enableAuth }} - mountPath: /etc/sw/sftp name: config-users readOnly: true {{- end }} + {{- end }} {{- if .Values.filer.notificationConfig }} - name: notification-config mountPath: /etc/seaweedfs/notification.toml @@ -332,15 +362,16 @@ spec: - containerPort: {{ .Values.filer.grpcPort }} name: swfs-fil-grpc {{- if .Values.allInOne.s3.enabled }} - - containerPort: {{ .Values.s3.port }} + - containerPort: {{ .Values.allInOne.s3.port | default .Values.s3.port }} name: swfs-s3 - {{- if .Values.s3.httpsPort }} - - containerPort: {{ .Values.s3.httpsPort }} + {{- $httpsPort := .Values.allInOne.s3.httpsPort | default .Values.s3.httpsPort }} + {{- if $httpsPort }} + - containerPort: {{ $httpsPort }} name: swfs-s3-tls {{- end }} {{- end }} {{- if .Values.allInOne.sftp.enabled }} - - containerPort: {{ .Values.sftp.port }} + - containerPort: {{ .Values.allInOne.sftp.port | default .Values.sftp.port }} name: swfs-sftp {{- end }} {{- if .Values.allInOne.metricsPort }} @@ -352,7 +383,7 @@ spec: httpGet: path: {{ .Values.allInOne.readinessProbe.httpGet.path }} port: {{ .Values.master.port }} - scheme: {{ .Values.allInOne.readinessProbe.scheme }} + scheme: {{ .Values.allInOne.readinessProbe.httpGet.scheme }} initialDelaySeconds: {{ .Values.allInOne.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.allInOne.readinessProbe.periodSeconds }} successThreshold: {{ .Values.allInOne.readinessProbe.successThreshold }} @@ -364,7 +395,7 @@ spec: httpGet: path: {{ .Values.allInOne.livenessProbe.httpGet.path }} port: {{ .Values.master.port }} - scheme: {{ .Values.allInOne.livenessProbe.scheme }} + scheme: {{ .Values.allInOne.livenessProbe.httpGet.scheme }} initialDelaySeconds: {{ .Values.allInOne.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.allInOne.livenessProbe.periodSeconds }} successThreshold: {{ .Values.allInOne.livenessProbe.successThreshold }} @@ -389,26 +420,31 @@ spec: path: {{ .Values.allInOne.data.hostPathPrefix }}/seaweedfs-all-in-one-data/ type: DirectoryOrCreate {{- else if eq .Values.allInOne.data.type "persistentVolumeClaim" }} + persistentVolumeClaim: + claimName: {{ template "seaweedfs.name" . }}-all-in-one-data + {{- else if eq .Values.allInOne.data.type "existingClaim" }} persistentVolumeClaim: claimName: {{ .Values.allInOne.data.claimName }} {{- else if eq .Values.allInOne.data.type "emptyDir" }} emptyDir: {} {{- end }} - {{- if and .Values.allInOne.s3.enabled (or .Values.s3.enableAuth .Values.filer.s3.enableAuth) }} + {{- if and .Values.allInOne.s3.enabled (or .Values.allInOne.s3.enableAuth .Values.s3.enableAuth .Values.filer.s3.enableAuth) }} - name: config-s3-users secret: defaultMode: 420 - secretName: {{ default (printf "%s-s3-secret" (include "seaweedfs.name" .)) (or .Values.s3.existingConfigSecret .Values.filer.s3.existingConfigSecret) }} + secretName: {{ default (printf "%s-s3-secret" (include "seaweedfs.name" .)) (or .Values.allInOne.s3.existingConfigSecret .Values.s3.existingConfigSecret .Values.filer.s3.existingConfigSecret) }} {{- end }} {{- if .Values.allInOne.sftp.enabled }} - name: config-ssh secret: defaultMode: 420 - secretName: {{ default (printf "%s-sftp-ssh-secret" (include "seaweedfs.name" .)) .Values.sftp.existingSshConfigSecret }} + secretName: {{ default (printf "%s-sftp-ssh-secret" (include "seaweedfs.name" .)) (or .Values.allInOne.sftp.existingSshConfigSecret .Values.sftp.existingSshConfigSecret) }} + {{- if or .Values.allInOne.sftp.enableAuth .Values.sftp.enableAuth }} - name: config-users secret: defaultMode: 420 - secretName: {{ default (printf "%s-sftp-secret" (include "seaweedfs.name" .)) .Values.sftp.existingConfigSecret }} + secretName: {{ default (printf "%s-sftp-secret" (include "seaweedfs.name" .)) (or .Values.allInOne.sftp.existingConfigSecret .Values.sftp.existingConfigSecret) }} + {{- end }} {{- end }} {{- if .Values.filer.notificationConfig }} - name: notification-config diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-pvc.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-pvc.yaml index 49ac2014..a62450c3 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-pvc.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-pvc.yaml @@ -1,21 +1,28 @@ -{{- if and .Values.allInOne.enabled (eq .Values.allInOne.data.type "persistentVolumeClaim") }} +{{- if .Values.allInOne.enabled }} +{{- if eq .Values.allInOne.data.type "persistentVolumeClaim" }} apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: {{ .Values.allInOne.data.claimName }} + name: {{ template "seaweedfs.name" . }}-all-in-one-data + namespace: {{ .Release.Namespace }} labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/component: seaweedfs-all-in-one - {{- if .Values.allInOne.annotations }} + {{- with .Values.allInOne.data.annotations }} annotations: - {{- toYaml .Values.allInOne.annotations | nindent 4 }} + {{- toYaml . | nindent 4 }} {{- end }} spec: accessModes: - - ReadWriteOnce - resources: - requests: - storage: {{ .Values.allInOne.data.size }} + {{- toYaml (.Values.allInOne.data.accessModes | default (list "ReadWriteOnce")) | nindent 4 }} {{- if .Values.allInOne.data.storageClass }} storageClassName: {{ .Values.allInOne.data.storageClass }} {{- end }} -{{- end }} \ No newline at end of file + resources: + requests: + storage: {{ .Values.allInOne.data.size | default "10Gi" }} +{{- end }} +{{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-service.yml b/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-service.yml index 14076a9c..b13f5789 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-service.yml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/all-in-one/all-in-one-service.yml @@ -15,6 +15,7 @@ metadata: {{- toYaml .Values.allInOne.service.annotations | nindent 4 }} {{- end }} spec: + type: {{ .Values.allInOne.service.type | default "ClusterIP" }} internalTrafficPolicy: {{ .Values.allInOne.service.internalTrafficPolicy | default "Cluster" }} ports: # Master ports @@ -50,13 +51,14 @@ spec: # S3 ports (if enabled) {{- if .Values.allInOne.s3.enabled }} - name: "swfs-s3" - port: {{ if .Values.allInOne.s3.enabled }}{{ .Values.s3.port }}{{ else }}{{ .Values.filer.s3.port }}{{ end }} - targetPort: {{ if .Values.allInOne.s3.enabled }}{{ .Values.s3.port }}{{ else }}{{ .Values.filer.s3.port }}{{ end }} + port: {{ .Values.allInOne.s3.port | default .Values.s3.port }} + targetPort: {{ .Values.allInOne.s3.port | default .Values.s3.port }} protocol: TCP - {{- if and .Values.allInOne.s3.enabled .Values.s3.httpsPort }} + {{- $httpsPort := .Values.allInOne.s3.httpsPort | default .Values.s3.httpsPort }} + {{- if $httpsPort }} - name: "swfs-s3-tls" - port: {{ .Values.s3.httpsPort }} - targetPort: {{ .Values.s3.httpsPort }} + port: {{ $httpsPort }} + targetPort: {{ $httpsPort }} protocol: TCP {{- end }} {{- end }} @@ -64,8 +66,8 @@ spec: # SFTP ports (if enabled) {{- if .Values.allInOne.sftp.enabled }} - name: "swfs-sftp" - port: {{ .Values.sftp.port }} - targetPort: {{ .Values.sftp.port }} + port: {{ .Values.allInOne.sftp.port | default .Values.sftp.port }} + targetPort: {{ .Values.allInOne.sftp.port | default .Values.sftp.port }} protocol: TCP {{- end }} @@ -80,4 +82,4 @@ spec: selector: app.kubernetes.io/name: {{ template "seaweedfs.name" . }} app.kubernetes.io/component: seaweedfs-all-in-one -{{- end }} \ No newline at end of file +{{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/cert/ca-cert.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/cert/ca-cert.yaml index f2572558..b01a8dcc 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/cert/ca-cert.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/cert/ca-cert.yaml @@ -13,8 +13,12 @@ spec: secretName: {{ template "seaweedfs.name" . }}-ca-cert commonName: "{{ template "seaweedfs.name" . }}-root-ca" isCA: true - duration: 87600h - renewBefore: 720h + {{- if .Values.certificates.ca.duration }} + duration: {{ .Values.certificates.ca.duration }} + {{- end }} + {{- if .Values.certificates.ca.renewBefore }} + renewBefore: {{ .Values.certificates.ca.renewBefore }} + {{- end }} issuerRef: name: {{ template "seaweedfs.name" . }}-issuer kind: Issuer diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-ingress.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-ingress.yaml index 9ce15ae9..b185a58b 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-ingress.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-ingress.yaml @@ -1,5 +1,8 @@ -{{- if .Values.filer.enabled }} -{{- if .Values.filer.ingress.enabled }} +{{- /* Filer ingress works for both normal mode (filer.enabled) and all-in-one mode (allInOne.enabled) */}} +{{- $filerEnabled := or .Values.filer.enabled .Values.allInOne.enabled }} +{{- if and $filerEnabled .Values.filer.ingress.enabled }} +{{- /* Determine service name based on deployment mode */}} +{{- $serviceName := ternary (printf "%s-all-in-one" (include "seaweedfs.name" .)) (printf "%s-filer" (include "seaweedfs.name" .)) .Values.allInOne.enabled }} {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} apiVersion: networking.k8s.io/v1 {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion }} @@ -33,16 +36,14 @@ spec: backend: {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} service: - name: {{ template "seaweedfs.name" . }}-filer + name: {{ $serviceName }} port: number: {{ .Values.filer.port }} - #name: {{- else }} - serviceName: {{ template "seaweedfs.name" . }}-filer + serviceName: {{ $serviceName }} servicePort: {{ .Values.filer.port }} {{- end }} {{- if .Values.filer.ingress.host }} host: {{ .Values.filer.ingress.host }} {{- end }} {{- end }} -{{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-statefulset.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-statefulset.yaml index 5c1a0950..2b8c2744 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-statefulset.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-statefulset.yaml @@ -213,9 +213,6 @@ spec: -s3.cert.file=/usr/local/share/ca-certificates/client/tls.crt \ -s3.key.file=/usr/local/share/ca-certificates/client/tls.key \ {{- end }} - {{- if eq (typeOf .Values.filer.s3.allowEmptyFolder) "bool" }} - -s3.allowEmptyFolder={{ .Values.filer.s3.allowEmptyFolder }} \ - {{- end }} {{- if .Values.filer.s3.enableAuth }} -s3.config=/etc/sw/seaweedfs_s3_config \ {{- end }} @@ -289,7 +286,7 @@ spec: httpGet: path: {{ .Values.filer.readinessProbe.httpGet.path }} port: {{ .Values.filer.port }} - scheme: {{ .Values.filer.readinessProbe.scheme }} + scheme: {{ .Values.filer.readinessProbe.httpGet.scheme }} initialDelaySeconds: {{ .Values.filer.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.filer.readinessProbe.periodSeconds }} successThreshold: {{ .Values.filer.readinessProbe.successThreshold }} @@ -301,7 +298,7 @@ spec: httpGet: path: {{ .Values.filer.livenessProbe.httpGet.path }} port: {{ .Values.filer.port }} - scheme: {{ .Values.filer.livenessProbe.scheme }} + scheme: {{ .Values.filer.livenessProbe.httpGet.scheme }} initialDelaySeconds: {{ .Values.filer.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.filer.livenessProbe.periodSeconds }} successThreshold: {{ .Values.filer.livenessProbe.successThreshold }} @@ -392,10 +389,12 @@ spec: nodeSelector: {{ tpl .Values.filer.nodeSelector . | indent 8 | trim }} {{- end }} - {{- if and (.Values.filer.enablePVC) (eq .Values.filer.data.type "persistentVolumeClaim") }} + {{- if and (.Values.filer.enablePVC) (not .Values.filer.data) }} # DEPRECATION: Deprecate in favor of filer.data section below volumeClaimTemplates: - - metadata: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: name: data-filer spec: accessModes: @@ -411,7 +410,9 @@ spec: {{- if $pvc_exists }} volumeClaimTemplates: {{- if eq .Values.filer.data.type "persistentVolumeClaim" }} - - metadata: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: name: data-filer {{- with .Values.filer.data.annotations }} annotations: @@ -425,7 +426,9 @@ spec: storage: {{ .Values.filer.data.size }} {{- end }} {{- if eq .Values.filer.logs.type "persistentVolumeClaim" }} - - metadata: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: name: seaweedfs-filer-log-volume {{- with .Values.filer.logs.annotations }} annotations: diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/master/master-statefulset.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/master/master-statefulset.yaml index 01387fc9..a7067345 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/master/master-statefulset.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/master/master-statefulset.yaml @@ -235,7 +235,7 @@ spec: httpGet: path: {{ .Values.master.readinessProbe.httpGet.path }} port: {{ .Values.master.port }} - scheme: {{ .Values.master.readinessProbe.scheme }} + scheme: {{ .Values.master.readinessProbe.httpGet.scheme }} initialDelaySeconds: {{ .Values.master.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.master.readinessProbe.periodSeconds }} successThreshold: {{ .Values.master.readinessProbe.successThreshold }} @@ -247,7 +247,7 @@ spec: httpGet: path: {{ .Values.master.livenessProbe.httpGet.path }} port: {{ .Values.master.port }} - scheme: {{ .Values.master.livenessProbe.scheme }} + scheme: {{ .Values.master.livenessProbe.httpGet.scheme }} initialDelaySeconds: {{ .Values.master.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.master.livenessProbe.periodSeconds }} successThreshold: {{ .Values.master.livenessProbe.successThreshold }} @@ -327,7 +327,9 @@ spec: {{- if $pvc_exists }} volumeClaimTemplates: {{- if eq .Values.master.data.type "persistentVolumeClaim"}} - - metadata: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: name: data-{{ .Release.Namespace }} {{- with .Values.master.data.annotations }} annotations: @@ -341,7 +343,9 @@ spec: storage: {{ .Values.master.data.size }} {{- end }} {{- if eq .Values.master.logs.type "persistentVolumeClaim"}} - - metadata: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: name: seaweedfs-master-log-volume {{- with .Values.master.logs.annotations }} annotations: diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-deployment.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-deployment.yaml index 0c6d52c3..29dd2d43 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-deployment.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-deployment.yaml @@ -143,9 +143,6 @@ spec: {{- if .Values.s3.domainName }} -domainName={{ .Values.s3.domainName }} \ {{- end }} - {{- if eq (typeOf .Values.s3.allowEmptyFolder) "bool" }} - -allowEmptyFolder={{ .Values.s3.allowEmptyFolder }} \ - {{- end }} {{- if .Values.s3.enableAuth }} -config=/etc/sw/seaweedfs_s3_config \ {{- end }} @@ -204,7 +201,7 @@ spec: httpGet: path: {{ .Values.s3.readinessProbe.httpGet.path }} port: {{ .Values.s3.port }} - scheme: {{ .Values.s3.readinessProbe.scheme }} + scheme: {{ .Values.s3.readinessProbe.httpGet.scheme }} initialDelaySeconds: {{ .Values.s3.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.s3.readinessProbe.periodSeconds }} successThreshold: {{ .Values.s3.readinessProbe.successThreshold }} @@ -216,7 +213,7 @@ spec: httpGet: path: {{ .Values.s3.livenessProbe.httpGet.path }} port: {{ .Values.s3.port }} - scheme: {{ .Values.s3.livenessProbe.scheme }} + scheme: {{ .Values.s3.livenessProbe.httpGet.scheme }} initialDelaySeconds: {{ .Values.s3.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.s3.livenessProbe.periodSeconds }} successThreshold: {{ .Values.s3.livenessProbe.successThreshold }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-ingress.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-ingress.yaml index a856923e..899773ae 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-ingress.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-ingress.yaml @@ -1,4 +1,9 @@ -{{- if .Values.s3.ingress.enabled }} +{{- /* S3 ingress works for standalone S3 gateway (s3.enabled), S3 on Filer (filer.s3.enabled), and all-in-one mode (allInOne.s3.enabled) */}} +{{- $s3Enabled := or .Values.s3.enabled (and .Values.filer.s3.enabled (not .Values.allInOne.enabled)) (and .Values.allInOne.enabled .Values.allInOne.s3.enabled) }} +{{- if and $s3Enabled .Values.s3.ingress.enabled }} +{{- /* Determine service name based on deployment mode */}} +{{- $serviceName := ternary (printf "%s-all-in-one" (include "seaweedfs.name" .)) (printf "%s-s3" (include "seaweedfs.name" .)) .Values.allInOne.enabled }} +{{- $s3Port := .Values.allInOne.s3.port | default .Values.s3.port }} {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} apiVersion: networking.k8s.io/v1 {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion }} @@ -32,13 +37,12 @@ spec: backend: {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} service: - name: {{ template "seaweedfs.name" . }}-s3 + name: {{ $serviceName }} port: - number: {{ .Values.s3.port }} - #name: + number: {{ $s3Port }} {{- else }} - serviceName: {{ template "seaweedfs.name" . }}-s3 - servicePort: {{ .Values.s3.port }} + serviceName: {{ $serviceName }} + servicePort: {{ $s3Port }} {{- end }} {{- if .Values.s3.ingress.host }} host: {{ .Values.s3.ingress.host | quote }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/shared/post-install-bucket-hook.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/shared/post-install-bucket-hook.yaml index 44d65089..a0c56edc 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/shared/post-install-bucket-hook.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/shared/post-install-bucket-hook.yaml @@ -1,6 +1,32 @@ -{{- if .Values.master.enabled }} -{{- if .Values.filer.s3.enabled }} -{{- if .Values.filer.s3.createBuckets }} +{{- /* Support bucket creation for both standalone filer.s3 and allInOne modes */}} +{{- $createBuckets := list }} +{{- $s3Enabled := false }} +{{- $enableAuth := false }} +{{- $existingConfigSecret := "" }} + +{{- /* Check allInOne mode first */}} +{{- if .Values.allInOne.enabled }} + {{- if .Values.allInOne.s3.enabled }} + {{- $s3Enabled = true }} + {{- if .Values.allInOne.s3.createBuckets }} + {{- $createBuckets = .Values.allInOne.s3.createBuckets }} + {{- end }} + {{- $enableAuth = or .Values.allInOne.s3.enableAuth .Values.s3.enableAuth .Values.filer.s3.enableAuth }} + {{- $existingConfigSecret = or .Values.allInOne.s3.existingConfigSecret .Values.s3.existingConfigSecret .Values.filer.s3.existingConfigSecret }} + {{- end }} +{{- else if .Values.master.enabled }} + {{- /* Check standalone filer.s3 mode */}} + {{- if .Values.filer.s3.enabled }} + {{- $s3Enabled = true }} + {{- if .Values.filer.s3.createBuckets }} + {{- $createBuckets = .Values.filer.s3.createBuckets }} + {{- end }} + {{- $enableAuth = .Values.filer.s3.enableAuth }} + {{- $existingConfigSecret = .Values.filer.s3.existingConfigSecret }} + {{- end }} +{{- end }} + +{{- if and $s3Enabled $createBuckets }} --- apiVersion: batch/v1 kind: Job @@ -32,9 +58,9 @@ spec: - name: WEED_CLUSTER_DEFAULT value: "sw" - name: WEED_CLUSTER_SW_MASTER - value: "{{ template "seaweedfs.name" . }}-master.{{ .Release.Namespace }}:{{ .Values.master.port }}" + value: {{ include "seaweedfs.cluster.masterAddress" . | quote }} - name: WEED_CLUSTER_SW_FILER - value: "{{ template "seaweedfs.name" . }}-filer-client.{{ .Release.Namespace }}:{{ .Values.filer.port }}" + value: {{ include "seaweedfs.cluster.filerAddress" . | quote }} - name: POD_IP valueFrom: fieldRef: @@ -71,24 +97,29 @@ spec: echo "Service at $url failed to become ready within 5 minutes" exit 1 } + {{- if .Values.allInOne.enabled }} + wait_for_service "http://$WEED_CLUSTER_SW_MASTER{{ .Values.allInOne.readinessProbe.httpGet.path }}" + wait_for_service "http://$WEED_CLUSTER_SW_FILER{{ .Values.filer.readinessProbe.httpGet.path }}" + {{- else }} wait_for_service "http://$WEED_CLUSTER_SW_MASTER{{ .Values.master.readinessProbe.httpGet.path }}" wait_for_service "http://$WEED_CLUSTER_SW_FILER{{ .Values.filer.readinessProbe.httpGet.path }}" - {{- range $reg, $props := $.Values.filer.s3.createBuckets }} - exec /bin/echo \ - "s3.bucket.create --name {{ $props.name }}" |\ + {{- end }} + {{- range $createBuckets }} + /bin/echo \ + "s3.bucket.create --name {{ .name }}" |\ /usr/bin/weed shell {{- end }} - {{- range $reg, $props := $.Values.filer.s3.createBuckets }} - {{- if $props.anonymousRead }} - exec /bin/echo \ + {{- range $createBuckets }} + {{- if .anonymousRead }} + /bin/echo \ "s3.configure --user anonymous \ - --buckets {{ $props.name }} \ + --buckets {{ .name }} \ --actions Read \ --apply true" |\ /usr/bin/weed shell {{- end }} {{- end }} - {{- if .Values.filer.s3.enableAuth }} + {{- if $enableAuth }} volumeMounts: - name: config-users mountPath: /etc/sw @@ -106,17 +137,15 @@ spec: {{- if .Values.filer.containerSecurityContext.enabled }} securityContext: {{- omit .Values.filer.containerSecurityContext "enabled" | toYaml | nindent 12 }} {{- end }} - {{- if .Values.filer.s3.enableAuth }} + {{- if $enableAuth }} volumes: - name: config-users secret: defaultMode: 420 - {{- if not (empty .Values.filer.s3.existingConfigSecret) }} - secretName: {{ .Values.filer.s3.existingConfigSecret }} + {{- if $existingConfigSecret }} + secretName: {{ $existingConfigSecret }} {{- else }} - secretName: seaweedfs-s3-secret + secretName: {{ template "seaweedfs.name" . }}-s3-secret {{- end }} - {{- end }}{{/** if .Values.filer.s3.enableAuth **/}} -{{- end }}{{/** if .Values.master.enabled **/}} -{{- end }}{{/** if .Values.filer.s3.enabled **/}} -{{- end }}{{/** if .Values.filer.s3.createBuckets **/}} + {{- end }} +{{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-statefulset.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-statefulset.yaml index 29a035a2..1a8964a5 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-statefulset.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-statefulset.yaml @@ -251,7 +251,7 @@ spec: httpGet: path: {{ $volume.readinessProbe.httpGet.path }} port: {{ $volume.port }} - scheme: {{ $volume.readinessProbe.scheme }} + scheme: {{ $volume.readinessProbe.httpGet.scheme }} initialDelaySeconds: {{ $volume.readinessProbe.initialDelaySeconds }} periodSeconds: {{ $volume.readinessProbe.periodSeconds }} successThreshold: {{ $volume.readinessProbe.successThreshold }} @@ -263,7 +263,7 @@ spec: httpGet: path: {{ $volume.livenessProbe.httpGet.path }} port: {{ $volume.port }} - scheme: {{ $volume.livenessProbe.scheme }} + scheme: {{ $volume.livenessProbe.httpGet.scheme }} initialDelaySeconds: {{ $volume.livenessProbe.initialDelaySeconds }} periodSeconds: {{ $volume.livenessProbe.periodSeconds }} successThreshold: {{ $volume.livenessProbe.successThreshold }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/values.yaml b/packages/system/seaweedfs/charts/seaweedfs/values.yaml index cf16623c..b71e1071 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/values.yaml @@ -22,6 +22,8 @@ global: serviceAccountName: "seaweedfs" automountServiceAccountToken: true certificates: + duration: 87600h + renewBefore: 720h alphacrds: false monitoring: enabled: false @@ -235,27 +237,27 @@ master: ingress: enabled: false - className: "nginx" + className: "" # host: false for "*" hostname host: "master.seaweedfs.local" path: "/sw-master/?(.*)" pathType: ImplementationSpecific - annotations: - nginx.ingress.kubernetes.io/auth-type: "basic" - nginx.ingress.kubernetes.io/auth-secret: "default/ingress-basic-auth-secret" - nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - SW-Master' - nginx.ingress.kubernetes.io/service-upstream: "true" - nginx.ingress.kubernetes.io/rewrite-target: /$1 - nginx.ingress.kubernetes.io/use-regex: "true" - nginx.ingress.kubernetes.io/enable-rewrite-log: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "false" - nginx.ingress.kubernetes.io/force-ssl-redirect: "false" - nginx.ingress.kubernetes.io/configuration-snippet: | - sub_filter '' ' '; #add base url - sub_filter '="/' '="./'; #make absolute paths to relative - sub_filter '=/' '=./'; - sub_filter '/seaweedfsstatic' './seaweedfsstatic'; - sub_filter_once off; + annotations: {} + # nginx.ingress.kubernetes.io/auth-type: "basic" + # nginx.ingress.kubernetes.io/auth-secret: "default/ingress-basic-auth-secret" + # nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - SW-Master' + # nginx.ingress.kubernetes.io/service-upstream: "true" + # nginx.ingress.kubernetes.io/rewrite-target: /$1 + # nginx.ingress.kubernetes.io/use-regex: "true" + # nginx.ingress.kubernetes.io/enable-rewrite-log: "true" + # nginx.ingress.kubernetes.io/ssl-redirect: "false" + # nginx.ingress.kubernetes.io/force-ssl-redirect: "false" + # nginx.ingress.kubernetes.io/configuration-snippet: | + # sub_filter '' ' '; #add base url + # sub_filter '="/' '="./'; #make absolute paths to relative + # sub_filter '=/' '=./'; + # sub_filter '/seaweedfsstatic' './seaweedfsstatic'; + # sub_filter_once off; tls: [] extraEnvironmentVars: @@ -308,7 +310,7 @@ volume: # limit file size to avoid out of memory, default 256mb fileSizeLimitMB: null # minimum free disk space(in percents). If free disk space lower this value - all volumes marks as ReadOnly - minFreeSpacePercent: 7 + minFreeSpacePercent: 1 # Custom command line arguments to add to the volume command # Example to fix IPv6 metrics connectivity issues: @@ -769,28 +771,28 @@ filer: ingress: enabled: false - className: "nginx" + className: "" # host: false for "*" hostname host: "seaweedfs.cluster.local" path: "/sw-filer/?(.*)" pathType: ImplementationSpecific - annotations: - nginx.ingress.kubernetes.io/backend-protocol: GRPC - nginx.ingress.kubernetes.io/auth-type: "basic" - nginx.ingress.kubernetes.io/auth-secret: "default/ingress-basic-auth-secret" - nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - SW-Filer' - nginx.ingress.kubernetes.io/service-upstream: "true" - nginx.ingress.kubernetes.io/rewrite-target: /$1 - nginx.ingress.kubernetes.io/use-regex: "true" - nginx.ingress.kubernetes.io/enable-rewrite-log: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "false" - nginx.ingress.kubernetes.io/force-ssl-redirect: "false" - nginx.ingress.kubernetes.io/configuration-snippet: | - sub_filter '' ' '; #add base url - sub_filter '="/' '="./'; #make absolute paths to relative - sub_filter '=/' '=./'; - sub_filter '/seaweedfsstatic' './seaweedfsstatic'; - sub_filter_once off; + annotations: {} + # nginx.ingress.kubernetes.io/backend-protocol: GRPC + # nginx.ingress.kubernetes.io/auth-type: "basic" + # nginx.ingress.kubernetes.io/auth-secret: "default/ingress-basic-auth-secret" + # nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - SW-Filer' + # nginx.ingress.kubernetes.io/service-upstream: "true" + # nginx.ingress.kubernetes.io/rewrite-target: /$1 + # nginx.ingress.kubernetes.io/use-regex: "true" + # nginx.ingress.kubernetes.io/enable-rewrite-log: "true" + # nginx.ingress.kubernetes.io/ssl-redirect: "false" + # nginx.ingress.kubernetes.io/force-ssl-redirect: "false" + # nginx.ingress.kubernetes.io/configuration-snippet: | + # sub_filter '' ' '; #add base url + # sub_filter '="/' '="./'; #make absolute paths to relative + # sub_filter '=/' '=./'; + # sub_filter '/seaweedfsstatic' './seaweedfsstatic'; + # sub_filter_once off; # extraEnvVars is a list of extra environment variables to set with the stateful set. extraEnvironmentVars: @@ -854,8 +856,6 @@ filer: port: 8333 # add additional https port httpsPort: 0 - # allow empty folders - allowEmptyFolder: false # Suffix of the host name, {bucket}.{domainName} domainName: "" # enable user & permission to s3 (need to inject to all services) @@ -873,7 +873,7 @@ filer: # anonymousRead: false s3: - enabled: true + enabled: false imageOverride: null restartPolicy: null replicas: 1 @@ -883,8 +883,6 @@ s3: httpsPort: 0 metricsPort: 9327 loggingOverrideLevel: null - # allow empty folders - allowEmptyFolder: true # enable user & permission to s3 (need to inject to all services) enableAuth: false # set to the name of an existing kubernetes Secret with the s3 json config file @@ -977,9 +975,9 @@ s3: extraEnvironmentVars: # Custom command line arguments to add to the s3 command - # Example to fix connection idle seconds: - extraArgs: ["-idleTimeout=30"] - # extraArgs: [] + # Default idleTimeout is 120 seconds. Example to customize: + # extraArgs: ["-idleTimeout=300"] + extraArgs: [] # used to configure livenessProbe on s3 containers # @@ -1009,7 +1007,7 @@ s3: ingress: enabled: false - className: "nginx" + className: "" # host: false for "*" hostname host: "seaweedfs.cluster.local" path: "/" @@ -1095,6 +1093,7 @@ allInOne: enabled: false imageOverride: null restartPolicy: Always + replicas: 1 # Number of replicas (note: multiple replicas may require shared storage) # Core configuration idleTimeout: 30 # Connection idle seconds @@ -1106,24 +1105,85 @@ allInOne: metricsIp: "" # Metrics listen IP. If empty, defaults to bindAddress loggingOverrideLevel: null # Override logging level - # Service configuration + # Custom command line arguments to add to the server command + # Example to fix IPv6 metrics connectivity issues: + # extraArgs: ["-metricsIp", "0.0.0.0"] + # Example with multiple args: + # extraArgs: ["-customFlag", "value", "-anotherFlag"] + extraArgs: [] + + # Update strategy configuration + # type: Recreate or RollingUpdate + # For single replica, Recreate is recommended to avoid data conflicts. + # For multiple replicas with RollingUpdate, you MUST use shared storage + # (e.g., data.type: persistentVolumeClaim with ReadWriteMany access mode) + # to avoid data loss or inconsistency between pods. + updateStrategy: + type: Recreate + + # S3 gateway configuration + # Note: Most parameters below default to null, which means they inherit from + # the global s3.* settings. Set explicit values here to override for allInOne only. s3: enabled: false # Whether to enable S3 gateway + port: null # S3 gateway port (null inherits from s3.port) + httpsPort: null # S3 gateway HTTPS port (null inherits from s3.httpsPort) + domainName: null # Suffix of the host name (null inherits from s3.domainName) + enableAuth: false # Enable user & permission to S3 + # Set to the name of an existing kubernetes Secret with the s3 json config file + # should have a secret key called seaweedfs_s3_config with an inline json config + existingConfigSecret: null + auditLogConfig: null # S3 audit log configuration (null inherits from s3.auditLogConfig) + # You may specify buckets to be created during the install process. + # Buckets may be exposed publicly by setting `anonymousRead` to `true` + # createBuckets: + # - name: bucket-a + # anonymousRead: true + # - name: bucket-b + # anonymousRead: false + + # SFTP server configuration + # Note: Most parameters below default to null, which means they inherit from + # the global sftp.* settings. Set explicit values here to override for allInOne only. sftp: enabled: false # Whether to enable SFTP server + port: null # SFTP port (null inherits from sftp.port) + sshPrivateKey: null # Path to SSH private key (null inherits from sftp.sshPrivateKey) + hostKeysFolder: null # Path to SSH host keys folder (null inherits from sftp.hostKeysFolder) + authMethods: null # Comma-separated auth methods (null inherits from sftp.authMethods) + maxAuthTries: null # Maximum authentication attempts (null inherits from sftp.maxAuthTries) + bannerMessage: null # Banner message (null inherits from sftp.bannerMessage) + loginGraceTime: null # Login grace time (null inherits from sftp.loginGraceTime) + clientAliveInterval: null # Client keep-alive interval (null inherits from sftp.clientAliveInterval) + clientAliveCountMax: null # Maximum missed keep-alive messages (null inherits from sftp.clientAliveCountMax) + enableAuth: false # Enable SFTP authentication + # Set to the name of an existing kubernetes Secret with the sftp json config file + existingConfigSecret: null + # Set to the name of an existing kubernetes Secret with the SSH keys + existingSshConfigSecret: null # Service settings service: annotations: {} # Annotations for the service type: ClusterIP # Service type (ClusterIP, NodePort, LoadBalancer) + internalTrafficPolicy: Cluster # Internal traffic policy + + # Note: For ingress in all-in-one mode, use the standard s3.ingress and + # filer.ingress settings. The templates automatically detect all-in-one mode + # and point to the correct service (seaweedfs-all-in-one instead of + # seaweedfs-s3 or seaweedfs-filer). # Storage configuration data: - type: "emptyDir" # Options: "hostPath", "persistentVolumeClaim", "emptyDir" + type: "emptyDir" # Options: "hostPath", "persistentVolumeClaim", "emptyDir", "existingClaim" hostPathPrefix: /mnt/data # Path prefix for hostPath volumes - claimName: seaweedfs-data-pvc # Name of the PVC to use - size: "" # Size of the PVC - storageClass: "" # Storage class for the PVC + claimName: seaweedfs-data-pvc # Name of the PVC to use (for existingClaim type) + size: null # Size of the PVC (null defaults to 10Gi for persistentVolumeClaim type) + storageClass: null # Storage class for the PVC (null uses cluster default) + # accessModes for the PVC. Default is ["ReadWriteOnce"]. + # For multi-replica deployments, use ["ReadWriteMany"] with a compatible storage class. + accessModes: [] + annotations: {} # Annotations for the PVC # Health checks readinessProbe: @@ -1131,7 +1191,7 @@ allInOne: httpGet: path: /cluster/status port: 9333 - scheme: HTTP + scheme: HTTP initialDelaySeconds: 10 periodSeconds: 15 successThreshold: 1 @@ -1143,7 +1203,7 @@ allInOne: httpGet: path: /cluster/status port: 9333 - scheme: HTTP + scheme: HTTP initialDelaySeconds: 20 periodSeconds: 30 successThreshold: 1 @@ -1152,6 +1212,18 @@ allInOne: # Additional resources extraEnvironmentVars: {} # Additional environment variables + # Secret environment variables (for database credentials, etc.) + # Example: + # secretExtraEnvironmentVars: + # WEED_POSTGRES_USERNAME: + # secretKeyRef: + # name: postgres-credentials + # key: username + # WEED_POSTGRES_PASSWORD: + # secretKeyRef: + # name: postgres-credentials + # key: password + secretExtraEnvironmentVars: {} extraVolumeMounts: "" # Additional volume mounts extraVolumes: "" # Additional volumes initContainers: "" # Init containers @@ -1171,7 +1243,7 @@ allInOne: matchLabels: app.kubernetes.io/name: {{ template "seaweedfs.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/component: master + app.kubernetes.io/component: seaweedfs-all-in-one topologyKey: kubernetes.io/hostname # Topology Spread Constraints Settings @@ -1179,16 +1251,16 @@ allInOne: # for a PodSpec. By Default no constraints are set. topologySpreadConstraints: "" - # Toleration Settings for master pods + # Toleration Settings for pods # This should be a multi-line string matching the Toleration array # in a PodSpec. tolerations: "" - # nodeSelector labels for master pod assignment, formatted as a muli-line string. + # nodeSelector labels for pod assignment, formatted as a muli-line string. # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector nodeSelector: "" - # Used to assign priority to master pods + # Used to assign priority to pods # ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ priorityClassName: "" @@ -1268,6 +1340,9 @@ certificates: keySize: 2048 duration: 2160h # 90d renewBefore: 360h # 15d + ca: + duration: 87600h # 10 years + renewBefore: 720h # 30d externalCertificates: # This will avoid the need to use cert-manager and will rely on providing your own external certificates and CA # you will need to store your provided certificates in the secret read by the different services: diff --git a/packages/system/seaweedfs/images/seaweedfs/Dockerfile b/packages/system/seaweedfs/images/seaweedfs/Dockerfile index 48776a56..7177c70f 100644 --- a/packages/system/seaweedfs/images/seaweedfs/Dockerfile +++ b/packages/system/seaweedfs/images/seaweedfs/Dockerfile @@ -1,2 +1,2 @@ -ARG VERSION=3.99 +ARG VERSION=4.02 FROM chrislusf/seaweedfs:${VERSION} diff --git a/packages/system/seaweedfs/patches/long-term-ca.diff b/packages/system/seaweedfs/patches/long-term-ca.diff deleted file mode 100644 index a53d697d..00000000 --- a/packages/system/seaweedfs/patches/long-term-ca.diff +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/cert/ca-cert.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/cert/ca-cert.yaml -index 0fd6615e..f2572558 100644 ---- a/packages/system/seaweedfs/charts/seaweedfs/templates/cert/ca-cert.yaml -+++ b/packages/system/seaweedfs/charts/seaweedfs/templates/cert/ca-cert.yaml -@@ -13,6 +13,8 @@ spec: - secretName: {{ template "seaweedfs.name" . }}-ca-cert - commonName: "{{ template "seaweedfs.name" . }}-root-ca" - isCA: true -+ duration: 87600h -+ renewBefore: 720h - issuerRef: - name: {{ template "seaweedfs.name" . }}-issuer - kind: Issuer From 540e1c6e0db744093569162158f41cc36fa1bbe0 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 9 Dec 2025 11:37:32 +0100 Subject: [PATCH 11/45] [virtual-machine,vm-instance] Add nodeAffinity for Windows VMs based on scheduling config Signed-off-by: Andrei Kvapil (cherry picked from commit 3975da93c6b8ac0c856c9756f1d0557e5045136c) --- .../virtual-machine/templates/_helpers.tpl | 33 +++++++++++++++++++ .../apps/virtual-machine/templates/vm.yaml | 2 ++ .../apps/vm-instance/templates/_helpers.tpl | 33 +++++++++++++++++++ packages/apps/vm-instance/templates/vm.yaml | 3 ++ 4 files changed, 71 insertions(+) diff --git a/packages/apps/virtual-machine/templates/_helpers.tpl b/packages/apps/virtual-machine/templates/_helpers.tpl index f3ade695..7b6929ac 100644 --- a/packages/apps/virtual-machine/templates/_helpers.tpl +++ b/packages/apps/virtual-machine/templates/_helpers.tpl @@ -69,3 +69,36 @@ Generate a stable UUID for cloud-init re-initialization upon upgrade. {{- end }} {{- $uuid }} {{- end }} + +{{/* +Node Affinity for Windows VMs +*/}} +{{- define "virtual-machine.nodeAffinity" -}} +{{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" -}} +{{- if $configMap -}} +{{- $dedicatedNodesForWindowsVMs := get $configMap.data "dedicatedNodesForWindowsVMs" -}} +{{- if eq $dedicatedNodesForWindowsVMs "true" -}} +{{- $isWindows := hasPrefix "windows" (toString .Values.instanceProfile) -}} +affinity: + nodeAffinity: + {{- if $isWindows }} + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: scheduling.cozystack.io/vm-windows + operator: In + values: + - "true" + {{- else }} + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: scheduling.cozystack.io/vm-windows + operator: NotIn + values: + - "true" + {{- end }} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/packages/apps/virtual-machine/templates/vm.yaml b/packages/apps/virtual-machine/templates/vm.yaml index 92084acb..efadf3af 100644 --- a/packages/apps/virtual-machine/templates/vm.yaml +++ b/packages/apps/virtual-machine/templates/vm.yaml @@ -124,6 +124,8 @@ spec: terminationGracePeriodSeconds: 30 + {{- include "virtual-machine.nodeAffinity" . | nindent 6 }} + volumes: - name: systemdisk dataVolume: diff --git a/packages/apps/vm-instance/templates/_helpers.tpl b/packages/apps/vm-instance/templates/_helpers.tpl index f3ade695..7b6929ac 100644 --- a/packages/apps/vm-instance/templates/_helpers.tpl +++ b/packages/apps/vm-instance/templates/_helpers.tpl @@ -69,3 +69,36 @@ Generate a stable UUID for cloud-init re-initialization upon upgrade. {{- end }} {{- $uuid }} {{- end }} + +{{/* +Node Affinity for Windows VMs +*/}} +{{- define "virtual-machine.nodeAffinity" -}} +{{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" -}} +{{- if $configMap -}} +{{- $dedicatedNodesForWindowsVMs := get $configMap.data "dedicatedNodesForWindowsVMs" -}} +{{- if eq $dedicatedNodesForWindowsVMs "true" -}} +{{- $isWindows := hasPrefix "windows" (toString .Values.instanceProfile) -}} +affinity: + nodeAffinity: + {{- if $isWindows }} + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: scheduling.cozystack.io/vm-windows + operator: In + values: + - "true" + {{- else }} + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: scheduling.cozystack.io/vm-windows + operator: NotIn + values: + - "true" + {{- end }} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/packages/apps/vm-instance/templates/vm.yaml b/packages/apps/vm-instance/templates/vm.yaml index 61fc65a7..acad3475 100644 --- a/packages/apps/vm-instance/templates/vm.yaml +++ b/packages/apps/vm-instance/templates/vm.yaml @@ -95,6 +95,9 @@ spec: noCloud: {} {{- end }} terminationGracePeriodSeconds: 30 + + {{- include "virtual-machine.nodeAffinity" . | nindent 6 }} + volumes: {{- range .Values.disks }} - name: disk-{{ .name }} From fb5b4da2b2745aa78ff751184c71a78f3f9cbdf3 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 16 Dec 2025 10:42:10 +0100 Subject: [PATCH 12/45] [cilium] Enable automatic pod rollout on configmap updates Signed-off-by: Andrei Kvapil (cherry picked from commit 97e8d2aa497b72fe5fa5ab53290256b7c3cf1f0a) --- packages/system/cilium/values.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/system/cilium/values.yaml b/packages/system/cilium/values.yaml index 72263556..abadcc36 100644 --- a/packages/system/cilium/values.yaml +++ b/packages/system/cilium/values.yaml @@ -18,3 +18,6 @@ cilium: digest: "sha256:81262986a41487bfa3d0465091d3a386def5bd1ab476350bd4af2fdee5846fe6" envoy: enabled: false + rollOutCiliumPods: true + operator: + rollOutPods: true From da44a65ba948bc664421ed06a35108f1a00323f2 Mon Sep 17 00:00:00 2001 From: Nikita <166552198+nbykov0@users.noreply.github.com> Date: Tue, 23 Dec 2025 17:11:22 +0300 Subject: [PATCH 13/45] [system/cilium] Enable topology-aware routing for services (#1734) --- packages/system/cilium/values.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/system/cilium/values.yaml b/packages/system/cilium/values.yaml index abadcc36..95159b1f 100644 --- a/packages/system/cilium/values.yaml +++ b/packages/system/cilium/values.yaml @@ -10,6 +10,7 @@ cilium: enabled: true loadBalancer: algorithm: maglev + serviceTopology: true ipam: mode: "kubernetes" image: From 37a2e3879827b8996924f0c9aa5cb7bfcae0ed57 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:27:57 +0000 Subject: [PATCH 14/45] Prepare release v0.39.0 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- packages/apps/http-cache/images/nginx-cache.tag | 2 +- packages/apps/kubernetes/images/kubevirt-csi-driver.tag | 2 +- packages/core/installer/values.yaml | 2 +- packages/core/testing/values.yaml | 2 +- packages/extra/bootbox/images/matchbox.tag | 2 +- packages/extra/seaweedfs/images/objectstorage-sidecar.tag | 2 +- packages/system/bucket/images/s3manager.tag | 2 +- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 4 ++-- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/values.yaml | 2 +- packages/system/kubeovn/values.yaml | 2 +- packages/system/kubevirt-csi-node/values.yaml | 2 +- packages/system/lineage-controller-webhook/values.yaml | 2 +- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 19 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/apps/http-cache/images/nginx-cache.tag b/packages/apps/http-cache/images/nginx-cache.tag index 0970e11c..185dcc66 100644 --- a/packages/apps/http-cache/images/nginx-cache.tag +++ b/packages/apps/http-cache/images/nginx-cache.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af +ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:e0a07082bb6fc6aeaae2315f335386f1705a646c72f9e0af512aebbca5cb2b15 diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index 61e9f2ba..550279b8 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:47396dac04a24f824f82892c90efa160539e1e0f8ac697f5dc8ecf397e1ac413 +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:64c0b248e0d6b0a1578f59cab725152f3223a6d53ac0371eccaf023c0cd75904 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index e1a56418..6f053bbd 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,2 +1,2 @@ cozystack: - image: ghcr.io/cozystack/cozystack/installer:v0.38.4@sha256:2530f78cd456e0b0619d8db607c4d47127bbdd36b99f82a4029758a106169f09 + image: ghcr.io/cozystack/cozystack/installer:v0.39.0@sha256:8fb2f4f9ab482a00b6200f56bed69fad68a117bb65b341d3beca4bed5bd7b80f diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index 97c640db..7bf63e3a 100755 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.38.4@sha256:8284585dfdc337d6720f44b82417b42dc9321661025a8c046e085004d4573597 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.39.0@sha256:c113cb262e8e8fd87c013a347c16def906e3a3dd179519f2ad6a5c054a44f271 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 48db300d..20f412c7 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v0.38.4@sha256:8398c5546dedfcd9bf68a6eef165e423b2f0bcb10a21f7e76b7ae4a6beb579e2 +ghcr.io/cozystack/cozystack/matchbox:v0.39.0@sha256:ec0d9e2a74e313c76a93f6a0522e31731f33cfc6575d14d93ddfe24f2fe53eec diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index c302bef2..0a20a5d9 100644 --- a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag +++ b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.4@sha256:5136a36dddb14d2b95ceefbf8cc4b0cf6411b2ec9ae6898173b24c57790cc857 +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.39.0@sha256:d5a52df856002b81cbff9c08c28d600271ccbaf202c36fc01aab7fdaec0ac7c9 diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index ec7741c2..3ba8a1db 100644 --- a/packages/system/bucket/images/s3manager.tag +++ b/packages/system/bucket/images/s3manager.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:b58f5b192d1f47026c19ad51a857e31c256a9ff46c24a5fbbfcfe4f61981b8eb +ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:eb363ee78465884ac314a3e5357ff30b0274dbee6ca2b94935b2ee2193870e0f diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 920222b6..65587d04 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,5 +1,5 @@ cozystackAPI: - image: ghcr.io/cozystack/cozystack/cozystack-api:v0.38.4@sha256:58dba7b8b9915ece3a5785b6178536377fce56dc55451f2736415cdeab9185ff + image: ghcr.io/cozystack/cozystack/cozystack-api:v0.39.0@sha256:d9669a49fd22b3f1ed56aa21857f99c63e88426488530092f606ad275f6ce650 localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index d1446268..b8ba4408 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,6 +1,6 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.38.4@sha256:a5cbe6b939ce10125b975e0395c9bd4a0c880aabe93dae832afc9334d33b7f63 + image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.39.0@sha256:af229b1d63705af9eb8bbf6867b4e94412516cd41ecfbc73f7fb19ac705e739a debug: false disableTelemetry: false - cozystackVersion: "v0.38.4" + cozystackVersion: "v0.39.0" cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index 6219aa0e..e2baa680 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} -{{- $tenantText := "v0.38.4" }} +{{- $tenantText := "v0.39.0" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index 52983589..5ce5fdd5 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.38.4@sha256:aae9aea2aa9aea905f0dea051104bff75f05bf554ed450ae013225359ae4f316 + image: ghcr.io/cozystack/cozystack/openapi-ui:v0.39.0@sha256:ea8b99160f7ab94de418bb445651769b8546b0659ae1f598670d4c581d370f88 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.38.4@sha256:2845688891e0cf9513768aa468ff4de5b6b40e58cc763bacbfe2b417ebad1bdf + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.39.0@sha256:0a5168e29ba667cc3532ebc6814c7f9c9f1102874a9d8b0c038e9fa2eefb6efd tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v0.38.4@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b + image: ghcr.io/cozystack/cozystack/token-proxy:v0.39.0@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index 40d85177..b8d00f00 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v0.38.4@sha256:458b8ae75e845e08c3c3d20f8af0ea1d7f8933739609d19f266fbf280417b764 + tag: v0.39.0@sha256:a8981fc592628855693aeeebc3a1f51058a8e667fa2405416b6ed1b1cbe86c09 repository: ghcr.io/cozystack/cozystack/kamaji resources: limits: @@ -13,4 +13,4 @@ kamaji: cpu: 100m memory: 100Mi extraArgs: - - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.38.4@sha256:458b8ae75e845e08c3c3d20f8af0ea1d7f8933739609d19f266fbf280417b764 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.39.0@sha256:a8981fc592628855693aeeebc3a1f51058a8e667fa2405416b6ed1b1cbe86c09 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index 9dc9d594..3478e16c 100644 --- a/packages/system/kubeovn-plunger/values.yaml +++ b/packages/system/kubeovn-plunger/values.yaml @@ -1,4 +1,4 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.38.4@sha256:e8bd1f93e38b7b7d124deddd0c2498f7df0fbe89b613d12a0fbf3e47220f9053 +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.39.0@sha256:ce250d9ac04dc251f105f385d204f58ec3c27ced6e1023e52e7e8121b3c99fd5 ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 83bbfd1a..7c3cbf0a 100644 --- a/packages/system/kubeovn-webhook/values.yaml +++ b/packages/system/kubeovn-webhook/values.yaml @@ -1,3 +1,3 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.38.4@sha256:cb39773a626f01da04cb4e818faa9460a7d193e045f5c80f2c2e9116d18ef3c0 +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.39.0@sha256:7bfd458299a507f2cf82cddb65941ded6991fd4ba92fd46010cbc8c363126085 diff --git a/packages/system/kubeovn/values.yaml b/packages/system/kubeovn/values.yaml index 7de48124..2b7f4f62 100644 --- a/packages/system/kubeovn/values.yaml +++ b/packages/system/kubeovn/values.yaml @@ -65,4 +65,4 @@ global: images: kubeovn: repository: kubeovn - tag: v1.14.11@sha256:e277ee347814af85528851cf3d604a65ab9fb2e833b173e658490c35e00596b0 + tag: v1.14.11@sha256:d413fa033b6e2971866f2b27d25d5482f923099afe4b58ceedcfd031b22e410d diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index 6ff9991a..2de432c4 100644 --- a/packages/system/kubevirt-csi-node/values.yaml +++ b/packages/system/kubevirt-csi-node/values.yaml @@ -1,3 +1,3 @@ storageClass: replicated csiDriver: - image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:47396dac04a24f824f82892c90efa160539e1e0f8ac697f5dc8ecf397e1ac413 + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:64c0b248e0d6b0a1578f59cab725152f3223a6d53ac0371eccaf023c0cd75904 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 80db7f86..ee39e044 100644 --- a/packages/system/lineage-controller-webhook/values.yaml +++ b/packages/system/lineage-controller-webhook/values.yaml @@ -1,5 +1,5 @@ lineageControllerWebhook: - image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.38.4@sha256:557e6cee3dfce7ec9e69b404806c6b3c3766fa523cb3dc1cbd7155c24af2cc96 + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.39.0@sha256:cb264b193256dd5db79ba50ad44fc2301fb19ac34155f6f6638d6cadfd45e370 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index b902622e..e7d1e757 100644 --- a/packages/system/objectstorage-controller/values.yaml +++ b/packages/system/objectstorage-controller/values.yaml @@ -1,3 +1,3 @@ objectstorage: controller: - image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.38.4@sha256:91ed0ba4de7958708f77427d2c752dece969236c13927c17033d792700e9c6f4" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.39.0@sha256:91ed0ba4de7958708f77427d2c752dece969236c13927c17033d792700e9c6f4" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 52ab1551..6d8d396d 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -124,7 +124,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.4@sha256:5136a36dddb14d2b95ceefbf8cc4b0cf6411b2ec9ae6898173b24c57790cc857" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.39.0@sha256:d5a52df856002b81cbff9c08c28d600271ccbaf202c36fc01aab7fdaec0ac7c9" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From b6106305926a1a1d8b31fd50412d159e468774e3 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 18 Dec 2025 09:13:35 +0100 Subject: [PATCH 15/45] [monitoring] Add SLACK_SEVERITY_FILTER field and VMAgent for tenant monitoring (#1712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [monitoring] Add SLACK_SEVERITY_FILTER field and VMAgent for tenant monitoring What this PR does This PR introduces the SLACK_SEVERITY_FILTER environment variable in the Alerta deployment to enable filtering of alert severities for Slack notifications based on the disabledSeverity configuration. Additionally, it adds a VMAgent resource template for scraping metrics within tenant namespaces, improving monitoring granularity and control. ```release-note [monitoring] Add SLACK_SEVERITY_FILTER for filtering Slack alert severities and VMAgent configuration for tenant-specific metrics scraping. ``` * **New Features** * Added configurable severity filtering for Telegram alerts. * Extended Slack severity filtering to accept lists of severities. * **Bug Fixes / Behavior** * Severity settings now accept arrays (multiple severities) instead of single comma-separated strings. * **Documentation** * Updated configuration docs and examples to show list-style severity settings. ✏️ Tip: You can customize this high-level summary in your review settings. --- packages/extra/monitoring/README.md | 39 ++++++++++--------- .../monitoring/templates/alerta/alerta.yaml | 8 +++- packages/extra/monitoring/values.schema.json | 17 ++++++-- packages/extra/monitoring/values.yaml | 6 ++- .../cozyrds/monitoring.yaml | 4 +- 5 files changed, 47 insertions(+), 27 deletions(-) diff --git a/packages/extra/monitoring/README.md b/packages/extra/monitoring/README.md index 3666765b..1bbd14b6 100644 --- a/packages/extra/monitoring/README.md +++ b/packages/extra/monitoring/README.md @@ -55,25 +55,26 @@ ### Alerta configuration -| Name | Description | Type | Value | -| ----------------------------------------- | ----------------------------------------------------------------- | ---------- | ------- | -| `alerta` | Configuration for the Alerta service. | `object` | `{}` | -| `alerta.storage` | Persistent volume size for the database. | `string` | `10Gi` | -| `alerta.storageClassName` | StorageClass used for the database. | `string` | `""` | -| `alerta.resources` | Resource configuration. | `object` | `{}` | -| `alerta.resources.requests` | Resource requests. | `object` | `{}` | -| `alerta.resources.requests.cpu` | CPU request. | `quantity` | `100m` | -| `alerta.resources.requests.memory` | Memory request. | `quantity` | `256Mi` | -| `alerta.resources.limits` | Resource limits. | `object` | `{}` | -| `alerta.resources.limits.cpu` | CPU limit. | `quantity` | `1` | -| `alerta.resources.limits.memory` | Memory limit. | `quantity` | `1Gi` | -| `alerta.alerts` | Alert routing configuration. | `object` | `{}` | -| `alerta.alerts.telegram` | Configuration for Telegram alerts. | `object` | `{}` | -| `alerta.alerts.telegram.token` | Telegram bot token. | `string` | `""` | -| `alerta.alerts.telegram.chatID` | Telegram chat ID(s), separated by commas. | `string` | `""` | -| `alerta.alerts.telegram.disabledSeverity` | List of severities without alerts (e.g. "informational,warning"). | `string` | `""` | -| `alerta.alerts.slack` | Configuration for Slack alerts. | `object` | `{}` | -| `alerta.alerts.slack.url` | Configuration uri for Slack alerts. | `string` | `""` | +| Name | Description | Type | Value | +| ----------------------------------------- | --------------------------------------------------------------------- | ---------- | ------- | +| `alerta` | Configuration for the Alerta service. | `object` | `{}` | +| `alerta.storage` | Persistent volume size for the database. | `string` | `10Gi` | +| `alerta.storageClassName` | StorageClass used for the database. | `string` | `""` | +| `alerta.resources` | Resource configuration. | `object` | `{}` | +| `alerta.resources.requests` | Resource requests. | `object` | `{}` | +| `alerta.resources.requests.cpu` | CPU request. | `quantity` | `100m` | +| `alerta.resources.requests.memory` | Memory request. | `quantity` | `256Mi` | +| `alerta.resources.limits` | Resource limits. | `object` | `{}` | +| `alerta.resources.limits.cpu` | CPU limit. | `quantity` | `1` | +| `alerta.resources.limits.memory` | Memory limit. | `quantity` | `1Gi` | +| `alerta.alerts` | Alert routing configuration. | `object` | `{}` | +| `alerta.alerts.telegram` | Configuration for Telegram alerts. | `object` | `{}` | +| `alerta.alerts.telegram.token` | Telegram bot token. | `string` | `""` | +| `alerta.alerts.telegram.chatID` | Telegram chat ID(s), separated by commas. | `string` | `""` | +| `alerta.alerts.telegram.disabledSeverity` | List of severities without alerts (e.g. ["informational","warning"]). | `[]string` | `[]` | +| `alerta.alerts.slack` | Configuration for Slack alerts. | `object` | `{}` | +| `alerta.alerts.slack.url` | Configuration uri for Slack alerts. | `string` | `""` | +| `alerta.alerts.slack.disabledSeverity` | List of severities without alerts (e.g. ["informational","warning"]). | `[]string` | `[]` | ### Grafana configuration diff --git a/packages/extra/monitoring/templates/alerta/alerta.yaml b/packages/extra/monitoring/templates/alerta/alerta.yaml index 71336286..72500948 100644 --- a/packages/extra/monitoring/templates/alerta/alerta.yaml +++ b/packages/extra/monitoring/templates/alerta/alerta.yaml @@ -129,13 +129,19 @@ spec: value: "{{ .Values.alerta.alerts.telegram.token }}" - name: TELEGRAM_WEBHOOK_URL value: "https://{{ printf "alerta.%s" (.Values.host | default $host) }}/api/webhooks/telegram?api-key={{ $apiKey }}" + {{- if .Values.alerta.alerts.telegram.disabledSeverity }} - name: TELEGRAM_DISABLE_NOTIFICATION_SEVERITY - value: "{{ .Values.alerta.alerts.telegram.disabledSeverity }}" + value: {{ .Values.alerta.alerts.telegram.disabledSeverity | toJson | quote }} + {{- end }} {{- end }} {{- if .Values.alerta.alerts.slack.url }} - name: "SLACK_WEBHOOK_URL" value: "{{ .Values.alerta.alerts.slack.url }}" + {{- if .Values.alerta.alerts.slack.disabledSeverity }} + - name: SLACK_SEVERITY_FILTER + value: {{ .Values.alerta.alerts.slack.disabledSeverity | toJson | quote }} + {{- end }} {{- end }} ports: diff --git a/packages/extra/monitoring/values.schema.json b/packages/extra/monitoring/values.schema.json index de81acd1..dcf8d320 100644 --- a/packages/extra/monitoring/values.schema.json +++ b/packages/extra/monitoring/values.schema.json @@ -20,6 +20,14 @@ "url" ], "properties": { + "disabledSeverity": { + "description": "List of severities without alerts (e.g. [\"informational\",\"warning\"]).", + "type": "array", + "default": [], + "items": { + "type": "string" + } + }, "url": { "description": "Configuration uri for Slack alerts.", "type": "string", @@ -42,9 +50,12 @@ "default": "" }, "disabledSeverity": { - "description": "List of severities without alerts (e.g. \"informational,warning\").", - "type": "string", - "default": "" + "description": "List of severities without alerts (e.g. [\"informational\",\"warning\"]).", + "type": "array", + "default": [], + "items": { + "type": "string" + } }, "token": { "description": "Telegram bot token.", diff --git a/packages/extra/monitoring/values.yaml b/packages/extra/monitoring/values.yaml index d687f213..6ac0fbf4 100644 --- a/packages/extra/monitoring/values.yaml +++ b/packages/extra/monitoring/values.yaml @@ -99,10 +99,11 @@ logsStorages: ## @typedef {struct} TelegramAlerts - Telegram alert configuration. ## @field {string} token - Telegram bot token. ## @field {string} chatID - Telegram chat ID(s), separated by commas. -## @field {string} [disabledSeverity] - List of severities without alerts (e.g. "informational,warning"). +## @field {[]string} [disabledSeverity] - List of severities without alerts (e.g. ["informational","warning"]). ## @typedef {struct} SlackAlerts - Slack alert configuration. ## @field {string} url - Configuration uri for Slack alerts. +## @field {[]string} [disabledSeverity] - List of severities without alerts (e.g. ["informational","warning"]). ## @typedef {struct} Alerts - Alert routing configuration. ## @field {TelegramAlerts} [telegram] - Configuration for Telegram alerts. @@ -129,9 +130,10 @@ alerta: telegram: token: "" chatID: "" - disabledSeverity: "" + disabledSeverity: [] slack: url: "" + disabledSeverity: [] ## ## @section Grafana configuration ## diff --git a/packages/system/cozystack-resource-definitions/cozyrds/monitoring.yaml b/packages/system/cozystack-resource-definitions/cozyrds/monitoring.yaml index 520e4671..774beccf 100644 --- a/packages/system/cozystack-resource-definitions/cozyrds/monitoring.yaml +++ b/packages/system/cozystack-resource-definitions/cozyrds/monitoring.yaml @@ -8,7 +8,7 @@ spec: singular: monitoring plural: monitorings openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"alerta":{"description":"Configuration for the Alerta service.","type":"object","default":{},"properties":{"alerts":{"description":"Alert routing configuration.","type":"object","default":{},"properties":{"slack":{"description":"Configuration for Slack alerts.","type":"object","default":{},"required":["url"],"properties":{"url":{"description":"Configuration uri for Slack alerts.","type":"string","default":""}}},"telegram":{"description":"Configuration for Telegram alerts.","type":"object","default":{},"required":["chatID","token"],"properties":{"chatID":{"description":"Telegram chat ID(s), separated by commas.","type":"string","default":""},"disabledSeverity":{"description":"List of severities without alerts (e.g. \"informational,warning\").","type":"string","default":""},"token":{"description":"Telegram bot token.","type":"string","default":""}}}}},"resources":{"description":"Resource configuration.","type":"object","default":{},"properties":{"limits":{"description":"Resource limits.","type":"object","default":{},"properties":{"cpu":{"description":"CPU limit.","default":1,"pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory limit.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"requests":{"description":"Resource requests.","type":"object","default":{},"properties":{"cpu":{"description":"CPU request.","default":"100m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory request.","default":"256Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}},"storage":{"description":"Persistent volume size for the database.","type":"string","default":"10Gi"},"storageClassName":{"description":"StorageClass used for the database.","type":"string","default":""}}},"grafana":{"description":"Configuration for Grafana.","type":"object","default":{},"properties":{"db":{"description":"Database configuration.","type":"object","default":{},"properties":{"size":{"description":"Persistent volume size for the database.","type":"string","default":"10Gi"}}},"resources":{"description":"Resource configuration.","type":"object","default":{},"properties":{"limits":{"description":"Resource limits.","type":"object","default":{},"properties":{"cpu":{"description":"CPU limit.","default":1,"pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory limit.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"requests":{"description":"Resource requests.","type":"object","default":{},"properties":{"cpu":{"description":"CPU request.","default":"100m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory request.","default":"256Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}}}},"host":{"description":"The hostname used to access Grafana externally (defaults to 'grafana' subdomain for the tenant host).","type":"string","default":""},"logsStorages":{"description":"Configuration of logs storage instances.","type":"array","default":[{"name":"generic","retentionPeriod":"1","storage":"10Gi","storageClassName":"replicated"}],"items":{"type":"object","required":["name","retentionPeriod","storage","storageClassName"],"properties":{"name":{"description":"Name of the storage instance.","type":"string"},"retentionPeriod":{"description":"Retention period for logs.","type":"string","default":"1"},"storage":{"description":"Persistent volume size.","type":"string","default":"10Gi"},"storageClassName":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}},"metricsStorages":{"description":"Configuration of metrics storage instances.","type":"array","default":[{"deduplicationInterval":"15s","name":"shortterm","retentionPeriod":"3d","storage":"10Gi","storageClassName":""},{"deduplicationInterval":"5m","name":"longterm","retentionPeriod":"14d","storage":"10Gi","storageClassName":""}],"items":{"type":"object","required":["deduplicationInterval","name","retentionPeriod","storage"],"properties":{"deduplicationInterval":{"description":"Deduplication interval for metrics.","type":"string"},"name":{"description":"Name of the storage instance.","type":"string"},"retentionPeriod":{"description":"Retention period for metrics.","type":"string"},"storage":{"description":"Persistent volume size.","type":"string","default":"10Gi"},"storageClassName":{"description":"StorageClass used for the data.","type":"string"},"vminsert":{"description":"Configuration for vminsert.","type":"object","properties":{"maxAllowed":{"description":"Maximum allowed resources.","type":"object","properties":{"cpu":{"description":"CPU limit.","default":1,"pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory limit.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"minAllowed":{"description":"Minimum guaranteed resources.","type":"object","properties":{"cpu":{"description":"CPU request.","default":"100m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory request.","default":"256Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}},"vmselect":{"description":"Configuration for vmselect.","type":"object","properties":{"maxAllowed":{"description":"Maximum allowed resources.","type":"object","properties":{"cpu":{"description":"CPU limit.","default":1,"pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory limit.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"minAllowed":{"description":"Minimum guaranteed resources.","type":"object","properties":{"cpu":{"description":"CPU request.","default":"100m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory request.","default":"256Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}},"vmstorage":{"description":"Configuration for vmstorage.","type":"object","properties":{"maxAllowed":{"description":"Maximum allowed resources.","type":"object","properties":{"cpu":{"description":"CPU limit.","default":1,"pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory limit.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"minAllowed":{"description":"Minimum guaranteed resources.","type":"object","properties":{"cpu":{"description":"CPU request.","default":"100m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory request.","default":"256Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}}}}}}} + {"title":"Chart Values","type":"object","properties":{"alerta":{"description":"Configuration for the Alerta service.","type":"object","default":{},"properties":{"alerts":{"description":"Alert routing configuration.","type":"object","default":{},"properties":{"slack":{"description":"Configuration for Slack alerts.","type":"object","default":{},"required":["url"],"properties":{"disabledSeverity":{"description":"List of severities without alerts (e.g. [\"informational\",\"warning\"]).","type":"array","default":[],"items":{"type":"string"}},"url":{"description":"Configuration uri for Slack alerts.","type":"string","default":""}}},"telegram":{"description":"Configuration for Telegram alerts.","type":"object","default":{},"required":["chatID","token"],"properties":{"chatID":{"description":"Telegram chat ID(s), separated by commas.","type":"string","default":""},"disabledSeverity":{"description":"List of severities without alerts (e.g. [\"informational\",\"warning\"]).","type":"array","default":[],"items":{"type":"string"}},"token":{"description":"Telegram bot token.","type":"string","default":""}}}}},"resources":{"description":"Resource configuration.","type":"object","default":{},"properties":{"limits":{"description":"Resource limits.","type":"object","default":{},"properties":{"cpu":{"description":"CPU limit.","default":1,"pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory limit.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"requests":{"description":"Resource requests.","type":"object","default":{},"properties":{"cpu":{"description":"CPU request.","default":"100m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory request.","default":"256Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}},"storage":{"description":"Persistent volume size for the database.","type":"string","default":"10Gi"},"storageClassName":{"description":"StorageClass used for the database.","type":"string","default":""}}},"grafana":{"description":"Configuration for Grafana.","type":"object","default":{},"properties":{"db":{"description":"Database configuration.","type":"object","default":{},"properties":{"size":{"description":"Persistent volume size for the database.","type":"string","default":"10Gi"}}},"resources":{"description":"Resource configuration.","type":"object","default":{},"properties":{"limits":{"description":"Resource limits.","type":"object","default":{},"properties":{"cpu":{"description":"CPU limit.","default":1,"pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory limit.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"requests":{"description":"Resource requests.","type":"object","default":{},"properties":{"cpu":{"description":"CPU request.","default":"100m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory request.","default":"256Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}}}},"host":{"description":"The hostname used to access Grafana externally (defaults to 'grafana' subdomain for the tenant host).","type":"string","default":""},"logsStorages":{"description":"Configuration of logs storage instances.","type":"array","default":[{"name":"generic","retentionPeriod":"1","storage":"10Gi","storageClassName":"replicated"}],"items":{"type":"object","required":["name","retentionPeriod","storage","storageClassName"],"properties":{"name":{"description":"Name of the storage instance.","type":"string"},"retentionPeriod":{"description":"Retention period for logs.","type":"string","default":"1"},"storage":{"description":"Persistent volume size.","type":"string","default":"10Gi"},"storageClassName":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}},"metricsStorages":{"description":"Configuration of metrics storage instances.","type":"array","default":[{"deduplicationInterval":"15s","name":"shortterm","retentionPeriod":"3d","storage":"10Gi","storageClassName":""},{"deduplicationInterval":"5m","name":"longterm","retentionPeriod":"14d","storage":"10Gi","storageClassName":""}],"items":{"type":"object","required":["deduplicationInterval","name","retentionPeriod","storage"],"properties":{"deduplicationInterval":{"description":"Deduplication interval for metrics.","type":"string"},"name":{"description":"Name of the storage instance.","type":"string"},"retentionPeriod":{"description":"Retention period for metrics.","type":"string"},"storage":{"description":"Persistent volume size.","type":"string","default":"10Gi"},"storageClassName":{"description":"StorageClass used for the data.","type":"string"},"vminsert":{"description":"Configuration for vminsert.","type":"object","properties":{"maxAllowed":{"description":"Maximum allowed resources.","type":"object","properties":{"cpu":{"description":"CPU limit.","default":1,"pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory limit.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"minAllowed":{"description":"Minimum guaranteed resources.","type":"object","properties":{"cpu":{"description":"CPU request.","default":"100m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory request.","default":"256Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}},"vmselect":{"description":"Configuration for vmselect.","type":"object","properties":{"maxAllowed":{"description":"Maximum allowed resources.","type":"object","properties":{"cpu":{"description":"CPU limit.","default":1,"pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory limit.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"minAllowed":{"description":"Minimum guaranteed resources.","type":"object","properties":{"cpu":{"description":"CPU request.","default":"100m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory request.","default":"256Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}},"vmstorage":{"description":"Configuration for vmstorage.","type":"object","properties":{"maxAllowed":{"description":"Maximum allowed resources.","type":"object","properties":{"cpu":{"description":"CPU limit.","default":1,"pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory limit.","default":"1Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"minAllowed":{"description":"Minimum guaranteed resources.","type":"object","properties":{"cpu":{"description":"CPU request.","default":"100m","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory request.","default":"256Mi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}}}}}}}}} release: prefix: "" labels: @@ -28,7 +28,7 @@ spec: description: Monitoring and observability stack module: true icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzI2OCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zMjY4KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMzg0NTRGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNNTQuMTcyMiAzOC43NzU3SDMzLjEwMzJDMzIuMTMwNyAzOC43NzU3IDMxLjQ4MjQgMzguMTI3NCAzMS40ODI0IDM3LjE1NDlDMzEuNDgyNCAzNi4xODI0IDMyLjEzMDcgMzUuNTM0MiAzMy4xMDMyIDM1LjUzNDJINTQuMTcyMkM1NS4xNDQ3IDM1LjUzNDIgNTUuNzkzIDM2LjE4MjQgNTUuNzkzIDM3LjE1NDlDNTUuNzkyOCAzOC4xMjc0IDU1LjE0NDUgMzguNzc1NyA1NC4xNzIyIDM4Ljc3NTdaIiBmaWxsPSIjREQzNDJFIi8+CjxwYXRoIGQ9Ik02My44OTYzIDQ1LjI1OTFINDEuMjA2N0M0MC4yMzQyIDQ1LjI1OTEgMzkuNTg1OSA0NC42MTA4IDM5LjU4NTkgNDMuNjM4M0MzOS41ODU5IDQyLjY2NTggNDAuMjM0MiA0Mi4wMTc2IDQxLjIwNjcgNDIuMDE3Nkg2My44OTYzQzY0Ljg2ODggNDIuMDE3NiA2NS41MTcxIDQyLjY2NTggNjUuNTE3MSA0My42MzgzQzY1LjUxNzEgNDQuNjEwOCA2NC44Njg4IDQ1LjI1OTEgNjMuODk2MyA0NS4yNTkxWiIgZmlsbD0iIzczODNCRiIvPgo8cGF0aCBkPSJNMzQuNzI0IDQ1LjI1OTFIMzMuMTAzMkMzMi4xMzA3IDQ1LjI1OTEgMzEuNDgyNCA0NC42MTA4IDMxLjQ4MjQgNDMuNjM4M0MzMS40ODI0IDQyLjY2NTggMzIuMTMwNyA0Mi4wMTc2IDMzLjEwMzIgNDIuMDE3NkgzNC43MjRDMzUuNjk2NCA0Mi4wMTc2IDM2LjM0NDcgNDIuNjY1OCAzNi4zNDQ3IDQzLjYzODNDMzYuMzQ0NyA0NC42MTA4IDM1LjY5NjMgNDUuMjU5MSAzNC43MjQgNDUuMjU5MVoiIGZpbGw9IiM0MkIwNUMiLz4KPHBhdGggZD0iTTYzLjg5NjMgMzguNzc1N0g2MC42NTQ5QzU5LjY4MjQgMzguNzc1NyA1OS4wMzQyIDM4LjEyNzQgNTkuMDM0MiAzNy4xNTQ5QzU5LjAzNDIgMzYuMTgyNCA1OS42ODI0IDM1LjUzNDIgNjAuNjU0OSAzNS41MzQySDYzLjg5NjNDNjQuODY4OCAzNS41MzQyIDY1LjUxNzEgMzYuMTgyNCA2NS41MTcxIDM3LjE1NDlDNjUuNTE3MSAzOC4xMjc0IDY0Ljg2ODggMzguNzc1NyA2My44OTYzIDM4Ljc3NTdaIiBmaWxsPSIjRUNCQTE2Ii8+CjxwYXRoIGQ9Ik00Ny42ODkzIDUxLjc0MTNIMzMuMTAzMkMzMi4xMzA3IDUxLjc0MTMgMzEuNDgyNCA1MS4wOTMxIDMxLjQ4MjQgNTAuMTIwNkMzMS40ODI0IDQ5LjE0ODEgMzIuMTMwNyA0OC41IDMzLjEwMzIgNDguNUg0Ny42ODkzQzQ4LjY2MTggNDguNSA0OS4zMTAxIDQ5LjE0ODMgNDkuMzEwMSA1MC4xMjA4QzQ5LjMxMDEgNTEuMDkzMyA0OC42NjE4IDUxLjc0MTMgNDcuNjg5MyA1MS43NDEzWiIgZmlsbD0iI0REMzQyRSIvPgo8cGF0aCBkPSJNNjMuODk2OCA1MS43NDEzSDU0LjE3MjVDNTMuMiA1MS43NDEzIDUyLjU1MTggNTEuMDkzMSA1Mi41NTE4IDUwLjEyMDZDNTIuNTUxOCA0OS4xNDgxIDUzLjIwMDIgNDguNSA1NC4xNzI3IDQ4LjVINjMuODk2OUM2NC44Njk0IDQ4LjUgNjUuNTE3NyA0OS4xNDgzIDY1LjUxNzcgNTAuMTIwOEM2NS41MTc3IDUxLjA5MzMgNjQuODY5MiA1MS43NDEzIDYzLjg5NjggNTEuNzQxM1oiIGZpbGw9IiNFQ0JBMTYiLz4KPHBhdGggZD0iTTU0LjE3MjIgNTguMjI0SDMzLjEwMzJDMzIuMTMwNyA1OC4yMjQgMzEuNDgyNCA1Ny41NzU3IDMxLjQ4MjQgNTYuNjAzMkMzMS40ODI0IDU1LjYzMDcgMzIuMTMwNyA1NC45ODI0IDMzLjEwMzIgNTQuOTgyNEg1NC4xNzIyQzU1LjE0NDcgNTQuOTgyNCA1NS43OTMgNTUuNjMwNyA1NS43OTMgNTYuNjAzMkM1NS43OTMgNTcuNTc1NyA1NS4xNDQ1IDU4LjIyNCA1NC4xNzIyIDU4LjIyNFoiIGZpbGw9IiM0MkIwNUMiLz4KPHBhdGggZD0iTTYzLjg5NjMgNjQuNzA3NEg0MS4yMDY3QzQwLjIzNDIgNjQuNzA3NCAzOS41ODU5IDY0LjA1OTEgMzkuNTg1OSA2My4wODY2QzM5LjU4NTkgNjIuMTE0MSA0MC4yMzQyIDYxLjQ2NTggNDEuMjA2NyA2MS40NjU4SDYzLjg5NjNDNjQuODY4OCA2MS40NjU4IDY1LjUxNzEgNjIuMTE0MSA2NS41MTcxIDYzLjA4NjZDNjUuNTE3MSA2NC4wNTkxIDY0Ljg2ODggNjQuNzA3NCA2My44OTYzIDY0LjcwNzRaIiBmaWxsPSIjRUNCQTE2Ii8+CjxwYXRoIGQ9Ik0zNC43MjQgNjQuNzA3NEgzMy4xMDMyQzMyLjEzMDcgNjQuNzA3NCAzMS40ODI0IDY0LjA1OTEgMzEuNDgyNCA2My4wODY2QzMxLjQ4MjQgNjIuMTE0MSAzMi4xMzA3IDYxLjQ2NTggMzMuMTAzMiA2MS40NjU4SDM0LjcyNEMzNS42OTY0IDYxLjQ2NTggMzYuMzQ0NyA2Mi4xMTQxIDM2LjM0NDcgNjMuMDg2NkMzNi4zNDQ3IDY0LjA1OTEgMzUuNjk2MyA2NC43MDc0IDM0LjcyNCA2NC43MDc0WiIgZmlsbD0iI0REMzQyRSIvPgo8cGF0aCBkPSJNNDcuNjg5MyA3MS4xODk4SDMzLjEwMzJDMzIuMTMwNyA3MS4xODk4IDMxLjQ4MjQgNzAuNTQxNSAzMS40ODI0IDY5LjU2OUMzMS40ODI0IDY4LjU5NjUgMzIuMTMwNyA2Ny45NDgyIDMzLjEwMzIgNjcuOTQ4Mkg0Ny42ODkzQzQ4LjY2MTggNjcuOTQ4MiA0OS4zMTAxIDY4LjU5NjUgNDkuMzEwMSA2OS41NjlDNDkuMzEwMSA3MC41NDE1IDQ4LjY2MTggNzEuMTg5OCA0Ny42ODkzIDcxLjE4OThaIiBmaWxsPSIjNDJCMDVDIi8+CjxwYXRoIGQ9Ik02My44OTY4IDcxLjE4OThINTQuMTcyNUM1My4yIDcxLjE4OTggNTIuNTUxOCA3MC41NDE1IDUyLjU1MTggNjkuNTY5QzUyLjU1MTggNjguNTk2NSA1My4yIDY3Ljk0ODIgNTQuMTcyNSA2Ny45NDgySDYzLjg5NjhDNjQuODY5MiA2Ny45NDgyIDY1LjUxNzUgNjguNTk2NSA2NS41MTc1IDY5LjU2OUM2NS41MTc1IDcwLjU0MTUgNjQuODY5MiA3MS4xODk4IDYzLjg5NjggNzEuMTg5OFoiIGZpbGw9IiM3MzgzQkYiLz4KPHBhdGggZD0iTTU0LjE3MjIgNzcuNjcyMkgzMy4xMDMyQzMyLjEzMDcgNzcuNjcyMiAzMS40ODI0IDc3LjAyMzkgMzEuNDgyNCA3Ni4wNTE0QzMxLjQ4MjQgNzUuMDc4OSAzMi4xMzA3IDc0LjQzMDcgMzMuMTAzMiA3NC40MzA3SDU0LjE3MjJDNTUuMTQ0NyA3NC40MzA3IDU1Ljc5MyA3NS4wNzg5IDU1Ljc5MyA3Ni4wNTE0QzU1Ljc5MjggNzcuMDIzOSA1NS4xNDQ1IDc3LjY3MjIgNTQuMTcyMiA3Ny42NzIyWiIgZmlsbD0iI0VDQkExNiIvPgo8cGF0aCBkPSJNNjMuODk2MyA3Ny42NzIySDYwLjY1NDlDNTkuNjgyNCA3Ny42NzIyIDU5LjAzNDIgNzcuMDIzOSA1OS4wMzQyIDc2LjA1MTRDNTkuMDM0MiA3NS4wNzg5IDU5LjY4MjQgNzQuNDMwNyA2MC42NTQ5IDc0LjQzMDdINjMuODk2M0M2NC44Njg4IDc0LjQzMDcgNjUuNTE3MSA3NS4wNzg5IDY1LjUxNzEgNzYuMDUxNEM2NS41MTcxIDc3LjAyMzkgNjQuODY4OCA3Ny42NzIyIDYzLjg5NjMgNzcuNjcyMloiIGZpbGw9IiM0MkIwNUMiLz4KPHBhdGggZD0iTTEwMS4xNzIgNzcuNjcyMkg4MC4xMDMyQzc5LjEzMDcgNzcuNjcyMiA3OC40ODI0IDc3LjAyMzkgNzguNDgyNCA3Ni4wNTE0Qzc4LjQ4MjQgNzUuMDc4OSA3OS4xMzA3IDc0LjQzMDcgODAuMTAzMiA3NC40MzA3SDEwMS4xNzJDMTAyLjE0NSA3NC40MzA3IDEwMi43OTMgNzUuMDc4OSAxMDIuNzkzIDc2LjA1MTRDMTAyLjc5MyA3Ny4wMjM5IDEwMi4xNDUgNzcuNjcyMiAxMDEuMTcyIDc3LjY3MjJaIiBmaWxsPSIjREQzNDJFIi8+CjxwYXRoIGQ9Ik0xMTAuODk2IDc3LjY3MjJIMTA3LjY1NUMxMDYuNjgyIDc3LjY3MjIgMTA2LjAzNCA3Ny4wMjM5IDEwNi4wMzQgNzYuMDUxNEMxMDYuMDM0IDc1LjA3ODkgMTA2LjY4MiA3NC40MzA3IDEwNy42NTUgNzQuNDMwN0gxMTAuODk2QzExMS44NjkgNzQuNDMwNyAxMTIuNTE3IDc1LjA3ODkgMTEyLjUxNyA3Ni4wNTE0QzExMi41MTcgNzcuMDIzOSAxMTEuODY5IDc3LjY3MjIgMTEwLjg5NiA3Ny42NzIyWiIgZmlsbD0iIzQyQjA1QyIvPgo8cGF0aCBkPSJNNjMuODk2MyA1OC4yMjRINjAuNjU0OUM1OS42ODI0IDU4LjIyNCA1OS4wMzQyIDU3LjU3NTcgNTkuMDM0MiA1Ni42MDMyQzU5LjAzNDIgNTUuNjMwNyA1OS42ODI0IDU0Ljk4MjQgNjAuNjU0OSA1NC45ODI0SDYzLjg5NjNDNjQuODY4OCA1NC45ODI0IDY1LjUxNzEgNTUuNjMwNyA2NS41MTcxIDU2LjYwMzJDNjUuNTE3MSA1Ny41NzU3IDY0Ljg2ODggNTguMjI0IDYzLjg5NjMgNTguMjI0WiIgZmlsbD0iIzczODNCRiIvPgo8cGF0aCBkPSJNMTEyLjUxNyA1MS43NDExQzExMi41MTcgNjAuNjU0OSAxMDUuMjI0IDY3Ljk0OCA5Ni4zMTA0IDY3Ljk0OEM4Ny4zOTY2IDY3Ljk0OCA4MC4xMDM1IDYwLjY1NDkgODAuMTAzNSA1MS43NDExQzgwLjEwMzUgNDIuODI3MyA4Ny4zOTY2IDM1LjUzNDIgOTYuMzEwNCAzNS41MzQyQzEwNS4yMjQgMzUuNTM0MiAxMTIuNTE3IDQyLjgyNzMgMTEyLjUxNyA1MS43NDExWiIgZmlsbD0iI0VDQkExNiIvPgo8cGF0aCBkPSJNODAuMTAzNSA1MS43NDExQzgwLjEwMzUgNTIuMjI3MyA4MC4xMDM1IDUyLjg3NTUgODAuMTAzNSA1My4zNjE5SDk2LjMxMDRWMzUuNTM0MkM4Ny4zOTY2IDM1LjUzNDIgODAuMTAzNSA0Mi44MjczIDgwLjEwMzUgNTEuNzQxMVoiIGZpbGw9IiM0MkIwNUMiLz4KPC9nPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zMjY4IiB4MT0iMS4yMzIzOWUtMDYiIHkxPSItOS41MDAwMSIgeDI9IjE2OCIgeTI9IjE2MiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjOEZEREZGIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwNzVGRiIvPgo8L2xpbmVhckdyYWRpZW50Pgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzY4N18zMjY4Ij4KPHJlY3Qgd2lkdGg9Ijk0IiBoZWlnaHQ9Ijk0IiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjUgMjUpIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg== - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "metricsStorages"], ["spec", "logsStorages"], ["spec", "alerta"], ["spec", "alerta", "storage"], ["spec", "alerta", "storageClassName"], ["spec", "alerta", "resources"], ["spec", "alerta", "resources", "limits"], ["spec", "alerta", "resources", "limits", "cpu"], ["spec", "alerta", "resources", "limits", "memory"], ["spec", "alerta", "resources", "requests"], ["spec", "alerta", "resources", "requests", "cpu"], ["spec", "alerta", "resources", "requests", "memory"], ["spec", "alerta", "alerts"], ["spec", "alerta", "alerts", "telegram"], ["spec", "alerta", "alerts", "telegram", "token"], ["spec", "alerta", "alerts", "telegram", "chatID"], ["spec", "alerta", "alerts", "telegram", "disabledSeverity"], ["spec", "alerta", "alerts", "slack"], ["spec", "alerta", "alerts", "slack", "url"], ["spec", "grafana"], ["spec", "grafana", "db"], ["spec", "grafana", "db", "size"], ["spec", "grafana", "resources"], ["spec", "grafana", "resources", "limits"], ["spec", "grafana", "resources", "limits", "cpu"], ["spec", "grafana", "resources", "limits", "memory"], ["spec", "grafana", "resources", "requests"], ["spec", "grafana", "resources", "requests", "cpu"], ["spec", "grafana", "resources", "requests", "memory"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "metricsStorages"], ["spec", "logsStorages"], ["spec", "alerta"], ["spec", "alerta", "storage"], ["spec", "alerta", "storageClassName"], ["spec", "alerta", "resources"], ["spec", "alerta", "resources", "limits"], ["spec", "alerta", "resources", "limits", "cpu"], ["spec", "alerta", "resources", "limits", "memory"], ["spec", "alerta", "resources", "requests"], ["spec", "alerta", "resources", "requests", "cpu"], ["spec", "alerta", "resources", "requests", "memory"], ["spec", "alerta", "alerts"], ["spec", "alerta", "alerts", "telegram"], ["spec", "alerta", "alerts", "telegram", "token"], ["spec", "alerta", "alerts", "telegram", "chatID"], ["spec", "alerta", "alerts", "telegram", "disabledSeverity"], ["spec", "alerta", "alerts", "slack"], ["spec", "alerta", "alerts", "slack", "url"], ["spec", "alerta", "alerts", "slack", "disabledSeverity"], ["spec", "grafana"], ["spec", "grafana", "db"], ["spec", "grafana", "db", "size"], ["spec", "grafana", "resources"], ["spec", "grafana", "resources", "limits"], ["spec", "grafana", "resources", "limits", "cpu"], ["spec", "grafana", "resources", "limits", "memory"], ["spec", "grafana", "resources", "requests"], ["spec", "grafana", "resources", "requests", "cpu"], ["spec", "grafana", "resources", "requests", "memory"]] secrets: exclude: [] include: From 0e262838d2be9a35f98e05067b9dcc71f9614071 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Tue, 23 Dec 2025 23:26:06 +0000 Subject: [PATCH 16/45] Prepare release v0.39.1 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- packages/apps/http-cache/images/nginx-cache.tag | 2 +- packages/core/installer/values.yaml | 2 +- packages/core/testing/values.yaml | 2 +- packages/extra/bootbox/images/matchbox.tag | 2 +- packages/extra/seaweedfs/images/objectstorage-sidecar.tag | 2 +- packages/system/bucket/images/s3manager.tag | 2 +- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 4 ++-- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/values.yaml | 2 +- packages/system/kubeovn/values.yaml | 2 +- packages/system/lineage-controller-webhook/values.yaml | 2 +- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 17 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/apps/http-cache/images/nginx-cache.tag b/packages/apps/http-cache/images/nginx-cache.tag index 185dcc66..a5e4ca7b 100644 --- a/packages/apps/http-cache/images/nginx-cache.tag +++ b/packages/apps/http-cache/images/nginx-cache.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:e0a07082bb6fc6aeaae2315f335386f1705a646c72f9e0af512aebbca5cb2b15 +ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:b7633717cd7449c0042ae92d8ca9b36e4d69566561f5c7d44e21058e7d05c6d5 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 6f053bbd..eaf0ace4 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,2 +1,2 @@ cozystack: - image: ghcr.io/cozystack/cozystack/installer:v0.39.0@sha256:8fb2f4f9ab482a00b6200f56bed69fad68a117bb65b341d3beca4bed5bd7b80f + image: ghcr.io/cozystack/cozystack/installer:v0.39.1@sha256:165b5d5d5fdf48aca552db070bf145fbf328e9e523a8d8cbb1b68112fdbd5d5d diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index 7bf63e3a..d896d2aa 100755 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.39.0@sha256:c113cb262e8e8fd87c013a347c16def906e3a3dd179519f2ad6a5c054a44f271 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.39.1@sha256:22fdbc676c17b679e4ea39fd88741fa35c449250f89ef3c75333c1849008eca7 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 20f412c7..404abc9d 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v0.39.0@sha256:ec0d9e2a74e313c76a93f6a0522e31731f33cfc6575d14d93ddfe24f2fe53eec +ghcr.io/cozystack/cozystack/matchbox:v0.39.1@sha256:f3a8a55e5048a08b41a0efd4a36980b2ea508373acf1e5caca7849276cc4f2a6 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index 0a20a5d9..1674f9e6 100644 --- a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag +++ b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.39.0@sha256:d5a52df856002b81cbff9c08c28d600271ccbaf202c36fc01aab7fdaec0ac7c9 +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.39.1@sha256:df9e205a0d063a30da6cff595d9d1951a76d35bf8cad9d21ac66aacef47a1f65 diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index 3ba8a1db..aaa70840 100644 --- a/packages/system/bucket/images/s3manager.tag +++ b/packages/system/bucket/images/s3manager.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:eb363ee78465884ac314a3e5357ff30b0274dbee6ca2b94935b2ee2193870e0f +ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:75760dcae5fc01743328b1f8456fa451f6be2cc2b3f639ee84897a0ee99cdd14 diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 65587d04..97ca88c5 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,5 +1,5 @@ cozystackAPI: - image: ghcr.io/cozystack/cozystack/cozystack-api:v0.39.0@sha256:d9669a49fd22b3f1ed56aa21857f99c63e88426488530092f606ad275f6ce650 + image: ghcr.io/cozystack/cozystack/cozystack-api:v0.39.1@sha256:fa6bd65b041e75bb2f11f0e564dc78060ab12091188e0d7e03161fec68e03014 localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index b8ba4408..342c0889 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,6 +1,6 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.39.0@sha256:af229b1d63705af9eb8bbf6867b4e94412516cd41ecfbc73f7fb19ac705e739a + image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.39.1@sha256:5258fd467f62aa04120a163642694f32aa2e4fc8752980f8b1c50509e301f23d debug: false disableTelemetry: false - cozystackVersion: "v0.39.0" + cozystackVersion: "v0.39.1" cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index e2baa680..7fb1a3cf 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} -{{- $tenantText := "v0.39.0" }} +{{- $tenantText := "v0.39.1" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index 5ce5fdd5..164d8bb9 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.39.0@sha256:ea8b99160f7ab94de418bb445651769b8546b0659ae1f598670d4c581d370f88 + image: ghcr.io/cozystack/cozystack/openapi-ui:v0.39.1@sha256:97aeeabcd8e50a4dfc30e0b849d2dc0361e322e1f2fa716da5daf44e470d20b9 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.39.0@sha256:0a5168e29ba667cc3532ebc6814c7f9c9f1102874a9d8b0c038e9fa2eefb6efd + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.39.1@sha256:0a5168e29ba667cc3532ebc6814c7f9c9f1102874a9d8b0c038e9fa2eefb6efd tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v0.39.0@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b + image: ghcr.io/cozystack/cozystack/token-proxy:v0.39.1@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index b8d00f00..33f2c420 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v0.39.0@sha256:a8981fc592628855693aeeebc3a1f51058a8e667fa2405416b6ed1b1cbe86c09 + tag: v0.39.1@sha256:ab66ab206da1b8b2fe1aa623a4accd8c91d2c5560aa857f949ee9183d79087ad repository: ghcr.io/cozystack/cozystack/kamaji resources: limits: @@ -13,4 +13,4 @@ kamaji: cpu: 100m memory: 100Mi extraArgs: - - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.39.0@sha256:a8981fc592628855693aeeebc3a1f51058a8e667fa2405416b6ed1b1cbe86c09 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.39.1@sha256:ab66ab206da1b8b2fe1aa623a4accd8c91d2c5560aa857f949ee9183d79087ad diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index 3478e16c..875932dd 100644 --- a/packages/system/kubeovn-plunger/values.yaml +++ b/packages/system/kubeovn-plunger/values.yaml @@ -1,4 +1,4 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.39.0@sha256:ce250d9ac04dc251f105f385d204f58ec3c27ced6e1023e52e7e8121b3c99fd5 +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.39.1@sha256:135ceb7289bc0522680fe9002f76ff2dc5feec03cdbc6b3008109aba9093e069 ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 7c3cbf0a..15eb396b 100644 --- a/packages/system/kubeovn-webhook/values.yaml +++ b/packages/system/kubeovn-webhook/values.yaml @@ -1,3 +1,3 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.39.0@sha256:7bfd458299a507f2cf82cddb65941ded6991fd4ba92fd46010cbc8c363126085 +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.39.1@sha256:fc777726120350e5bdf4b864f49826b89983eb07a63c768d4907ce1330b35049 diff --git a/packages/system/kubeovn/values.yaml b/packages/system/kubeovn/values.yaml index 2b7f4f62..3f3841c2 100644 --- a/packages/system/kubeovn/values.yaml +++ b/packages/system/kubeovn/values.yaml @@ -65,4 +65,4 @@ global: images: kubeovn: repository: kubeovn - tag: v1.14.11@sha256:d413fa033b6e2971866f2b27d25d5482f923099afe4b58ceedcfd031b22e410d + tag: v1.14.11@sha256:3ff2a91ed1f9ca7b3cb6281c7685dbb6bdc2108cb0d9ae3bfe74e8126125190a diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index ee39e044..d954b181 100644 --- a/packages/system/lineage-controller-webhook/values.yaml +++ b/packages/system/lineage-controller-webhook/values.yaml @@ -1,5 +1,5 @@ lineageControllerWebhook: - image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.39.0@sha256:cb264b193256dd5db79ba50ad44fc2301fb19ac34155f6f6638d6cadfd45e370 + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.39.1@sha256:58e20b3a403a319b1de3e27a3dea103421517052134dfaa3c13a5d41c5a0458d debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index e7d1e757..7b45a2b0 100644 --- a/packages/system/objectstorage-controller/values.yaml +++ b/packages/system/objectstorage-controller/values.yaml @@ -1,3 +1,3 @@ objectstorage: controller: - image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.39.0@sha256:91ed0ba4de7958708f77427d2c752dece969236c13927c17033d792700e9c6f4" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.39.1@sha256:91ed0ba4de7958708f77427d2c752dece969236c13927c17033d792700e9c6f4" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 6d8d396d..5d85ed54 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -124,7 +124,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.39.0@sha256:d5a52df856002b81cbff9c08c28d600271ccbaf202c36fc01aab7fdaec0ac7c9" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.39.1@sha256:df9e205a0d063a30da6cff595d9d1951a76d35bf8cad9d21ac66aacef47a1f65" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From 1ffa1ef45466f024222d3436f74f95e2b05d491b Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Fri, 19 Dec 2025 14:37:46 +0300 Subject: [PATCH 17/45] [vm] Always expose VMs with a service ## What this PR does When VMs are created without a public IP, no service is created for them and they have no in-cluster DNS name. This PR fixes this and resolves #1731. ### Release note ```release-note [vm] Always expose VMs with at least a ClusterIP service. ``` Signed-off-by: Timofei Larkin (cherry picked from commit ba50d01877736a28c62310d117d93c2f62047fad) --- packages/apps/virtual-machine/templates/service.yaml | 10 +++++++++- packages/apps/vm-instance/templates/service.yaml | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/apps/virtual-machine/templates/service.yaml b/packages/apps/virtual-machine/templates/service.yaml index f212db62..b12f7612 100644 --- a/packages/apps/virtual-machine/templates/service.yaml +++ b/packages/apps/virtual-machine/templates/service.yaml @@ -1,4 +1,3 @@ -{{- if .Values.external }} --- apiVersion: v1 kind: Service @@ -7,17 +6,24 @@ metadata: labels: apps.cozystack.io/user-service: "true" {{- include "virtual-machine.labels" . | nindent 4 }} +{{- if .Values.external }} annotations: networking.cozystack.io/wholeIP: "true" +{{- end }} spec: type: {{ ternary "LoadBalancer" "ClusterIP" .Values.external }} +{{- if .Values.external }} externalTrafficPolicy: Local {{- if ((include "cozy-lib.network.disableLoadBalancerNodePorts" $) | fromYaml) }} allocateLoadBalancerNodePorts: false {{- end }} +{{- else }} + clusterIP: None +{{- end }} selector: {{- include "virtual-machine.selectorLabels" . | nindent 4 }} ports: +{{- if .Values.external }} {{- if and (eq .Values.externalMethod "WholeIP") (not .Values.externalPorts) }} - port: 65535 {{- else }} @@ -27,4 +33,6 @@ spec: targetPort: {{ . }} {{- end }} {{- end }} +{{- else }} + - port: 65535 {{- end }} diff --git a/packages/apps/vm-instance/templates/service.yaml b/packages/apps/vm-instance/templates/service.yaml index f212db62..b12f7612 100644 --- a/packages/apps/vm-instance/templates/service.yaml +++ b/packages/apps/vm-instance/templates/service.yaml @@ -1,4 +1,3 @@ -{{- if .Values.external }} --- apiVersion: v1 kind: Service @@ -7,17 +6,24 @@ metadata: labels: apps.cozystack.io/user-service: "true" {{- include "virtual-machine.labels" . | nindent 4 }} +{{- if .Values.external }} annotations: networking.cozystack.io/wholeIP: "true" +{{- end }} spec: type: {{ ternary "LoadBalancer" "ClusterIP" .Values.external }} +{{- if .Values.external }} externalTrafficPolicy: Local {{- if ((include "cozy-lib.network.disableLoadBalancerNodePorts" $) | fromYaml) }} allocateLoadBalancerNodePorts: false {{- end }} +{{- else }} + clusterIP: None +{{- end }} selector: {{- include "virtual-machine.selectorLabels" . | nindent 4 }} ports: +{{- if .Values.external }} {{- if and (eq .Values.externalMethod "WholeIP") (not .Values.externalPorts) }} - port: 65535 {{- else }} @@ -27,4 +33,6 @@ spec: targetPort: {{ . }} {{- end }} {{- end }} +{{- else }} + - port: 65535 {{- end }} From 13bfb72ea5551d52d28cfd77a52f7f0901591fb8 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 29 Dec 2025 16:06:08 +0300 Subject: [PATCH 18/45] [kubevirt-operator] Fix typo in VMNotRunningFor10Minutes alert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix case sensitivity in status check: "running" → "Running". The metric uses "Running" (capital R), so the lowercase check was always triggering alerts even for running VMs. Fixes: https://github.com/cozystack/cozystack/issues/1552 Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin (cherry picked from commit 7fab919e077275db1a52f6af8f16fc28f7e56de1) --- packages/system/kubevirt-operator/alerts/PrometheusRule.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml b/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml index 73ef3ce4..c4d73cc5 100644 --- a/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml +++ b/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml @@ -10,7 +10,7 @@ spec: expr: | max_over_time( kubevirt_vm_info{ - status!="running", + status!="Running", exported_namespace=~".+", name=~".+" }[10m] From d90a76bc25b5828d67a429f142122467144b0ed3 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 26 Dec 2025 17:44:23 +0300 Subject: [PATCH 19/45] fix(tenant): allow egress to parent ingress pods Nested Kubernetes clusters with exposeMethod: Proxied cannot reach their own external domains because the clusterwide egress policy blocks traffic to ingress pods in parent namespaces. This breaks cert-manager HTTP-01 self-check and any scenario where pods need to access services exposed through parent ingress. Add egress rule allowing traffic to ingress pods (cozystack.io/service: ingress) in parent namespaces, following the same pattern as existing vminsert and etcd rules. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin (cherry picked from commit 4dfdbfeb62c76facbb4a9f94e24e129f35f854db) --- packages/apps/tenant/templates/networkpolicy.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/apps/tenant/templates/networkpolicy.yaml b/packages/apps/tenant/templates/networkpolicy.yaml index b66e85ff..2c15a877 100644 --- a/packages/apps/tenant/templates/networkpolicy.yaml +++ b/packages/apps/tenant/templates/networkpolicy.yaml @@ -67,6 +67,19 @@ spec: {{- end }} {{- end }} {{- end }} + {{- if ne (include "tenant.name" .) "tenant-root" }} + - toEndpoints: + {{- if hasPrefix "tenant-" .Release.Namespace }} + {{- $parts := splitList "-" .Release.Namespace }} + {{- range $i, $v := $parts }} + {{- if ne $i 0 }} + - matchLabels: + cozystack.io/service: ingress + "k8s:io.kubernetes.pod.namespace": {{ join "-" (slice $parts 0 (add $i 1)) }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} --- apiVersion: cilium.io/v2 kind: CiliumClusterwideNetworkPolicy From 7c766fc27dafea3bcbde4de1493cac263ca610a7 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 30 Dec 2025 11:38:49 +0300 Subject: [PATCH 20/45] [tenant] Run cleanup job from system namespace ## What this PR does The Helm hook that creates a job deleting all applications in a tenant before deleting the tenant itself now runs this job from the cozy-system namespace. This prevents conflicts with resource quotas (not enough resources to run cleanup job) without temporary increases of the quota or similar vulnerability-introducing hacks. ### Release note ```release-note [tenant] Run cleanup job in system namespace to avoid conflicts on resource quotas. ``` Signed-off-by: Timofei Larkin (cherry picked from commit b94fc6c9c84cc73e5f53341a24bbdfe0f2fb0f62) --- packages/apps/tenant/templates/cleanup-job.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/apps/tenant/templates/cleanup-job.yaml b/packages/apps/tenant/templates/cleanup-job.yaml index 9a1bcdd5..9b2f2c50 100644 --- a/packages/apps/tenant/templates/cleanup-job.yaml +++ b/packages/apps/tenant/templates/cleanup-job.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "tenant.name" . }}-cleanup - namespace: {{ include "tenant.name" . }} + namespace: cozy-system annotations: helm.sh/hook: pre-delete helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded @@ -39,13 +39,13 @@ roleRef: subjects: - kind: ServiceAccount name: {{ include "tenant.name" . }}-cleanup - namespace: {{ include "tenant.name" . }} + namespace: cozy-system --- apiVersion: batch/v1 kind: Job metadata: name: {{ include "tenant.name" . }}-cleanup - namespace: {{ include "tenant.name" . }} + namespace: cozy-system annotations: helm.sh/hook: pre-delete helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded From fc84b80b704e67507293d3387a75b49b3cf636d2 Mon Sep 17 00:00:00 2001 From: Matthieu ROBIN Date: Fri, 2 Jan 2026 15:53:54 +0100 Subject: [PATCH 21/45] Add resource requests and limits to etcd-defrag Added resource requests and limits for etcd-defrag container. Signed-off-by: Matthieu ROBIN (cherry picked from commit 675eaa6178df66161a68a487a1596cbf7eef91fc) --- packages/extra/etcd/templates/etcd-defrag.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/extra/etcd/templates/etcd-defrag.yaml b/packages/extra/etcd/templates/etcd-defrag.yaml index 21a8e514..089df920 100644 --- a/packages/extra/etcd/templates/etcd-defrag.yaml +++ b/packages/extra/etcd/templates/etcd-defrag.yaml @@ -12,6 +12,13 @@ spec: containers: - name: etcd-defrag image: ghcr.io/ahrtr/etcd-defrag:v0.13.0 + resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi args: - --endpoints={{ range $i, $e := until (int .Values.replicas) }}{{ if $i }},{{ end }}https://{{ $.Release.Name }}-{{ $i }}.{{ $.Release.Name }}-headless.{{ $.Release.Namespace }}.svc:2379{{ end }} - --cacert=/etc/etcd/pki/client/cert/ca.crt From f4ff44a18cecfac008d5515f1d8afe487af2bba4 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:38:23 +0000 Subject: [PATCH 22/45] Prepare release v0.39.2 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- packages/apps/kubernetes/images/kubevirt-csi-driver.tag | 2 +- packages/core/installer/values.yaml | 2 +- packages/core/testing/values.yaml | 2 +- packages/extra/bootbox/images/matchbox.tag | 2 +- packages/extra/seaweedfs/images/objectstorage-sidecar.tag | 2 +- packages/system/bucket/images/s3manager.tag | 2 +- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 4 ++-- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/values.yaml | 2 +- packages/system/kubeovn/values.yaml | 2 +- packages/system/kubevirt-csi-node/values.yaml | 2 +- packages/system/lineage-controller-webhook/values.yaml | 2 +- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 18 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index 550279b8..0e97fa7a 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:64c0b248e0d6b0a1578f59cab725152f3223a6d53ac0371eccaf023c0cd75904 +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:ae0c5e3e005388d865617097a9fe4b4e8a2be6b62cec893e08f2143d0acde241 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index eaf0ace4..9de2bcff 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,2 +1,2 @@ cozystack: - image: ghcr.io/cozystack/cozystack/installer:v0.39.1@sha256:165b5d5d5fdf48aca552db070bf145fbf328e9e523a8d8cbb1b68112fdbd5d5d + image: ghcr.io/cozystack/cozystack/installer:v0.39.2@sha256:1c0eef43e1a06f5bf653c0bb7a2821a623fcad71c41c2c15b080fe0f6c809e39 diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index d896d2aa..e6bd42c8 100755 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.39.1@sha256:22fdbc676c17b679e4ea39fd88741fa35c449250f89ef3c75333c1849008eca7 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.39.2@sha256:9fe32b5faf233bdbae27859f1c6aaa229230b0ac9ee5212a1ddf2763182cea0f diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 404abc9d..9a13bbb8 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v0.39.1@sha256:f3a8a55e5048a08b41a0efd4a36980b2ea508373acf1e5caca7849276cc4f2a6 +ghcr.io/cozystack/cozystack/matchbox:v0.39.2@sha256:80da0287d6705ffaec918d1d7dc02f1d0a512045c56b78afb2707743d09e629e diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index 1674f9e6..1f75983e 100644 --- a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag +++ b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.39.1@sha256:df9e205a0d063a30da6cff595d9d1951a76d35bf8cad9d21ac66aacef47a1f65 +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.39.2@sha256:b180a6c4ce4d46cdf31af2731cc8b477e80ade99d4296d507ec5edb38c56a1ea diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index aaa70840..641bb877 100644 --- a/packages/system/bucket/images/s3manager.tag +++ b/packages/system/bucket/images/s3manager.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:75760dcae5fc01743328b1f8456fa451f6be2cc2b3f639ee84897a0ee99cdd14 +ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:7dde8d748442c4457fb7bf67c0e0f4abf0da60af00a9171c6d4c0bfcc8a8b9c5 diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 97ca88c5..e61ab611 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,5 +1,5 @@ cozystackAPI: - image: ghcr.io/cozystack/cozystack/cozystack-api:v0.39.1@sha256:fa6bd65b041e75bb2f11f0e564dc78060ab12091188e0d7e03161fec68e03014 + image: ghcr.io/cozystack/cozystack/cozystack-api:v0.39.2@sha256:e859dc81b4905fa163b75b1d567e91bd1eac572f85931954c2dc646cb07d3959 localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 342c0889..41cda67a 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,6 +1,6 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.39.1@sha256:5258fd467f62aa04120a163642694f32aa2e4fc8752980f8b1c50509e301f23d + image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.39.2@sha256:b96283a1774f23735849802069a907cb85f4fd57df89f8740a954a7d2922afc3 debug: false disableTelemetry: false - cozystackVersion: "v0.39.1" + cozystackVersion: "v0.39.2" cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index 7fb1a3cf..c74d63bf 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} -{{- $tenantText := "v0.39.1" }} +{{- $tenantText := "v0.39.2" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index 164d8bb9..237dfb45 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.39.1@sha256:97aeeabcd8e50a4dfc30e0b849d2dc0361e322e1f2fa716da5daf44e470d20b9 + image: ghcr.io/cozystack/cozystack/openapi-ui:v0.39.2@sha256:fddab274cd6e0c590aed0a49faba584c2b34fe875c7deaab9c96678c4e33cd3d openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.39.1@sha256:0a5168e29ba667cc3532ebc6814c7f9c9f1102874a9d8b0c038e9fa2eefb6efd + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.39.2@sha256:70b68d8666220b0b56dbdbda0297d85cf53c5c4fa225a24a8f36afad1ac65a70 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v0.39.1@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b + image: ghcr.io/cozystack/cozystack/token-proxy:v0.39.2@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index 33f2c420..1b010243 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v0.39.1@sha256:ab66ab206da1b8b2fe1aa623a4accd8c91d2c5560aa857f949ee9183d79087ad + tag: v0.39.2@sha256:dbf879e70fc4a98070980be8d086fb79dc6a5b3ae4c1a1cc7578df5c39d8bd36 repository: ghcr.io/cozystack/cozystack/kamaji resources: limits: @@ -13,4 +13,4 @@ kamaji: cpu: 100m memory: 100Mi extraArgs: - - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.39.1@sha256:ab66ab206da1b8b2fe1aa623a4accd8c91d2c5560aa857f949ee9183d79087ad + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.39.2@sha256:dbf879e70fc4a98070980be8d086fb79dc6a5b3ae4c1a1cc7578df5c39d8bd36 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index 875932dd..df21d7a8 100644 --- a/packages/system/kubeovn-plunger/values.yaml +++ b/packages/system/kubeovn-plunger/values.yaml @@ -1,4 +1,4 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.39.1@sha256:135ceb7289bc0522680fe9002f76ff2dc5feec03cdbc6b3008109aba9093e069 +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.39.2@sha256:72bf70c59caa0a196936867297fac38822db09e40742c0762abbed31ab6237b6 ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 15eb396b..13437ce0 100644 --- a/packages/system/kubeovn-webhook/values.yaml +++ b/packages/system/kubeovn-webhook/values.yaml @@ -1,3 +1,3 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.39.1@sha256:fc777726120350e5bdf4b864f49826b89983eb07a63c768d4907ce1330b35049 +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.39.2@sha256:d5023bbfe524ec97bc499d853a3fe67affa9b37dd849eac521f38c651d50382c diff --git a/packages/system/kubeovn/values.yaml b/packages/system/kubeovn/values.yaml index 3f3841c2..eb33c3d3 100644 --- a/packages/system/kubeovn/values.yaml +++ b/packages/system/kubeovn/values.yaml @@ -65,4 +65,4 @@ global: images: kubeovn: repository: kubeovn - tag: v1.14.11@sha256:3ff2a91ed1f9ca7b3cb6281c7685dbb6bdc2108cb0d9ae3bfe74e8126125190a + tag: v1.14.11@sha256:b7ab6447a8ba692942596bc3b0f0f95b446a97546c4d15af0ad82a17cb7622a0 diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index 2de432c4..101d6d73 100644 --- a/packages/system/kubevirt-csi-node/values.yaml +++ b/packages/system/kubevirt-csi-node/values.yaml @@ -1,3 +1,3 @@ storageClass: replicated csiDriver: - image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:64c0b248e0d6b0a1578f59cab725152f3223a6d53ac0371eccaf023c0cd75904 + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:ae0c5e3e005388d865617097a9fe4b4e8a2be6b62cec893e08f2143d0acde241 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index d954b181..29da9b95 100644 --- a/packages/system/lineage-controller-webhook/values.yaml +++ b/packages/system/lineage-controller-webhook/values.yaml @@ -1,5 +1,5 @@ lineageControllerWebhook: - image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.39.1@sha256:58e20b3a403a319b1de3e27a3dea103421517052134dfaa3c13a5d41c5a0458d + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.39.2@sha256:eb1186a56be49a9e6d65dad7b95febc6446b555d1d2dadf337e9c4a75c43193b debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index 7b45a2b0..91b25cc2 100644 --- a/packages/system/objectstorage-controller/values.yaml +++ b/packages/system/objectstorage-controller/values.yaml @@ -1,3 +1,3 @@ objectstorage: controller: - image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.39.1@sha256:91ed0ba4de7958708f77427d2c752dece969236c13927c17033d792700e9c6f4" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.39.2@sha256:494394c28f0487e5382cabe88e77b83ce20235c0fe0b1d14cb81ecc4c4446c49" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 5d85ed54..569493c3 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -124,7 +124,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.39.1@sha256:df9e205a0d063a30da6cff595d9d1951a76d35bf8cad9d21ac66aacef47a1f65" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.39.2@sha256:b180a6c4ce4d46cdf31af2731cc8b477e80ade99d4296d507ec5edb38c56a1ea" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From 591451537133c5bbb0c08700eb3938b44560bb89 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 5 Jan 2026 17:35:56 +0300 Subject: [PATCH 23/45] [kubevirt-operator] Revert incorrect case change in VM alerts Revert PR #1770 which incorrectly changed status check from lowercase to uppercase. The actual metrics use lowercase: - kubevirt_vm_info uses status="running" (not "Running") - kubevirt_vmi_info uses phase="running" (not "Running") Verified by querying virt-controller metrics in instories cluster. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin (cherry picked from commit 36836fd84e77a1234b21a7d86df665314490f90a) --- packages/system/kubevirt-operator/alerts/PrometheusRule.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml b/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml index c4d73cc5..72fabfa0 100644 --- a/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml +++ b/packages/system/kubevirt-operator/alerts/PrometheusRule.yaml @@ -10,7 +10,7 @@ spec: expr: | max_over_time( kubevirt_vm_info{ - status!="Running", + status!="running", exported_namespace=~".+", name=~".+" }[10m] @@ -27,7 +27,7 @@ spec: expr: | max_over_time( kubevirt_vmi_info{ - phase!="Running", + phase!="running", exported_namespace=~".+", name=~".+" }[10m] From ad3bff66929ffb5b8187a2e93f8d18554a838f9c Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Fri, 5 Dec 2025 23:33:50 +0400 Subject: [PATCH 24/45] [kubeovn] Package from external repo (#1535) Previously, all system charts have had their versions bumped with every update of Cozystack, even if the underlying images and templates did not change. Additionally, some components would have their docker images rebuilt and get updated digests because of a changed docker context, even though no relevant code had been changed. This would cause an upgrade of several HelmReleases on every upgrade of Cozystack, even when in practice nothing had changed. This is especially noticeable for charts, such as kubeovn, which is a dependency for almost all other system HelmReleases. By moving kubeovn out of the monorepo and decoupling its release cycle from the release cycle of Cozystack, this issue can be resolved and is another step towards decomposing Cozystack. ```release-note [kubeovn] Decouple the release cycle of kubeovn from Cozystack's release cycle, preventing unnecessary redeployments of all Helm releases, when upgrading Cozystack. ``` --- Makefile | 1 - packages/system/Makefile | 4 +- packages/system/kubeovn/Chart.yaml | 2 +- packages/system/kubeovn/Makefile | 31 +---- .../system/kubeovn/images/kubeovn/Dockerfile | 47 -------- .../images/kubeovn/patches/northd_probe.diff | 109 ------------------ .../system/kubeovn/patches/cozyconfig.diff | 103 ----------------- packages/system/kubeovn/patches/mtu.diff | 14 --- packages/system/kubeovn/values.yaml | 2 +- 9 files changed, 10 insertions(+), 303 deletions(-) delete mode 100644 packages/system/kubeovn/images/kubeovn/Dockerfile delete mode 100644 packages/system/kubeovn/images/kubeovn/patches/northd_probe.diff delete mode 100644 packages/system/kubeovn/patches/cozyconfig.diff delete mode 100644 packages/system/kubeovn/patches/mtu.diff diff --git a/Makefile b/Makefile index 2038ff9f..31a947c4 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,6 @@ build: build-deps make -C packages/system/cozystack-controller image make -C packages/system/lineage-controller-webhook image make -C packages/system/cilium image - make -C packages/system/kubeovn image make -C packages/system/kubeovn-webhook image make -C packages/system/kubeovn-plunger image make -C packages/system/dashboard image diff --git a/packages/system/Makefile b/packages/system/Makefile index 2104647b..e068be8a 100644 --- a/packages/system/Makefile +++ b/packages/system/Makefile @@ -1,11 +1,13 @@ OUT=../../_out/repos/system -CHARTS := $(shell find . -maxdepth 2 -name Chart.yaml | awk -F/ '{print $$2}') +CHARTS := $(shell grep -F 'version: 0.0.0' */Chart.yaml | cut -f1 -d/) +VERSIONED_CHARTS := $(shell grep '^version:' */Chart.yaml | grep -Fv '0.0.0' | cut -f1 -d/) include ../../scripts/common-envs.mk repo: rm -rf "$(OUT)" helm package -d "$(OUT)" $(CHARTS) --version $(COZYSTACK_VERSION) + helm package -d "$(OUT)" $(VERSIONED_CHARTS) cd "$(OUT)" && helm repo index . fix-charts: diff --git a/packages/system/kubeovn/Chart.yaml b/packages/system/kubeovn/Chart.yaml index 16cb9dc5..d1532794 100644 --- a/packages/system/kubeovn/Chart.yaml +++ b/packages/system/kubeovn/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: v2 name: cozy-kubeovn -version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process +version: 0.39.0 diff --git a/packages/system/kubeovn/Makefile b/packages/system/kubeovn/Makefile index 15373d2f..a4a0d1ed 100644 --- a/packages/system/kubeovn/Makefile +++ b/packages/system/kubeovn/Makefile @@ -1,4 +1,4 @@ -KUBEOVN_TAG=$(shell awk '$$1 == "version:" {print $$2}' charts/kube-ovn/Chart.yaml) +KUBEOVN_TAG=v0.39.0 export NAME=kubeovn export NAMESPACE=cozy-$(NAME) @@ -7,28 +7,7 @@ include ../../../scripts/common-envs.mk include ../../../scripts/package.mk update: - rm -rf charts && mkdir -p charts/kube-ovn - tag=$$(git ls-remote --tags --sort="v:refname" https://github.com/kubeovn/kube-ovn | awk -F'[/^]' '{print $$3}' | grep '^v1\.14\.' | tail -n1 ) && \ - curl -sSL https://github.com/kubeovn/kube-ovn/archive/refs/tags/$${tag}.tar.gz | \ - tar xzvf - --strip 1 kube-ovn-$${tag#*v}/charts/kube-ovn - patch --no-backup-if-mismatch -p4 < patches/cozyconfig.diff - patch --no-backup-if-mismatch -p4 < patches/mtu.diff - version=$$(awk '$$1 == "version:" {print $$2}' charts/kube-ovn/Chart.yaml) && \ - sed -i "s/ARG VERSION=.*/ARG VERSION=$${version}/" images/kubeovn/Dockerfile && \ - sed -i "s/ARG TAG=.*/ARG TAG=$${version}/" images/kubeovn/Dockerfile - -image: - docker buildx build images/kubeovn \ - --tag $(REGISTRY)/kubeovn:$(call settag,$(KUBEOVN_TAG)) \ - --tag $(REGISTRY)/kubeovn:$(call settag,$(KUBEOVN_TAG)-$(TAG)) \ - --cache-from type=registry,ref=$(REGISTRY)/kubeovn:latest \ - --cache-to type=inline \ - --metadata-file images/kubeovn.json \ - $(BUILDX_ARGS) - REGISTRY="$(REGISTRY)" \ - yq -i '.global.registry.address = strenv(REGISTRY)' values.yaml - REPOSITORY="kubeovn" \ - yq -i '.global.images.kubeovn.repository = strenv(REPOSITORY)' values.yaml - TAG="$(call settag,$(KUBEOVN_TAG))@$$(yq e '."containerimage.digest"' images/kubeovn.json -o json -r)" \ - yq -i '.global.images.kubeovn.tag = strenv(TAG)' values.yaml - rm -f images/kubeovn.json + rm -rf charts values.yaml Chart.yaml + tag=$(KUBEOVN_TAG) && \ + curl -sSL https://github.com/cozystack/kubeovn/archive/refs/tags/$${tag}.tar.gz | \ + tar xzvf - --strip 2 kubeovn-$${tag#*v}/chart diff --git a/packages/system/kubeovn/images/kubeovn/Dockerfile b/packages/system/kubeovn/images/kubeovn/Dockerfile deleted file mode 100644 index 5ea19c93..00000000 --- a/packages/system/kubeovn/images/kubeovn/Dockerfile +++ /dev/null @@ -1,47 +0,0 @@ -# syntax = docker/dockerfile:experimental -ARG VERSION=v1.14.11 -ARG BASE_TAG=$VERSION - -FROM golang:1.25-bookworm as builder - -ARG TAG=v1.14.11 -RUN git clone --branch ${TAG} --depth 1 https://github.com/kubeovn/kube-ovn /source - -WORKDIR /source - -COPY patches /patches -RUN git apply /patches/*.diff -RUN make build-go - -WORKDIR /source/dist/images - -# imported from https://github.com/kubeovn/kube-ovn/blob/master/dist/images/Dockerfile -FROM kubeovn/kube-ovn-base:$BASE_TAG AS setcap - -COPY --from=builder /source/dist/images/*.sh /kube-ovn/ -COPY --from=builder /source/dist/images/kubectl-ko /kube-ovn/kubectl-ko -COPY --from=builder /source/dist/images/01-kube-ovn.conflist /kube-ovn/01-kube-ovn.conflist - -COPY --from=builder /source/dist/images/kube-ovn /kube-ovn/kube-ovn -COPY --from=builder /source/dist/images/kube-ovn-cmd /kube-ovn/kube-ovn-cmd -COPY --from=builder /source/dist/images/kube-ovn-daemon /kube-ovn/kube-ovn-daemon -COPY --from=builder /source/dist/images/kube-ovn-controller /kube-ovn/kube-ovn-controller -RUN ln -s /kube-ovn/kube-ovn-cmd /kube-ovn/kube-ovn-monitor && \ - ln -s /kube-ovn/kube-ovn-cmd /kube-ovn/kube-ovn-speaker && \ - ln -s /kube-ovn/kube-ovn-cmd /kube-ovn/kube-ovn-webhook && \ - ln -s /kube-ovn/kube-ovn-cmd /kube-ovn/kube-ovn-leader-checker && \ - ln -s /kube-ovn/kube-ovn-cmd /kube-ovn/kube-ovn-ic-controller && \ - ln -s /kube-ovn/kube-ovn-controller /kube-ovn/kube-ovn-pinger && \ - setcap CAP_NET_BIND_SERVICE+eip /kube-ovn/kube-ovn-cmd && \ - setcap CAP_NET_RAW,CAP_NET_BIND_SERVICE+eip /kube-ovn/kube-ovn-controller && \ - setcap CAP_NET_ADMIN,CAP_NET_RAW,CAP_NET_BIND_SERVICE,CAP_SYS_ADMIN+eip /kube-ovn/kube-ovn-daemon - -FROM kubeovn/kube-ovn-base:$BASE_TAG - -COPY --chmod=0644 --from=builder /source/dist/images/logrotate/* /etc/logrotate.d/ -COPY --from=builder /source/dist/images/grace_stop_ovn_controller /usr/share/ovn/scripts/grace_stop_ovn_controller - -COPY --from=setcap /kube-ovn /kube-ovn -RUN /kube-ovn/iptables-wrapper-installer.sh --no-sanity-check - -WORKDIR /kube-ovn diff --git a/packages/system/kubeovn/images/kubeovn/patches/northd_probe.diff b/packages/system/kubeovn/images/kubeovn/patches/northd_probe.diff deleted file mode 100644 index 93edd879..00000000 --- a/packages/system/kubeovn/images/kubeovn/patches/northd_probe.diff +++ /dev/null @@ -1,109 +0,0 @@ -diff --git a/pkg/ovn_leader_checker/ovn.go b/pkg/ovn_leader_checker/ovn.go -index 0f86a371d..8ddf7bca6 100755 ---- a/pkg/ovn_leader_checker/ovn.go -+++ b/pkg/ovn_leader_checker/ovn.go -@@ -10,6 +10,7 @@ import ( - "os/exec" - "strconv" - "strings" -+ "sync" - "syscall" - "time" - -@@ -271,19 +272,56 @@ func checkNorthdSvcExist(cfg *Configuration, namespace, svcName string) bool { - return true - } - --func checkNorthdEpAvailable(ip string) bool { -- address := net.JoinHostPort(ip, OvnNorthdPort) -- conn, err := net.DialTimeout("tcp", address, 3*time.Second) -- if err != nil { -- klog.Errorf("failed to connect to northd leader %s, err: %v", ip, err) -- failCount++ -- if failCount >= MaxFailCount { -- return false -- } -- } else { -+func checkNorthdEpAvailable(ips ...string) bool { -+ var wg sync.WaitGroup -+ success := make(chan struct{}, 1) -+ failure := make(chan struct{}) -+ ctx, cancel := context.WithCancel(context.Background()) -+ defer cancel() -+ d := net.Dialer{Timeout: 3 * time.Second} -+ for _, ip := range ips { -+ address := net.JoinHostPort(ip, OvnNorthdPort) -+ wg.Add(1) -+ go func() { -+ defer wg.Done() -+ conn, err := d.DialContext(ctx, "tcp", address) -+ if err != nil { -+ klog.Errorf("failed to connect to northd leader %s, err: %v", ip, err) -+ return -+ } else { -+ defer conn.Close() -+ select { -+ case success <- struct{}{}: -+ klog.V(5).Infof("succeed to connect to northd leader %s", ip) -+ cancel() -+ default: -+ // someone else already succeeded -+ } -+ } -+ }() -+ } -+ go func() { -+ wg.Wait() -+ close(failure) -+ }() -+ select { -+ case <-success: - failCount = 0 -- klog.V(5).Infof("succeed to connect to northd leader %s", ip) -- _ = conn.Close() -+ return true -+ case <-failure: -+ // if the last groroutine is the one to succeed, -+ // there's a small chance that failure is selected, -+ // this is a guard for this -+ select { -+ case <-success: -+ failCount = 0 -+ return true -+ default: -+ failCount++ -+ if failCount >= MaxFailCount { -+ return false -+ } -+ } - } - return true - } -@@ -295,6 +333,8 @@ func checkNorthdEpAlive(cfg *Configuration, namespace, service string) bool { - return false - } - -+ addresses := []string{} -+ - for _, eps := range epsList.Items { - for _, ep := range eps.Endpoints { - if (ep.Conditions.Ready != nil && !*ep.Conditions.Ready) || len(ep.Addresses) == 0 { -@@ -303,12 +343,15 @@ func checkNorthdEpAlive(cfg *Configuration, namespace, service string) bool { - - // Found an address, check its availability. We only need one. - klog.V(5).Infof("found address %s in endpoint slice %s/%s for service %s, checking availability", ep.Addresses[0], eps.Namespace, eps.Name, service) -- return checkNorthdEpAvailable(ep.Addresses[0]) -+ addresses = append(addresses, ep.Addresses[0]) // Addresses are fungible by k8s API design - } - } - -- klog.V(5).Infof("no address found in any endpoint slices for service %s/%s", namespace, service) -- return false -+ if len(addresses) == 0 { -+ klog.V(5).Infof("no address found in any endpoint slices for service %s/%s", namespace, service) -+ return false -+ } -+ return checkNorthdEpAvailable(addresses...) - } - - func compactOvnDatabase(db string) { diff --git a/packages/system/kubeovn/patches/cozyconfig.diff b/packages/system/kubeovn/patches/cozyconfig.diff deleted file mode 100644 index f7a683f7..00000000 --- a/packages/system/kubeovn/patches/cozyconfig.diff +++ /dev/null @@ -1,103 +0,0 @@ - -diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml -index d9a9a67..b2e12dd 100644 ---- a/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml -+++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml -@@ -51,18 +51,15 @@ spec: - - bash - - /kube-ovn/start-cniserver.sh - args: -+ {{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} - - --enable-mirror={{- .Values.debug.ENABLE_MIRROR }} - - --mirror-iface={{- .Values.debug.MIRROR_IFACE }} - - --node-switch={{ .Values.networking.NODE_SUBNET }} - - --encap-checksum=true -- - --service-cluster-ip-range= -- {{- if eq .Values.networking.NET_STACK "dual_stack" -}} -- {{ .Values.dual_stack.SVC_CIDR }} -- {{- else if eq .Values.networking.NET_STACK "ipv4" -}} -- {{ .Values.ipv4.SVC_CIDR }} -- {{- else if eq .Values.networking.NET_STACK "ipv6" -}} -- {{ .Values.ipv6.SVC_CIDR }} -- {{- end }} -+ - --service-cluster-ip-range={{ index $cozyConfig.data "ipv4-svc-cidr" }} -+ {{- if .Values.global.logVerbosity }} -+ - --v={{ .Values.global.logVerbosity }} -+ {{- end }} - {{- if eq .Values.networking.NETWORK_TYPE "vlan" }} - - --iface= - {{- else}} -diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml -index 0e69494..756eb7c 100644 ---- a/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml -+++ b/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml -@@ -52,46 +52,22 @@ spec: - image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - args: -+ {{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} - - /kube-ovn/start-controller.sh - - --default-ls={{ .Values.networking.DEFAULT_SUBNET }} -- - --default-cidr= -- {{- if eq .Values.networking.NET_STACK "dual_stack" -}} -- {{ .Values.dual_stack.POD_CIDR }} -- {{- else if eq .Values.networking.NET_STACK "ipv4" -}} -- {{ .Values.ipv4.POD_CIDR }} -- {{- else if eq .Values.networking.NET_STACK "ipv6" -}} -- {{ .Values.ipv6.POD_CIDR }} -- {{- end }} -- - --default-gateway= -- {{- if eq .Values.networking.NET_STACK "dual_stack" -}} -- {{ .Values.dual_stack.POD_GATEWAY }} -- {{- else if eq .Values.networking.NET_STACK "ipv4" -}} -- {{ .Values.ipv4.POD_GATEWAY }} -- {{- else if eq .Values.networking.NET_STACK "ipv6" -}} -- {{ .Values.ipv6.POD_GATEWAY }} -- {{- end }} -+ - --default-cidr={{ index $cozyConfig.data "ipv4-pod-cidr" }} -+ - --default-gateway={{ index $cozyConfig.data "ipv4-pod-gateway" }} - - --default-gateway-check={{- .Values.func.CHECK_GATEWAY }} - - --default-logical-gateway={{- .Values.func.LOGICAL_GATEWAY }} - - --default-u2o-interconnection={{- .Values.func.U2O_INTERCONNECTION }} - - --default-exclude-ips={{- .Values.networking.EXCLUDE_IPS }} - - --cluster-router={{ .Values.networking.DEFAULT_VPC }} - - --node-switch={{ .Values.networking.NODE_SUBNET }} -- - --node-switch-cidr= -- {{- if eq .Values.networking.NET_STACK "dual_stack" -}} -- {{ .Values.dual_stack.JOIN_CIDR }} -- {{- else if eq .Values.networking.NET_STACK "ipv4" -}} -- {{ .Values.ipv4.JOIN_CIDR }} -- {{- else if eq .Values.networking.NET_STACK "ipv6" -}} -- {{ .Values.ipv6.JOIN_CIDR }} -- {{- end }} -- - --service-cluster-ip-range= -- {{- if eq .Values.networking.NET_STACK "dual_stack" -}} -- {{ .Values.dual_stack.SVC_CIDR }} -- {{- else if eq .Values.networking.NET_STACK "ipv4" -}} -- {{ .Values.ipv4.SVC_CIDR }} -- {{- else if eq .Values.networking.NET_STACK "ipv6" -}} -- {{ .Values.ipv6.SVC_CIDR }} -- {{- end }} -+ - --node-switch-cidr={{ index $cozyConfig.data "ipv4-join-cidr" }} -+ - --service-cluster-ip-range={{ index $cozyConfig.data "ipv4-svc-cidr" }} -+ {{- if .Values.global.logVerbosity }} -+ - --v={{ .Values.global.logVerbosity }} -+ {{- end }} - - --network-type={{- .Values.networking.NETWORK_TYPE }} - - --default-provider-name={{ .Values.networking.vlan.PROVIDER_NAME }} - - --default-interface-name={{- .Values.networking.vlan.VLAN_INTERFACE_NAME }} -diff --git a/packages/system/kubeovn/charts/kube-ovn/values.yaml b/packages/system/kubeovn/charts/kube-ovn/values.yaml -index bfffc4d..b880749 100644 ---- a/packages/system/kubeovn/charts/kube-ovn/values.yaml -+++ b/packages/system/kubeovn/charts/kube-ovn/values.yaml -@@ -70,10 +70,6 @@ func: - ENABLE_TPROXY: false - - ipv4: -- POD_CIDR: "10.16.0.0/16" -- POD_GATEWAY: "10.16.0.1" -- SVC_CIDR: "10.96.0.0/12" -- JOIN_CIDR: "100.64.0.0/16" - PINGER_EXTERNAL_ADDRESS: "1.1.1.1" - PINGER_EXTERNAL_DOMAIN: "alauda.cn." - diff --git a/packages/system/kubeovn/patches/mtu.diff b/packages/system/kubeovn/patches/mtu.diff deleted file mode 100644 index da3de3aa..00000000 --- a/packages/system/kubeovn/patches/mtu.diff +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml -index 63f4258..dafe1fd 100644 ---- a/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml -+++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml -@@ -112,6 +112,9 @@ spec: - - --secure-serving={{- .Values.func.SECURE_SERVING }} - - --enable-ovn-ipsec={{- .Values.func.ENABLE_OVN_IPSEC }} - - --set-vxlan-tx-off={{- .Values.func.SET_VXLAN_TX_OFF }} -+ {{- with .Values.mtu }} -+ - --mtu={{ . }} -+ {{- end }} - securityContext: - runAsUser: 0 - privileged: false diff --git a/packages/system/kubeovn/values.yaml b/packages/system/kubeovn/values.yaml index eb33c3d3..89f20e7c 100644 --- a/packages/system/kubeovn/values.yaml +++ b/packages/system/kubeovn/values.yaml @@ -65,4 +65,4 @@ global: images: kubeovn: repository: kubeovn - tag: v1.14.11@sha256:b7ab6447a8ba692942596bc3b0f0f95b446a97546c4d15af0ad82a17cb7622a0 + tag: v1.14.11@sha256:0e3e9db960a9600d58c33c0787cabd0e9bf263930fd8c9fe65417e258c383d01 From 143da399ff041bbc1b97c48754561a2e6a34f694 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 16 Dec 2025 10:50:19 +0100 Subject: [PATCH 25/45] [kubernetes] Fix endpoints for cilium-gateway Signed-off-by: Andrei Kvapil (cherry picked from commit f59665208c2d0d7847150354a5ea6cdd7c291913) --- .../patches/{354.diff => 379.diff} | 83 ++++++++++++++++++- 1 file changed, 80 insertions(+), 3 deletions(-) rename packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/{354.diff => 379.diff} (63%) diff --git a/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/354.diff b/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/379.diff similarity index 63% rename from packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/354.diff rename to packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/379.diff index 3410ea93..ad1ec9c5 100644 --- a/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/354.diff +++ b/packages/apps/kubernetes/images/kubevirt-cloud-provider/patches/379.diff @@ -1,5 +1,5 @@ diff --git a/pkg/controller/kubevirteps/kubevirteps_controller.go b/pkg/controller/kubevirteps/kubevirteps_controller.go -index 53388eb8e..28644236f 100644 +index 53388eb8e..873060251 100644 --- a/pkg/controller/kubevirteps/kubevirteps_controller.go +++ b/pkg/controller/kubevirteps/kubevirteps_controller.go @@ -12,7 +12,6 @@ import ( @@ -10,12 +10,17 @@ index 53388eb8e..28644236f 100644 "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" -@@ -669,35 +668,50 @@ func (c *Controller) getDesiredEndpoints(service *v1.Service, tenantSlices []*di +@@ -666,38 +665,62 @@ func (c *Controller) getDesiredEndpoints(service *v1.Service, tenantSlices []*di + // for extracting the nodes it does not matter what type of address we are dealing with + // all nodes with an endpoint for a corresponding slice will be selected. + nodeSet := sets.Set[string]{} ++ hasEndpointsWithoutNodeName := false for _, slice := range tenantSlices { for _, endpoint := range slice.Endpoints { // find all unique nodes that correspond to an endpoint in a tenant slice + if endpoint.NodeName == nil { + klog.Warningf("Skipping endpoint without NodeName in slice %s/%s", slice.Namespace, slice.Name) ++ hasEndpointsWithoutNodeName = true + continue + } nodeSet.Insert(*endpoint.NodeName) @@ -23,6 +28,13 @@ index 53388eb8e..28644236f 100644 } - klog.Infof("Desired nodes for service %s in namespace %s: %v", service.Name, service.Namespace, sets.List(nodeSet)) ++ // Fallback: if no endpoints with NodeName were found, but there are endpoints without NodeName, ++ // distribute traffic to all VMIs (similar to ExternalTrafficPolicy=Cluster behavior) ++ if nodeSet.Len() == 0 && hasEndpointsWithoutNodeName { ++ klog.Infof("No endpoints with NodeName found for service %s/%s, falling back to all VMIs", service.Namespace, service.Name) ++ return c.getAllVMIEndpoints() ++ } ++ + klog.Infof("Desired nodes for service %s/%s: %v", service.Namespace, service.Name, sets.List(nodeSet)) for _, node := range sets.List(nodeSet) { @@ -68,7 +80,7 @@ index 53388eb8e..28644236f 100644 desiredEndpoints = append(desiredEndpoints, &discovery.Endpoint{ Addresses: []string{i.IP}, Conditions: discovery.EndpointConditions{ -@@ -705,9 +719,9 @@ func (c *Controller) getDesiredEndpoints(service *v1.Service, tenantSlices []*di +@@ -705,9 +728,9 @@ func (c *Controller) getDesiredEndpoints(service *v1.Service, tenantSlices []*di Serving: &serving, Terminating: &terminating, }, @@ -80,6 +92,71 @@ index 53388eb8e..28644236f 100644 } } } +@@ -716,6 +739,64 @@ func (c *Controller) getDesiredEndpoints(service *v1.Service, tenantSlices []*di + return desiredEndpoints + } + ++// getAllVMIEndpoints returns endpoints for all VMIs in the infra namespace. ++// This is used as a fallback when tenant endpoints don't have NodeName specified, ++// similar to ExternalTrafficPolicy=Cluster behavior where traffic is distributed to all nodes. ++func (c *Controller) getAllVMIEndpoints() []*discovery.Endpoint { ++ var endpoints []*discovery.Endpoint ++ ++ // List all VMIs in the infra namespace ++ vmiList, err := c.infraDynamic. ++ Resource(kubevirtv1.VirtualMachineInstanceGroupVersionKind.GroupVersion().WithResource("virtualmachineinstances")). ++ Namespace(c.infraNamespace). ++ List(context.TODO(), metav1.ListOptions{}) ++ if err != nil { ++ klog.Errorf("Failed to list VMIs in namespace %q: %v", c.infraNamespace, err) ++ return endpoints ++ } ++ ++ for _, obj := range vmiList.Items { ++ vmi := &kubevirtv1.VirtualMachineInstance{} ++ err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, vmi) ++ if err != nil { ++ klog.Errorf("Failed to convert Unstructured to VirtualMachineInstance: %v", err) ++ continue ++ } ++ ++ if vmi.Status.NodeName == "" { ++ klog.Warningf("Skipping VMI %s/%s: NodeName is empty", vmi.Namespace, vmi.Name) ++ continue ++ } ++ nodeNamePtr := &vmi.Status.NodeName ++ ++ ready := vmi.Status.Phase == kubevirtv1.Running ++ serving := vmi.Status.Phase == kubevirtv1.Running ++ terminating := vmi.Status.Phase == kubevirtv1.Failed || vmi.Status.Phase == kubevirtv1.Succeeded ++ ++ for _, i := range vmi.Status.Interfaces { ++ if i.Name == "default" { ++ if i.IP == "" { ++ klog.Warningf("VMI %s/%s interface %q has no IP, skipping", vmi.Namespace, vmi.Name, i.Name) ++ continue ++ } ++ endpoints = append(endpoints, &discovery.Endpoint{ ++ Addresses: []string{i.IP}, ++ Conditions: discovery.EndpointConditions{ ++ Ready: &ready, ++ Serving: &serving, ++ Terminating: &terminating, ++ }, ++ NodeName: nodeNamePtr, ++ }) ++ break ++ } ++ } ++ } ++ ++ klog.Infof("Fallback: created %d endpoints from all VMIs in namespace %s", len(endpoints), c.infraNamespace) ++ return endpoints ++} ++ + func (c *Controller) ensureEndpointSliceLabels(slice *discovery.EndpointSlice, svc *v1.Service) (map[string]string, bool) { + labels := make(map[string]string) + labelsChanged := false diff --git a/pkg/controller/kubevirteps/kubevirteps_controller_test.go b/pkg/controller/kubevirteps/kubevirteps_controller_test.go index 1c97035b4..d205d0bed 100644 --- a/pkg/controller/kubevirteps/kubevirteps_controller_test.go From 07982b2646cf6b973b49e3c90312413d9ea86fdc Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Mon, 5 Jan 2026 15:27:36 +0300 Subject: [PATCH 26/45] [testing] Add aliases and autocomplete ## What this PR does Adds a `k=kubectl` alias and bash completion for kubectl to the e2e-testing sandbox container to maintainers have an easier time exec'ing into the CI container when something needs to be debugged. ### Release note ```release-note [testing] Add k=kubectl alias and enable kubectl completion in the CI container. ``` Signed-off-by: Timofei Larkin (cherry picked from commit bf1928c96f8ed61c87d3d4b923b24d83d08f9c85) --- packages/core/testing/Chart.yaml | 0 packages/core/testing/Makefile | 0 packages/core/testing/images/e2e-sandbox/Dockerfile | 10 +++++++++- packages/core/testing/values.yaml | 0 4 files changed, 9 insertions(+), 1 deletion(-) mode change 100755 => 100644 packages/core/testing/Chart.yaml mode change 100755 => 100644 packages/core/testing/Makefile mode change 100755 => 100644 packages/core/testing/images/e2e-sandbox/Dockerfile mode change 100755 => 100644 packages/core/testing/values.yaml diff --git a/packages/core/testing/Chart.yaml b/packages/core/testing/Chart.yaml old mode 100755 new mode 100644 diff --git a/packages/core/testing/Makefile b/packages/core/testing/Makefile old mode 100755 new mode 100644 diff --git a/packages/core/testing/images/e2e-sandbox/Dockerfile b/packages/core/testing/images/e2e-sandbox/Dockerfile old mode 100755 new mode 100644 index 069a1905..1dabdc55 --- a/packages/core/testing/images/e2e-sandbox/Dockerfile +++ b/packages/core/testing/images/e2e-sandbox/Dockerfile @@ -9,7 +9,7 @@ ARG TARGETOS ARG TARGETARCH RUN apt update -q -RUN apt install -yq --no-install-recommends psmisc genisoimage ca-certificates qemu-kvm qemu-utils iproute2 iptables wget xz-utils netcat curl jq make git +RUN apt install -yq --no-install-recommends psmisc genisoimage ca-certificates qemu-kvm qemu-utils iproute2 iptables wget xz-utils netcat curl jq make git bash-completion RUN curl -sSL "https://github.com/siderolabs/talos/releases/download/v${TALOSCTL_VERSION}/talosctl-${TARGETOS}-${TARGETARCH}" -o /usr/local/bin/talosctl \ && chmod +x /usr/local/bin/talosctl RUN curl -sSL "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/${TARGETOS}/${TARGETARCH}/kubectl" -o /usr/local/bin/kubectl \ @@ -21,5 +21,13 @@ RUN curl -sSL "https://fluxcd.io/install.sh" | bash RUN curl -sSL "https://github.com/cozystack/cozypkg/raw/refs/heads/main/hack/install.sh" | sh -s -- -v "${COZYPKG_VERSION}" RUN curl https://dl.min.io/client/mc/release/${TARGETOS}-${TARGETARCH}/mc --create-dirs -o /usr/local/bin/mc \ && chmod +x /usr/local/bin/mc +RUN <<'EOF' +cat <<'EOT' >> /etc/bash.bashrc +. /etc/bash_completion +. <(kubectl completion bash) +alias k=kubectl +complete -F __start_kubectl k +EOT +EOF COPY entrypoint.sh /usr/local/bin/entrypoint.sh ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml old mode 100755 new mode 100644 From cb0e457ddad963970a3e5579fed8c5450e3c31d4 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 16 Dec 2025 02:37:32 +0100 Subject: [PATCH 27/45] feat(linstor): build linstor-server with custom patches Add patches for piraeus-server: - adjust-on-resfile-change: handle resource file changes - allow-toggle-disk-retry: enable disk operation retries - force-metadata-check-on-disk-add: verify metadata on disk addition Update plunger-satellite script and values.yaml for new build. Signed-off-by: Andrei Kvapil (cherry picked from commit daa91fd2f36b9c9ee791e99569f613f858c1b842) --- Makefile | 1 + packages/system/linstor/Makefile | 19 ++ .../linstor/hack/plunger/plunger-satellite.sh | 122 ++++++++- .../linstor/images/piraeus-server/Dockerfile | 172 +++++++++++++ .../images/piraeus-server/patches/README.md | 12 + .../patches/adjust-on-resfile-change.diff | 48 ++++ .../patches/allow-toggle-disk-retry.diff | 235 ++++++++++++++++++ .../force-metadata-check-on-disk-add.diff | 63 +++++ .../skip-adjust-when-device-inaccessible.diff | 93 +++++++ .../system/linstor/templates/_helpers.tpl | 24 -- .../system/linstor/templates/cluster.yaml | 4 +- .../linstor/templates/satellites-cozy.yaml | 1 + .../linstor/templates/satellites-plunger.yaml | 4 +- packages/system/linstor/values.yaml | 5 +- 14 files changed, 773 insertions(+), 30 deletions(-) create mode 100644 packages/system/linstor/images/piraeus-server/Dockerfile create mode 100644 packages/system/linstor/images/piraeus-server/patches/README.md create mode 100644 packages/system/linstor/images/piraeus-server/patches/adjust-on-resfile-change.diff create mode 100644 packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff create mode 100644 packages/system/linstor/images/piraeus-server/patches/force-metadata-check-on-disk-add.diff create mode 100644 packages/system/linstor/images/piraeus-server/patches/skip-adjust-when-device-inaccessible.diff delete mode 100644 packages/system/linstor/templates/_helpers.tpl diff --git a/Makefile b/Makefile index 31a947c4..56322b04 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ build: build-deps make -C packages/system/cozystack-controller image make -C packages/system/lineage-controller-webhook image make -C packages/system/cilium image + make -C packages/system/linstor image make -C packages/system/kubeovn-webhook image make -C packages/system/kubeovn-plunger image make -C packages/system/dashboard image diff --git a/packages/system/linstor/Makefile b/packages/system/linstor/Makefile index 5de22194..da895085 100644 --- a/packages/system/linstor/Makefile +++ b/packages/system/linstor/Makefile @@ -1,4 +1,23 @@ export NAME=linstor export NAMESPACE=cozy-$(NAME) +include ../../../scripts/common-envs.mk include ../../../scripts/package.mk + +LINSTOR_VERSION ?= 1.32.3 + +image: + docker buildx build images/piraeus-server \ + --build-arg LINSTOR_VERSION=$(LINSTOR_VERSION) \ + --build-arg K8S_AWAIT_ELECTION_VERSION=v0.4.2 \ + --tag $(REGISTRY)/piraeus-server:$(call settag,$(LINSTOR_VERSION)) \ + --tag $(REGISTRY)/piraeus-server:$(call settag,$(LINSTOR_VERSION)-$(TAG)) \ + --cache-from type=registry,ref=$(REGISTRY)/piraeus-server:latest \ + --cache-to type=inline \ + --metadata-file images/piraeus-server.json \ + $(BUILDX_ARGS) + REPOSITORY="$(REGISTRY)/piraeus-server" \ + yq -i '.piraeusServer.image.repository = strenv(REPOSITORY)' values.yaml + TAG="$(call settag,$(LINSTOR_VERSION))@$$(yq e '."containerimage.digest"' images/piraeus-server.json -o json -r)" \ + yq -i '.piraeusServer.image.tag = strenv(TAG)' values.yaml + rm -f images/piraeus-server.json diff --git a/packages/system/linstor/hack/plunger/plunger-satellite.sh b/packages/system/linstor/hack/plunger/plunger-satellite.sh index 688cfd65..4a488a51 100755 --- a/packages/system/linstor/hack/plunger/plunger-satellite.sh +++ b/packages/system/linstor/hack/plunger/plunger-satellite.sh @@ -10,10 +10,125 @@ trap terminate SIGINT SIGQUIT SIGTERM echo "Starting Linstor per-satellite plunger" +INTERVAL_SEC="${INTERVAL_SEC:-30}" +STALL_ITERS="${STALL_ITERS:-4}" +STATE_FILE="${STATE_FILE:-/run/drbd-sync-watch.state}" + +log() { printf '%s %s\n' "$(date -Is)" "$*" >&2; } + +drbd_status_json() { + drbdsetup status --json 2>/dev/null || true +} + +# Detect DRBD resources where resync is stuck: +# - at least one local device is Inconsistent +# - there is an active SyncTarget peer +# - there are other peers suspended with resync-suspended:dependency +# Output format: " " +drbd_stall_candidates() { + jq -r ' + .[]? + | . as $r + | select(any($r.devices[]?; ."disk-state" == "Inconsistent")) + | ( + [ $r.connections[]? + | . as $c + | $c.peer_devices[]? + | select(."replication-state" == "SyncTarget") + | { peer: $c.name, pct: (."percent-in-sync" // empty) } + ] | .[0]? + ) as $sync + | select($sync != null and ($sync.pct|tostring) != "") + | select(any($r.connections[]?.peer_devices[]?; ."resync-suspended" == "dependency")) + | "\($r.name) \($sync.peer) \($sync.pct)" + ' +} + +drbd_stall_load_state() { + [ -f "$STATE_FILE" ] && cat "$STATE_FILE" || true +} + +drbd_stall_save_state() { + local tmp="${STATE_FILE}.tmp" + cat >"$tmp" + mv "$tmp" "$STATE_FILE" +} + +# Break stalled resync by disconnecting the current SyncTarget peer. +# After reconnect, DRBD will typically pick another eligible peer and continue syncing. +drbd_stall_act() { + local res="$1" + local peer="$2" + local pct="$3" + log "STALL detected: res=$res sync_peer=$peer percent_in_sync=$pct -> disconnect/connect" + drbdadm disconnect "${res}:${peer}" && drbdadm connect "$res" || log "WARN: action failed for ${res}:${peer}" +} + +# Track percent-in-sync progress across iterations. +# If progress does not change for STALL_ITERS loops, trigger reconnect. +drbd_fix_stalled_sync() { + local now prev json out + now="$(date +%s)" + prev="$(drbd_stall_load_state)" + + json="$(drbd_status_json)" + [ -n "$json" ] || return 0 + + out="$(printf '%s' "$json" | drbd_stall_candidates)" + + local new_state="" + local acts="" + + while IFS= read -r line; do + [ -n "$line" ] || continue + set -- $line + local res="$1" peer="$2" pct="$3" + local key="${res} ${peer}" + + local prev_line + prev_line="$(printf '%s\n' "$prev" | awk -v k="$key" '$1" "$2==k {print; exit}')" + + local cnt last_act prev_pct prev_cnt prev_act + if [ -n "$prev_line" ]; then + set -- $prev_line + prev_pct="$3" + prev_cnt="$4" + prev_act="$5" + if [ "$pct" = "$prev_pct" ]; then + cnt=$((prev_cnt + 1)) + else + cnt=1 + fi + last_act="$prev_act" + else + cnt=1 + last_act=0 + fi + + if [ "$cnt" -ge "$STALL_ITERS" ]; then + acts="${acts}${res} ${peer} ${pct}"$'\n' + cnt=0 + last_act="$now" + fi + + new_state="${new_state}${res} ${peer} ${pct} ${cnt} ${last_act}"$'\n' + done <<< "$out" + + if [ -n "$acts" ]; then + while IFS= read -r a; do + [ -n "$a" ] || continue + set -- $a + drbd_stall_act "$1" "$2" "$3" + done <<< "$acts" + fi + + printf '%s' "$new_state" | drbd_stall_save_state +} + while true; do # timeout at the start of the loop to give a chance for the fresh linstor-satellite instance to cleanup itself - sleep 30 & + sleep "$INTERVAL_SEC" & pid=$! wait $pid @@ -21,7 +136,7 @@ while true; do # the `/` path could not be a backing file for a loop device, so it's a good indicator of a stuck loop device # TODO describe the issue in more detail # Using the direct /usr/sbin/losetup as the linstor-satellite image has own wrapper in /usr/local - stale_loopbacks=$(/usr/sbin/losetup --json | jq -r '.[][] | select(."back-file" == "/" or ."back-file" == "/ (deleted)").name' ) + stale_loopbacks=$(/usr/sbin/losetup --json | jq -r '.[][] | select(."back-file" == "/" or ."back-file" == "/ (deleted)").name') for stale_device in $stale_loopbacks; do ( echo "Detaching stuck loop device ${stale_device}" set -x @@ -39,4 +154,7 @@ while true; do drbdadm up "${secondary}" || echo "Command failed" ); done + # Detect and fix stalled DRBD resync by switching SyncTarget peer + drbd_fix_stalled_sync || true + done diff --git a/packages/system/linstor/images/piraeus-server/Dockerfile b/packages/system/linstor/images/piraeus-server/Dockerfile new file mode 100644 index 00000000..049a3f47 --- /dev/null +++ b/packages/system/linstor/images/piraeus-server/Dockerfile @@ -0,0 +1,172 @@ +ARG DISTRO=bookworm +ARG LINSTOR_VERSION + +# ------------------------------------------------------------------------------ +# Build linstor-server from source +FROM debian:bookworm AS builder + +ARG LINSTOR_VERSION +ARG VERSION=${LINSTOR_VERSION} +ARG DISTRO + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update \ + && apt-get -y upgrade \ + && apt-get -y install build-essential git default-jdk-headless python3-all debhelper wget unzip && \ + wget https://services.gradle.org/distributions/gradle-8.9-bin.zip -O /tmp/gradle.zip && \ + unzip -d /opt /tmp/gradle.zip && \ + rm /tmp/gradle.zip && \ + ln -s /opt/gradle-8.9/bin/gradle /usr/local/bin/gradle + +RUN git clone https://github.com/LINBIT/linstor-server.git /linstor-server +WORKDIR /linstor-server +RUN git checkout v${VERSION} + +# Apply patches +COPY patches /patches +RUN git apply /patches/*.diff && \ + git config user.email "build@cozystack.io" && \ + git config user.name "Cozystack Builder" && \ + git add -A && \ + git commit -m "Apply patches" + +# Initialize git submodules +RUN git submodule update --init --recursive || make check-submods + +# Pre-download ALL dependencies before make tarball +# This ensures all transitive dependencies are cached, including optional ones like AWS SDK +RUN ./gradlew getProtoc +RUN ./gradlew generateJava +RUN ./gradlew --no-daemon --gradle-user-home .gradlehome downloadDependencies + +# Manually create tarball without removing caches +# make tarball removes .gradlehome/caches/[0-9]* which deletes dependencies +# So we'll do the steps manually but keep the caches +RUN make check-submods versioninfo gen-java FORCE=1 VERSION=${VERSION} +RUN make server/jar.deps controller/jar.deps satellite/jar.deps jclcrypto/jar.deps FORCE=1 VERSION=${VERSION} +RUN make .filelist FORCE=1 VERSION=${VERSION} PRESERVE_DEBIAN=1 +# Don't remove caches - we need them for offline build +RUN rm -Rf .gradlehome/wrapper .gradlehome/native .gradlehome/.tmp || true +RUN mkdir -p ./libs +RUN make tgz VERSION=${VERSION} + +# Extract tarball and build DEB packages from it +RUN mv linstor-server-${VERSION}.tar.gz /linstor-server_${VERSION}.orig.tar.gz \ + && tar -C / -xvf /linstor-server_${VERSION}.orig.tar.gz + +WORKDIR /linstor-server-${VERSION} +# Verify .gradlehome is present in extracted tarball +RUN test -d .gradlehome && echo ".gradlehome found in tarball" || (echo ".gradlehome not found in tarball!" && exit 1) + +# Build DEB packages from tarball +# Override GRADLE_FLAGS to remove --offline flag, allowing Gradle to download missing dependencies +RUN sed -i 's/GRADLE_FLAGS = --offline/GRADLE_FLAGS =/' debian/rules || true +RUN LD_LIBRARY_PATH='' dpkg-buildpackage -rfakeroot -b -uc + +# Copy built .deb packages to a location accessible from final image +# dpkg-buildpackage creates packages in parent directory +RUN mkdir -p /packages-output && \ + find .. -maxdepth 1 -name "linstor-*.deb" -exec cp {} /packages-output/ \; && \ + test -n "$(ls -A /packages-output)" || (echo "ERROR: No linstor .deb packages found after build." && exit 1) + +# ------------------------------------------------------------------------------ +# Final image +FROM debian:${DISTRO} + +LABEL maintainer="Roland Kammerer " + +ARG LINSTOR_VERSION +ARG DISTRO + +# Copy built .deb packages from builder stage +# dpkg-buildpackage creates packages in parent directory, we copied them to /packages-output +COPY --from=builder /packages-output/ /packages/ + +RUN { echo 'APT::Install-Recommends "false";' ; echo 'APT::Install-Suggests "false";' ; } > /etc/apt/apt.conf.d/99_piraeus + +RUN --mount=type=cache,target=/var/cache,sharing=private \ + --mount=type=cache,target=/var/lib/apt/lists,sharing=private \ + --mount=type=tmpfs,target=/var/log \ + # Install wget first for downloading keyring + apt-get update && apt-get install -y wget ca-certificates && \ + # Enable contrib repos for zfsutils \ + . /etc/os-release && \ + sed -i -r 's/^Components: (.*)$/Components: \1 contrib/' /etc/apt/sources.list.d/debian.sources && \ + echo "deb http://deb.debian.org/debian $VERSION_CODENAME-backports contrib" > /etc/apt/sources.list.d/backports.list && \ + wget https://packages.linbit.com/public/linbit-keyring.deb -O /var/cache/linbit-keyring.deb && \ + dpkg -i /var/cache/linbit-keyring.deb && \ + echo "deb http://packages.linbit.com/public $VERSION_CODENAME misc" > /etc/apt/sources.list.d/linbit.list && \ + apt-get update && \ + # Install useful utilities and general dependencies + apt-get install -y udev drbd-utils jq net-tools iputils-ping iproute2 dnsutils netcat-traditional sysstat curl util-linux && \ + # Install dependencies for optional features \ + apt-get install -y \ + # cryptsetup: luks layer + cryptsetup \ + # e2fsprogs: LINSTOR can create file systems \ + e2fsprogs \ + # lsscsi: exos layer \ + lsscsi \ + # lvm2: manage lvm storage pools \ + lvm2 \ + # multipath-tools: exos layer \ + multipath-tools \ + # nvme-cli: nvme layer + nvme-cli \ + # procps: used by LINSTOR to find orphaned send/receive processes \ + procps \ + # socat: used with thin-send-recv to send snapshots to another LINSTOR cluster + socat \ + # thin-send-recv: used to send/receive snapshots of LVM thin volumes \ + thin-send-recv \ + # xfsprogs: LINSTOR can create file systems; xfs deps \ + xfsprogs \ + # zstd: used with thin-send-recv to send snapshots to another LINSTOR cluster \ + zstd \ + # zfsutils-linux: for zfs storage pools \ + zfsutils-linux/$VERSION_CODENAME-backports \ + && \ + # remove udev, no need for it in the container \ + apt-get remove -y udev && \ + # Install linstor packages from built .deb files and linstor-client from repository + apt-get install -y default-jre-headless python3-all python3-natsort linstor-client \ + && ls packages/*.deb >/dev/null && (dpkg -i packages/*.deb || apt-get install -f -y) \ + && rm -rf /packages \ + && sed -i 's/"-Djdk.tls.acknowledgeCloseNotify=true"//g' /usr/share/linstor-server/bin/Controller \ + && apt-get clean + +# Log directory need to be group writable. OpenShift assigns random UID and GID, without extra RBAC changes we can only influence the GID. +RUN mkdir /var/log/linstor-controller && \ + chown 0:1000 /var/log/linstor-controller && \ + chmod -R 0775 /var/log/linstor-controller && \ + # Ensure we log to files in containers, otherwise SOS reports won't show any logs at all + sed -i 's###' /usr/share/linstor-server/lib/conf/logback.xml + + +RUN lvmconfig --type current --mergedconfig --config 'activation { udev_sync = 0 udev_rules = 0 monitoring = 0 } devices { global_filter = [ "r|^/dev/drbd|" ] obtain_device_list_from_udev = 0}' > /etc/lvm/lvm.conf.new && mv /etc/lvm/lvm.conf.new /etc/lvm/lvm.conf +RUN echo 'global { usage-count no; }' > /etc/drbd.d/global_common.conf + +# controller +EXPOSE 3376/tcp 3377/tcp 3370/tcp 3371/tcp + +# satellite +EXPOSE 3366/tcp 3367/tcp + +RUN wget https://raw.githubusercontent.com/piraeusdatastore/piraeus/refs/heads/master/dockerfiles/piraeus-server/entry.sh -O /usr/bin/piraeus-entry.sh \ + && chmod +x /usr/bin/piraeus-entry.sh + +ARG K8S_AWAIT_ELECTION_VERSION=v0.4.2 +# TARGETARCH is a docker special variable: https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope +ARG TARGETARCH + +RUN wget https://github.com/LINBIT/k8s-await-election/releases/download/${K8S_AWAIT_ELECTION_VERSION}/k8s-await-election-${K8S_AWAIT_ELECTION_VERSION}-linux-${TARGETARCH}.tar.gz -O - | tar -xvz -C /usr/bin/ + +ARG LOSETUP_CONTAINER_VERSION=v1.0.1 +RUN wget "https://github.com/LINBIT/losetup-container/releases/download/${LOSETUP_CONTAINER_VERSION}/losetup-container-$(uname -m)-unknown-linux-gnu.tar.gz" -O - | tar -xvz -C /usr/local/sbin && \ + printf '#!/bin/sh\nLOSETUP_CONTAINER_ORIGINAL_LOSETUP=%s exec /usr/local/sbin/losetup-container "$@"\n' $(command -v losetup) > /usr/local/sbin/losetup && \ + chmod +x /usr/local/sbin/losetup + +RUN wget "https://dl.k8s.io/$(wget -O - https://dl.k8s.io/release/stable.txt)/bin/linux/${TARGETARCH}/kubectl" -O /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl + +CMD ["startSatellite"] +ENTRYPOINT ["/usr/bin/k8s-await-election", "/usr/bin/piraeus-entry.sh"] diff --git a/packages/system/linstor/images/piraeus-server/patches/README.md b/packages/system/linstor/images/piraeus-server/patches/README.md new file mode 100644 index 00000000..c219eaf3 --- /dev/null +++ b/packages/system/linstor/images/piraeus-server/patches/README.md @@ -0,0 +1,12 @@ +# LINSTOR Server Patches + +Custom patches for piraeus-server (linstor-server) v1.32.3. + +- **adjust-on-resfile-change.diff** — Use actual device path in res file during toggle-disk; fix LUKS data offset + - Upstream: [#473](https://github.com/LINBIT/linstor-server/pull/473), [#472](https://github.com/LINBIT/linstor-server/pull/472) +- **allow-toggle-disk-retry.diff** — Allow retry and cancellation of failed toggle-disk operations + - Upstream: [#475](https://github.com/LINBIT/linstor-server/pull/475) +- **force-metadata-check-on-disk-add.diff** — Create metadata during toggle-disk from diskless to diskful + - Upstream: [#474](https://github.com/LINBIT/linstor-server/pull/474) +- **skip-adjust-when-device-inaccessible.diff** — Skip DRBD adjust/res file regeneration when child layer device is inaccessible + - Upstream: [#471](https://github.com/LINBIT/linstor-server/pull/471) diff --git a/packages/system/linstor/images/piraeus-server/patches/adjust-on-resfile-change.diff b/packages/system/linstor/images/piraeus-server/patches/adjust-on-resfile-change.diff new file mode 100644 index 00000000..6788efaf --- /dev/null +++ b/packages/system/linstor/images/piraeus-server/patches/adjust-on-resfile-change.diff @@ -0,0 +1,48 @@ +diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java +index 36c52ccf8..c0bb7b967 100644 +--- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java ++++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java +@@ -894,12 +894,16 @@ public class ConfFileBuilder + if (((Volume) vlmData.getVolume()).getFlags().isUnset(localAccCtx, Volume.Flags.DELETE)) + { + final String disk; ++ // Check if we're in toggle-disk operation (adding disk to diskless resource) ++ boolean isDiskAdding = vlmData.getVolume().getAbsResource().getStateFlags().isSomeSet( ++ localAccCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING); + if ((!isPeerRsc && vlmData.getDataDevice() == null) || + (isPeerRsc && + // FIXME: vlmData.getRscLayerObject().getFlags should be used here + vlmData.getVolume().getAbsResource().disklessForDrbdPeers(accCtx) + ) || +- (!isPeerRsc && ++ // For toggle-disk: if dataDevice is set and we're adding disk, use the actual device ++ (!isPeerRsc && !isDiskAdding && + // FIXME: vlmData.getRscLayerObject().getFlags should be used here + vlmData.getVolume().getAbsResource().isDrbdDiskless(accCtx) + ) +diff --git a/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java b/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java +index 54dd5c19f..018de58cf 100644 +--- a/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java ++++ b/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java +@@ -34,6 +34,9 @@ public class CryptSetupCommands implements Luks + private static final Version V2_1_0 = new Version(2, 1, 0); + private static final Version V2_0_0 = new Version(2, 0, 0); + private static final String PBDKF_MAX_MEMORY_KIB = "262144"; // 256 MiB ++ // Fixed LUKS2 data offset in 512-byte sectors (16 MiB = 32768 sectors) ++ // This ensures consistent LUKS header size across all nodes regardless of system defaults ++ private static final String LUKS2_DATA_OFFSET_SECTORS = "32768"; + + @SuppressWarnings("unused") + private final ErrorReporter errorReporter; +@@ -78,6 +81,11 @@ public class CryptSetupCommands implements Luks + command.add(CRYPTSETUP); + command.add("-q"); + command.add("luksFormat"); ++ // Always specify explicit offset to ensure consistent LUKS header size across all nodes ++ // Without this, different systems may create LUKS with different header sizes (16MiB vs 32MiB) ++ // which causes "Low.dev. smaller than requested DRBD-dev. size" errors during toggle-disk ++ command.add("--offset"); ++ command.add(LUKS2_DATA_OFFSET_SECTORS); + if (version.greaterOrEqual(V2_0_0)) + { + command.add("--pbkdf-memory"); diff --git a/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff b/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff new file mode 100644 index 00000000..4f4f2fc6 --- /dev/null +++ b/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff @@ -0,0 +1,235 @@ +diff --git a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java +index 1a6f7b7f0..bd447e049 100644 +--- a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java ++++ b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java +@@ -58,7 +58,9 @@ import com.linbit.linstor.stateflags.StateFlags; + import com.linbit.linstor.storage.StorageException; + import com.linbit.linstor.storage.data.adapter.drbd.DrbdRscData; + import com.linbit.linstor.storage.interfaces.categories.resource.AbsRscLayerObject; ++import com.linbit.linstor.storage.interfaces.categories.resource.VlmProviderObject; + import com.linbit.linstor.storage.kinds.DeviceLayerKind; ++import com.linbit.linstor.storage.kinds.DeviceProviderKind; + import com.linbit.linstor.storage.utils.LayerUtils; + import com.linbit.linstor.tasks.AutoDiskfulTask; + import com.linbit.linstor.utils.layer.LayerRscUtils; +@@ -317,21 +319,90 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + + Resource rsc = ctrlApiDataLoader.loadRsc(nodeName, rscName, true); + ++ // Allow retry of the same operation if the previous attempt failed ++ // (the requested flag remains set for retry on reconnection, but we should also allow manual retry) ++ // Also allow cancellation of a failed operation by requesting the opposite operation + if (hasDiskAddRequested(rsc)) + { +- throw new ApiRcException(ApiCallRcImpl.simpleEntry( +- ApiConsts.FAIL_RSC_BUSY, +- "Addition of disk to resource already requested", +- true +- )); ++ if (removeDisk) ++ { ++ // User wants to cancel the failed add-disk operation and go back to diskless ++ // Use the existing disk removal flow to properly cleanup storage on satellite ++ errorReporter.logInfo( ++ "Toggle Disk cancel on %s/%s - cancelling failed DISK_ADD_REQUESTED, reverting to diskless", ++ nodeNameStr, rscNameStr); ++ unmarkDiskAddRequested(rsc); ++ // Also clear DISK_ADDING if it was set ++ unmarkDiskAdding(rsc); ++ ++ // Set storage pool to diskless pool (overwrite the diskful pool that was set) ++ Props rscProps = ctrlPropsHelper.getProps(rsc); ++ rscProps.map().put(ApiConsts.KEY_STOR_POOL_NAME, LinStor.DISKLESS_STOR_POOL_NAME); ++ ++ // Set DISK_REMOVE_REQUESTED to use the existing disk removal flow ++ // This will: ++ // 1. updateAndAdjustDisk sets DISK_REMOVING flag ++ // 2. Satellite sees DISK_REMOVING and deletes LUKS/storage devices ++ // 3. finishOperation rebuilds layer stack as diskless ++ // We keep the existing layer data so satellite can properly cleanup ++ markDiskRemoveRequested(rsc); ++ ++ ctrlTransactionHelper.commit(); ++ ++ // Use existing disk removal flow - this will properly cleanup storage on satellite ++ return Flux ++ .just(ApiCallRcImpl.singleApiCallRc( ++ ApiConsts.MODIFIED, ++ "Cancelling disk addition, reverting to diskless" ++ )) ++ .concatWith(updateAndAdjustDisk(nodeName, rscName, true, toggleIntoTiebreakerRef, context)) ++ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition())); ++ } ++ // If adding disk and DISK_ADD_REQUESTED is already set, treat as retry ++ // First clean up partially created storage by removing and recreating layer data ++ errorReporter.logInfo( ++ "Toggle Disk retry on %s/%s - DISK_ADD_REQUESTED already set, cleaning up and retrying", ++ nodeNameStr, rscNameStr); ++ ++ // Remove old layer data and recreate to ensure clean state ++ // This forces satellite to delete any partially created storage and start fresh ++ LayerPayload payload = new LayerPayload(); ++ copyDrbdNodeIdIfExists(rsc, payload); ++ List layerList = removeLayerData(rsc); ++ ctrlLayerStackHelper.ensureStackDataExists(rsc, layerList, payload); ++ ++ ctrlTransactionHelper.commit(); ++ return Flux ++ .just(new ApiCallRcImpl()) ++ .concatWith(updateAndAdjustDisk(nodeName, rscName, false, toggleIntoTiebreakerRef, context)) ++ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition())); + } + if (hasDiskRemoveRequested(rsc)) + { +- throw new ApiRcException(ApiCallRcImpl.simpleEntry( +- ApiConsts.FAIL_RSC_BUSY, +- "Removal of disk from resource already requested", +- true +- )); ++ if (!removeDisk) ++ { ++ // User wants to cancel the failed remove-disk operation ++ errorReporter.logInfo( ++ "Toggle Disk cancel on %s/%s - cancelling failed DISK_REMOVE_REQUESTED", ++ nodeNameStr, rscNameStr); ++ unmarkDiskRemoveRequested(rsc); ++ ctrlTransactionHelper.commit(); ++ return Flux.just( ++ ApiCallRcImpl.singleApiCallRc( ++ ApiConsts.MODIFIED, ++ "Cancelled disk removal request" ++ ) ++ ); ++ } ++ // If removing disk and DISK_REMOVE_REQUESTED is already set, treat as retry ++ errorReporter.logInfo( ++ "Toggle Disk retry on %s/%s - DISK_REMOVE_REQUESTED already set, continuing operation", ++ nodeNameStr, rscNameStr); ++ ctrlTransactionHelper.commit(); ++ return Flux ++ .just(new ApiCallRcImpl()) ++ .concatWith(updateAndAdjustDisk(nodeName, rscName, true, toggleIntoTiebreakerRef, context)) ++ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition())); + } + + if (!removeDisk && !ctrlVlmCrtApiHelper.isDiskless(rsc)) +@@ -342,17 +413,43 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + true + )); + } ++ ResourceDefinition rscDfn = rsc.getResourceDefinition(); ++ AccessContext peerCtx = peerAccCtx.get(); ++ + if (removeDisk && ctrlVlmCrtApiHelper.isDiskless(rsc)) + { ++ // Resource is marked as diskless - check if it has orphaned storage layers that need cleanup ++ AbsRscLayerObject layerData = getLayerData(peerCtx, rsc); ++ if (layerData != null && (LayerUtils.hasLayer(layerData, DeviceLayerKind.LUKS) || ++ hasNonDisklessStorageLayer(layerData))) ++ { ++ // Resource is marked as diskless but has orphaned storage layers - need cleanup ++ // Use the existing disk removal flow to properly cleanup storage on satellite ++ errorReporter.logInfo( ++ "Toggle Disk cleanup on %s/%s - resource is diskless but has orphaned storage layers, cleaning up", ++ nodeNameStr, rscNameStr); ++ ++ // Set DISK_REMOVE_REQUESTED to use the existing disk removal flow ++ // This will trigger proper satellite cleanup via DISK_REMOVING flag ++ markDiskRemoveRequested(rsc); ++ ++ ctrlTransactionHelper.commit(); ++ ++ // Use existing disk removal flow - this will properly cleanup storage on satellite ++ return Flux ++ .just(ApiCallRcImpl.singleApiCallRc( ++ ApiConsts.MODIFIED, ++ "Cleaning up orphaned storage layers" ++ )) ++ .concatWith(updateAndAdjustDisk(nodeName, rscName, true, toggleIntoTiebreakerRef, context)) ++ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition())); ++ } + throw new ApiRcException(ApiCallRcImpl.simpleEntry( + ApiConsts.WARN_RSC_ALREADY_DISKLESS, + "Resource already diskless", + true + )); + } +- +- ResourceDefinition rscDfn = rsc.getResourceDefinition(); +- AccessContext peerCtx = peerAccCtx.get(); + if (removeDisk) + { + // Prevent removal of the last disk +@@ -1324,6 +1421,30 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + } + } + ++ private void unmarkDiskAddRequested(Resource rsc) ++ { ++ try ++ { ++ rsc.getStateFlags().disableFlags(apiCtx, Resource.Flags.DISK_ADD_REQUESTED); ++ } ++ catch (AccessDeniedException | DatabaseException exc) ++ { ++ throw new ImplementationError(exc); ++ } ++ } ++ ++ private void unmarkDiskRemoveRequested(Resource rsc) ++ { ++ try ++ { ++ rsc.getStateFlags().disableFlags(apiCtx, Resource.Flags.DISK_REMOVE_REQUESTED); ++ } ++ catch (AccessDeniedException | DatabaseException exc) ++ { ++ throw new ImplementationError(exc); ++ } ++ } ++ + private void markDiskAdded(Resource rscData) + { + try +@@ -1389,6 +1510,41 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + return layerData; + } + ++ /** ++ * Check if the layer stack has a non-diskless STORAGE layer. ++ * This is used to detect orphaned storage layers that need cleanup. ++ */ ++ private boolean hasNonDisklessStorageLayer(AbsRscLayerObject layerDataRef) ++ { ++ boolean hasNonDiskless = false; ++ if (layerDataRef != null) ++ { ++ if (layerDataRef.getLayerKind() == DeviceLayerKind.STORAGE) ++ { ++ for (VlmProviderObject vlmData : layerDataRef.getVlmLayerObjects().values()) ++ { ++ if (vlmData.getProviderKind() != DeviceProviderKind.DISKLESS) ++ { ++ hasNonDiskless = true; ++ break; ++ } ++ } ++ } ++ if (!hasNonDiskless) ++ { ++ for (AbsRscLayerObject child : layerDataRef.getChildren()) ++ { ++ if (hasNonDisklessStorageLayer(child)) ++ { ++ hasNonDiskless = true; ++ break; ++ } ++ } ++ } ++ } ++ return hasNonDiskless; ++ } ++ + private LockGuard createLockGuard() + { + return lockGuardFactory.buildDeferred(LockType.WRITE, LockObj.NODES_MAP, LockObj.RSC_DFN_MAP); diff --git a/packages/system/linstor/images/piraeus-server/patches/force-metadata-check-on-disk-add.diff b/packages/system/linstor/images/piraeus-server/patches/force-metadata-check-on-disk-add.diff new file mode 100644 index 00000000..0c413005 --- /dev/null +++ b/packages/system/linstor/images/piraeus-server/patches/force-metadata-check-on-disk-add.diff @@ -0,0 +1,63 @@ +diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java +index a302ee835..01967a31f 100644 +--- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java ++++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java +@@ -371,10 +371,13 @@ public class DrbdLayer implements DeviceLayer + boolean isDiskless = drbdRscData.getAbsResource().isDrbdDiskless(workerCtx); + StateFlags rscFlags = drbdRscData.getAbsResource().getStateFlags(); + boolean isDiskRemoving = rscFlags.isSet(workerCtx, Resource.Flags.DISK_REMOVING); ++ // Check if we're in toggle-disk operation (adding disk to diskless resource) ++ boolean isDiskAdding = rscFlags.isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING); + + boolean contProcess = isDiskless; + +- boolean processChildren = !isDiskless || isDiskRemoving; ++ // Process children when: has disk, removing disk, OR adding disk (toggle-disk) ++ boolean processChildren = !isDiskless || isDiskRemoving || isDiskAdding; + // do not process children when ONLY DRBD_DELETE flag is set (DELETE flag is still unset) + processChildren &= (!rscFlags.isSet(workerCtx, Resource.Flags.DRBD_DELETE) || + rscFlags.isSet(workerCtx, Resource.Flags.DELETE)); +@@ -570,7 +573,11 @@ public class DrbdLayer implements DeviceLayer + { + // hasMetaData needs to be run after child-resource processed + List> createMetaData = new ArrayList<>(); +- if (!drbdRscData.getAbsResource().isDrbdDiskless(workerCtx) && !skipDisk) ++ // Check if we're in toggle-disk operation (adding disk to diskless resource) ++ boolean isDiskAddingForMd = drbdRscData.getAbsResource().getStateFlags() ++ .isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING); ++ // Create metadata when: has disk OR adding disk (toggle-disk), and skipDisk is disabled ++ if ((!drbdRscData.getAbsResource().isDrbdDiskless(workerCtx) || isDiskAddingForMd) && !skipDisk) + { + // do not try to create meta data while the resource is diskless or skipDisk is enabled + for (DrbdVlmData drbdVlmData : checkMetaData) +@@ -988,8 +995,10 @@ public class DrbdLayer implements DeviceLayer + { + List> checkMetaData = new ArrayList<>(); + Resource rsc = drbdRscData.getAbsResource(); ++ // Include DISK_ADD_REQUESTED/DISK_ADDING for toggle-disk scenario where we need to check/create metadata + if (!rsc.isDrbdDiskless(workerCtx) || +- rsc.getStateFlags().isSet(workerCtx, Resource.Flags.DISK_REMOVING) ++ rsc.getStateFlags().isSet(workerCtx, Resource.Flags.DISK_REMOVING) || ++ rsc.getStateFlags().isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING) + ) + { + // using a dedicated list to prevent concurrentModificationException +@@ -1177,9 +1186,16 @@ public class DrbdLayer implements DeviceLayer + + boolean hasMetaData; + ++ // Check if we need to verify/create metadata ++ // Force metadata check when: ++ // 1. checkMetaData is enabled ++ // 2. volume doesn't have disk yet (diskless -> diskful transition) ++ // 3. DISK_ADD_REQUESTED/DISK_ADDING flag is set (retry scenario where storage exists but no metadata) ++ boolean isDiskAddingState = drbdVlmData.getRscLayerObject().getAbsResource().getStateFlags() ++ .isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING); + if (drbdVlmData.checkMetaData() || +- // when adding a disk, DRBD believes that it is diskless but we still need to create metadata +- !drbdVlmData.hasDisk()) ++ !drbdVlmData.hasDisk() || ++ isDiskAddingState) + { + if (mdUtils.hasMetaData()) + { diff --git a/packages/system/linstor/images/piraeus-server/patches/skip-adjust-when-device-inaccessible.diff b/packages/system/linstor/images/piraeus-server/patches/skip-adjust-when-device-inaccessible.diff new file mode 100644 index 00000000..09e7ccf9 --- /dev/null +++ b/packages/system/linstor/images/piraeus-server/patches/skip-adjust-when-device-inaccessible.diff @@ -0,0 +1,93 @@ +diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java +index 01967a3..871d830 100644 +--- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java ++++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java +@@ -592,7 +592,29 @@ public class DrbdLayer implements DeviceLayer + // The .res file might not have been generated in the prepare method since it was + // missing information from the child-layers. Now that we have processed them, we + // need to make sure the .res file exists in all circumstances. +- regenerateResFile(drbdRscData); ++ // However, if the underlying devices are not accessible (e.g., LUKS device is closed ++ // during resource deletion), we skip regenerating the res file to avoid errors ++ boolean canRegenerateResFile = true; ++ if (!skipDisk && !drbdRscData.getAbsResource().isDrbdDiskless(workerCtx)) ++ { ++ AbsRscLayerObject dataChild = drbdRscData.getChildBySuffix(RscLayerSuffixes.SUFFIX_DATA); ++ if (dataChild != null) ++ { ++ for (DrbdVlmData drbdVlmData : drbdRscData.getVlmLayerObjects().values()) ++ { ++ VlmProviderObject childVlm = dataChild.getVlmProviderObject(drbdVlmData.getVlmNr()); ++ if (childVlm == null || !childVlm.exists() || childVlm.getDevicePath() == null) ++ { ++ canRegenerateResFile = false; ++ break; ++ } ++ } ++ } ++ } ++ if (canRegenerateResFile) ++ { ++ regenerateResFile(drbdRscData); ++ } + + // createMetaData needs rendered resFile + for (DrbdVlmData drbdVlmData : createMetaData) +@@ -766,19 +788,47 @@ public class DrbdLayer implements DeviceLayer + + if (drbdRscData.isAdjustRequired()) + { +- try ++ // Check if underlying devices are accessible before adjusting ++ // This is important for encrypted resources (LUKS) where the device ++ // might be closed during deletion ++ boolean canAdjust = true; ++ if (!skipDisk && !drbdRscData.getAbsResource().isDrbdDiskless(workerCtx)) + { +- drbdUtils.adjust( +- drbdRscData, +- false, +- skipDisk, +- false +- ); ++ AbsRscLayerObject dataChild = drbdRscData.getChildBySuffix(RscLayerSuffixes.SUFFIX_DATA); ++ if (dataChild != null) ++ { ++ for (DrbdVlmData drbdVlmData : drbdRscData.getVlmLayerObjects().values()) ++ { ++ VlmProviderObject childVlm = dataChild.getVlmProviderObject(drbdVlmData.getVlmNr()); ++ if (childVlm == null || !childVlm.exists() || childVlm.getDevicePath() == null) ++ { ++ canAdjust = false; ++ break; ++ } ++ } ++ } + } +- catch (ExtCmdFailedException extCmdExc) ++ ++ if (canAdjust) ++ { ++ try ++ { ++ drbdUtils.adjust( ++ drbdRscData, ++ false, ++ skipDisk, ++ false ++ ); ++ } ++ catch (ExtCmdFailedException extCmdExc) ++ { ++ restoreBackupResFile(drbdRscData); ++ throw extCmdExc; ++ } ++ } ++ else + { +- restoreBackupResFile(drbdRscData); +- throw extCmdExc; ++ drbdRscData.setAdjustRequired(false); + } + } + diff --git a/packages/system/linstor/templates/_helpers.tpl b/packages/system/linstor/templates/_helpers.tpl deleted file mode 100644 index 20d43863..00000000 --- a/packages/system/linstor/templates/_helpers.tpl +++ /dev/null @@ -1,24 +0,0 @@ -{{- define "cozy.linstor.version" -}} -{{- $piraeusConfigMap := lookup "v1" "ConfigMap" "cozy-linstor" "piraeus-operator-image-config"}} -{{- if not $piraeusConfigMap }} - {{- fail "Piraeus controller is not yet installed, ConfigMap cozy-linstor/piraeus-operator-image-config is missing" }} -{{- end }} -{{- $piraeusImagesConfig := $piraeusConfigMap | dig "data" "0_piraeus_datastore_images.yaml" nil | required "No image config" | fromYaml }} -base: {{ $piraeusImagesConfig.base | required "No image base in piraeus config" }} -controller: - image: {{ $piraeusImagesConfig | dig "components" "linstor-controller" "image" nil | required "No controller image" }} - tag: {{ $piraeusImagesConfig | dig "components" "linstor-controller" "tag" nil | required "No controller tag" }} -satellite: - image: {{ $piraeusImagesConfig | dig "components" "linstor-satellite" "image" nil | required "No satellite image" }} - tag: {{ $piraeusImagesConfig | dig "components" "linstor-satellite" "tag" nil | required "No satellite tag" }} -{{- end -}} - -{{- define "cozy.linstor.version.controller" -}} -{{- $version := (include "cozy.linstor.version" .) | fromYaml }} -{{- printf "%s/%s:%s" $version.base $version.controller.image $version.controller.tag }} -{{- end -}} - -{{- define "cozy.linstor.version.satellite" -}} -{{- $version := (include "cozy.linstor.version" .) | fromYaml }} -{{- printf "%s/%s:%s" $version.base $version.satellite.image $version.satellite.tag }} -{{- end -}} diff --git a/packages/system/linstor/templates/cluster.yaml b/packages/system/linstor/templates/cluster.yaml index 68af90fd..584fd850 100644 --- a/packages/system/linstor/templates/cluster.yaml +++ b/packages/system/linstor/templates/cluster.yaml @@ -27,8 +27,10 @@ spec: podTemplate: spec: containers: + - name: linstor-controller + image: {{ .Values.piraeusServer.image.repository }}:{{ .Values.piraeusServer.image.tag }} - name: plunger - image: {{ include "cozy.linstor.version.controller" . }} + image: {{ .Values.piraeusServer.image.repository }}:{{ .Values.piraeusServer.image.tag }} command: - "/scripts/plunger-controller.sh" securityContext: diff --git a/packages/system/linstor/templates/satellites-cozy.yaml b/packages/system/linstor/templates/satellites-cozy.yaml index a4c1baa7..c621126d 100644 --- a/packages/system/linstor/templates/satellites-cozy.yaml +++ b/packages/system/linstor/templates/satellites-cozy.yaml @@ -13,6 +13,7 @@ spec: hostNetwork: true containers: - name: linstor-satellite + image: {{ .Values.piraeusServer.image.repository }}:{{ .Values.piraeusServer.image.tag }} securityContext: # real-world installations need some debugging from time to time readOnlyRootFilesystem: false diff --git a/packages/system/linstor/templates/satellites-plunger.yaml b/packages/system/linstor/templates/satellites-plunger.yaml index e3cfa3b1..ab71298b 100644 --- a/packages/system/linstor/templates/satellites-plunger.yaml +++ b/packages/system/linstor/templates/satellites-plunger.yaml @@ -11,7 +11,7 @@ spec: spec: containers: - name: plunger - image: {{ include "cozy.linstor.version.satellite" . }} + image: {{ .Values.piraeusServer.image.repository }}:{{ .Values.piraeusServer.image.tag }} command: - "/scripts/plunger-satellite.sh" securityContext: @@ -48,7 +48,7 @@ spec: name: script-volume readOnly: true - name: drbd-logger - image: {{ include "cozy.linstor.version.satellite" . }} + image: {{ .Values.piraeusServer.image.repository }}:{{ .Values.piraeusServer.image.tag }} command: - "/scripts/plunger-drbd-logger.sh" securityContext: diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 8b137891..23179bce 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -1 +1,4 @@ - +piraeusServer: + image: + repository: ghcr.io/cozystack/cozystack/piraeus-server + tag: latest@sha256:417532baa2801288147cd9ac9ae260751c1a7754f0b829725d09b72a770c111a From 0aee5b4dfa2ac65f6193393124e02e37b31efdd9 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 7 Jan 2026 13:27:06 +0100 Subject: [PATCH 28/45] fix(linstor): prevent orphaned DRBD devices during toggle-disk retry The previous retry logic in toggle-disk removed layer data from controller DB and recreated it. However, removeLayerData() only deletes from the database without calling drbdadm down on the satellite, leaving orphaned DRBD devices in the kernel that occupy ports and block new operations. This fix changes retry to simply repeat the operation with existing layer data, allowing the satellite to handle it idempotently. Upstream: https://github.com/LINBIT/linstor-server/pull/475 Co-Authored-By: Claude Signed-off-by: Andrei Kvapil (cherry picked from commit 8151e1e41a9b2b7e28ce128fdd6c258c6453a505) --- .../patches/allow-toggle-disk-retry.diff | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff b/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff index 4f4f2fc6..264e0221 100644 --- a/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff +++ b/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff @@ -1,8 +1,8 @@ diff --git a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java -index 1a6f7b7f0..bd447e049 100644 +index d93a18014..cc8ce4f04 100644 --- a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java +++ b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java -@@ -58,7 +58,9 @@ import com.linbit.linstor.stateflags.StateFlags; +@@ -57,7 +57,9 @@ import com.linbit.linstor.stateflags.StateFlags; import com.linbit.linstor.storage.StorageException; import com.linbit.linstor.storage.data.adapter.drbd.DrbdRscData; import com.linbit.linstor.storage.interfaces.categories.resource.AbsRscLayerObject; @@ -12,7 +12,7 @@ index 1a6f7b7f0..bd447e049 100644 import com.linbit.linstor.storage.utils.LayerUtils; import com.linbit.linstor.tasks.AutoDiskfulTask; import com.linbit.linstor.utils.layer.LayerRscUtils; -@@ -317,21 +319,90 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL +@@ -387,21 +389,84 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL Resource rsc = ctrlApiDataLoader.loadRsc(nodeName, rscName, true); @@ -61,18 +61,12 @@ index 1a6f7b7f0..bd447e049 100644 + .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition())); + } + // If adding disk and DISK_ADD_REQUESTED is already set, treat as retry -+ // First clean up partially created storage by removing and recreating layer data ++ // Simply retry the operation with existing layer data - satellite will handle it idempotently ++ // NOTE: We don't remove/recreate layer data here because removeLayerData() only deletes ++ // from controller DB without calling drbdadm down on satellite, leaving orphaned DRBD devices + errorReporter.logInfo( -+ "Toggle Disk retry on %s/%s - DISK_ADD_REQUESTED already set, cleaning up and retrying", ++ "Toggle Disk retry on %s/%s - DISK_ADD_REQUESTED already set, retrying operation", + nodeNameStr, rscNameStr); -+ -+ // Remove old layer data and recreate to ensure clean state -+ // This forces satellite to delete any partially created storage and start fresh -+ LayerPayload payload = new LayerPayload(); -+ copyDrbdNodeIdIfExists(rsc, payload); -+ List layerList = removeLayerData(rsc); -+ ctrlLayerStackHelper.ensureStackDataExists(rsc, layerList, payload); -+ + ctrlTransactionHelper.commit(); + return Flux + .just(new ApiCallRcImpl()) @@ -113,7 +107,7 @@ index 1a6f7b7f0..bd447e049 100644 } if (!removeDisk && !ctrlVlmCrtApiHelper.isDiskless(rsc)) -@@ -342,17 +413,43 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL +@@ -412,17 +477,43 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL true )); } @@ -160,7 +154,7 @@ index 1a6f7b7f0..bd447e049 100644 if (removeDisk) { // Prevent removal of the last disk -@@ -1324,6 +1421,30 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL +@@ -1446,6 +1537,30 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL } } @@ -191,7 +185,7 @@ index 1a6f7b7f0..bd447e049 100644 private void markDiskAdded(Resource rscData) { try -@@ -1389,6 +1510,41 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL +@@ -1511,6 +1626,41 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL return layerData; } From 8c8ab1f1a0c2287796eebcfb2ab2588351ea880b Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Wed, 31 Dec 2025 16:33:00 +0300 Subject: [PATCH 29/45] [lineage-webhook] Tolerate all taints Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> (cherry picked from commit aa66b8c0d3a71e60905cf1637d992650e94b88ab) --- .../lineage-controller-webhook/templates/daemonset.yaml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/system/lineage-controller-webhook/templates/daemonset.yaml b/packages/system/lineage-controller-webhook/templates/daemonset.yaml index 22074e1d..b6b73ac7 100644 --- a/packages/system/lineage-controller-webhook/templates/daemonset.yaml +++ b/packages/system/lineage-controller-webhook/templates/daemonset.yaml @@ -16,12 +16,7 @@ spec: nodeSelector: node-role.kubernetes.io/control-plane: "" tolerations: - - key: "node-role.kubernetes.io/control-plane" - operator: "Exists" - effect: "NoSchedule" - - key: "node-role.kubernetes.io/master" - operator: "Exists" - effect: "NoSchedule" + - operator: Exists serviceAccountName: lineage-controller-webhook containers: - name: lineage-controller-webhook From 363c8cf0669efe6c08bf32da13dca3898f0342a4 Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Wed, 31 Dec 2025 16:33:21 +0300 Subject: [PATCH 30/45] [cozystack-api] Tolerate all taints Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> (cherry picked from commit 4e602fd55d9f50c1c9d9d7c89175f362eafb7bd2) --- packages/system/cozystack-api/templates/deployment.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/system/cozystack-api/templates/deployment.yaml b/packages/system/cozystack-api/templates/deployment.yaml index 1a63a0e0..ee7e532f 100644 --- a/packages/system/cozystack-api/templates/deployment.yaml +++ b/packages/system/cozystack-api/templates/deployment.yaml @@ -21,6 +21,8 @@ spec: labels: app: cozystack-api spec: + tolerations: + - operator: Exists serviceAccountName: cozystack-api {{- if .Values.cozystackAPI.localK8sAPIEndpoint.enabled }} nodeSelector: From 25f31022f0ab67c4b9786f526441c41f377e72f0 Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Tue, 23 Dec 2025 23:11:19 +0300 Subject: [PATCH 31/45] [ingress] Add topology anti-affinities Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> (cherry picked from commit 6ca8011dfa85302d96372e8f7176ffdd41a30d77) --- packages/system/ingress-nginx/values.yaml | 38 ++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/system/ingress-nginx/values.yaml b/packages/system/ingress-nginx/values.yaml index 5571ff37..68436f51 100644 --- a/packages/system/ingress-nginx/values.yaml +++ b/packages/system/ingress-nginx/values.yaml @@ -54,9 +54,45 @@ ingress-nginx: requests: cpu: 100m memory: 90Mi + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 10 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - '{{ include "ingress-nginx.name" . }}' + - key: app.kubernetes.io/instance + operator: In + values: + - '{{ .Release.Name }}' + - key: app.kubernetes.io/component + operator: In + values: + - controller + topologyKey: kubernetes.io/hostname + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - '{{ include "ingress-nginx.name" . }}' + - key: app.kubernetes.io/instance + operator: In + values: + - '{{ .Release.Name }}' + - key: app.kubernetes.io/component + operator: In + values: + - controller + topologyKey: topology.kubernetes.io/zone defaultBackend: - ## enabled: true resources: limits: From 48ebe418b26231dba90da75126244e2fc4681ae8 Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Tue, 23 Dec 2025 23:12:21 +0300 Subject: [PATCH 32/45] [seaweedfs] Traffic locality for seaweedfs Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> (cherry picked from commit 2a8a8a480f57a2cadc0c8524782124ad3fea91e0) --- packages/system/seaweedfs/Makefile | 1 + .../seaweedfs/templates/s3/s3-service.yaml | 1 + .../patches/s3-traffic-distribution.patch | 12 ++++ packages/system/seaweedfs/values.yaml | 71 ++++++++++++++++--- 4 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 packages/system/seaweedfs/patches/s3-traffic-distribution.patch diff --git a/packages/system/seaweedfs/Makefile b/packages/system/seaweedfs/Makefile index 9d1f47cd..43f4053b 100644 --- a/packages/system/seaweedfs/Makefile +++ b/packages/system/seaweedfs/Makefile @@ -12,6 +12,7 @@ update: sed -i.bak "/ARG VERSION/ s|=.*|=$${version}|g" images/seaweedfs/Dockerfile && \ rm -f images/seaweedfs/Dockerfile.bak patch --no-backup-if-mismatch -p4 < patches/resize-api-server-annotation.diff + patch --no-backup-if-mismatch -p4 < patches/s3-traffic-distribution.patch #patch --no-backup-if-mismatch -p4 < patches/retention-policy-delete.yaml image: diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml index 8afd4865..86e0424e 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml @@ -14,6 +14,7 @@ metadata: {{- toYaml .Values.s3.annotations | nindent 4 }} {{- end }} spec: + trafficDistribution: PreferClose internalTrafficPolicy: {{ .Values.s3.internalTrafficPolicy | default "Cluster" }} ports: - name: "swfs-s3" diff --git a/packages/system/seaweedfs/patches/s3-traffic-distribution.patch b/packages/system/seaweedfs/patches/s3-traffic-distribution.patch new file mode 100644 index 00000000..93c72384 --- /dev/null +++ b/packages/system/seaweedfs/patches/s3-traffic-distribution.patch @@ -0,0 +1,12 @@ +diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml +index 8afd4865..86e0424e 100644 +--- a/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml ++++ b/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml +@@ -14,6 +14,7 @@ metadata: + {{- toYaml .Values.s3.annotations | nindent 4 }} + {{- end }} + spec: ++ trafficDistribution: PreferClose + internalTrafficPolicy: {{ .Values.s3.internalTrafficPolicy | default "Cluster" }} + ports: + - name: "swfs-s3" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 569493c3..c49b2252 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -1,16 +1,12 @@ global: enableSecurity: true serviceAccountName: "tenant-foo-seaweedfs" - imageName: "seaweedfs" extraEnvironmentVars: WEED_CLUSTER_SW_MASTER: "seaweedfs-master:9333" WEED_CLUSTER_SW_FILER: "seaweedfs-filer-client:8888" monitoring: enabled: true seaweedfs: - image: - tag: "latest@sha256:944e9bff98b088773847270238b63ce57dc5291054814d08e0226a139b3affb2" - registry: ghcr.io/cozystack/cozystack master: volumeSizeLimitMB: 30000 replicas: 3 @@ -75,7 +71,7 @@ seaweedfs: name: seaweedfs-db-app s3: replicas: 2 - enabled: true + enabled: false port: 8333 httpsPort: 0 # Suffix of the host name, {bucket}.{domainName} @@ -87,20 +83,24 @@ seaweedfs: existingConfigSecret: null auditLogConfig: {} s3: - enabled: false + enabled: true + replicas: 2 + port: 8333 extraArgs: - -idleTimeout=60 enableAuth: false readinessProbe: - scheme: HTTPS + httpGet: + scheme: HTTPS livenessProbe: - scheme: HTTPS + httpGet: + scheme: HTTPS logs: type: "" ingress: enabled: true className: "tenant-root" - host: "seaweedfs2.demo.cozystack.io" + host: "seaweedfs.demo.cozystack.io" annotations: nginx.ingress.kubernetes.io/proxy-body-size: "0" nginx.ingress.kubernetes.io/proxy-buffering: "off" @@ -110,12 +110,65 @@ seaweedfs: nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" nginx.ingress.kubernetes.io/client-body-timeout: "3600" nginx.ingress.kubernetes.io/client-header-timeout: "120" + nginx.ingress.kubernetes.io/service-upstream: "true" acme.cert-manager.io/http01-ingress-class: tenant-root cert-manager.io/cluster-issuer: letsencrypt-prod tls: - hosts: - seaweedfs.demo.cozystack.io secretName: seaweedfs-s3-ingress-tls + affinity: | + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 10 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - '{{ template "seaweedfs.name" . }}' + - key: app.kubernetes.io/instance + operator: In + values: + - '{{ .Release.Name }}' + - key: app.kubernetes.io/component + operator: In + values: + - s3 + topologyKey: kubernetes.io/hostname + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - '{{ template "seaweedfs.name" . }}' + - key: app.kubernetes.io/instance + operator: In + values: + - '{{ .Release.Name }}' + - key: app.kubernetes.io/component + operator: In + values: + - s3 + topologyKey: topology.kubernetes.io/zone + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 20 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - ingress-nginx + - key: app.kubernetes.io/component + operator: In + values: + - controller + topologyKey: kubernetes.io/hostname cosi: enabled: true podLabels: From 765cffdc6612f5c41d906a5580ab18499f3c465a Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:24:16 +0300 Subject: [PATCH 33/45] [seaweed] Update to 4.05 Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> (cherry picked from commit aede1b9217e9a346c025add2ac98276264dbbd6c) --- .../seaweedfs/charts/seaweedfs/Chart.yaml | 4 +- .../seaweedfs/charts/seaweedfs/README.md | 185 ++++++++++ .../seaweedfs-grafana-dashboard.json | 304 ++++++++++++++- .../templates/admin/admin-ingress.yaml | 52 +++ .../templates/admin/admin-secret.yaml | 20 + .../templates/admin/admin-service.yaml | 39 ++ .../templates/admin/admin-servicemonitor.yaml | 33 ++ .../templates/admin/admin-statefulset.yaml | 345 ++++++++++++++++++ .../seaweedfs/templates/cert/admin-cert.yaml | 43 +++ .../seaweedfs/templates/cert/worker-cert.yaml | 43 +++ .../templates/filer/filer-statefulset.yaml | 2 +- .../templates/master/master-statefulset.yaml | 2 +- .../seaweedfs/templates/s3/s3-ingress.yaml | 28 +- .../seaweedfs/templates/shared/_helpers.tpl | 54 +++ .../templates/shared/security-configmap.yaml | 8 + .../templates/volume/volume-statefulset.yaml | 5 +- .../templates/worker/worker-deployment.yaml | 288 +++++++++++++++ .../templates/worker/worker-service.yaml | 26 ++ .../worker/worker-servicemonitor.yaml | 33 ++ .../seaweedfs/charts/seaweedfs/values.yaml | 247 ++++++++++++- 20 files changed, 1750 insertions(+), 11 deletions(-) create mode 100644 packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-ingress.yaml create mode 100644 packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-secret.yaml create mode 100644 packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-service.yaml create mode 100644 packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-servicemonitor.yaml create mode 100644 packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-statefulset.yaml create mode 100644 packages/system/seaweedfs/charts/seaweedfs/templates/cert/admin-cert.yaml create mode 100644 packages/system/seaweedfs/charts/seaweedfs/templates/cert/worker-cert.yaml create mode 100644 packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-deployment.yaml create mode 100644 packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-service.yaml create mode 100644 packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-servicemonitor.yaml diff --git a/packages/system/seaweedfs/charts/seaweedfs/Chart.yaml b/packages/system/seaweedfs/charts/seaweedfs/Chart.yaml index 6ea31115..107149cf 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/Chart.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v1 description: SeaweedFS name: seaweedfs -appVersion: "4.02" +appVersion: "4.05" # Dev note: Trigger a helm chart release by `git tag -a helm-` -version: 4.0.402 +version: 4.0.405 diff --git a/packages/system/seaweedfs/charts/seaweedfs/README.md b/packages/system/seaweedfs/charts/seaweedfs/README.md index 30885aee..7f27cb22 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/README.md +++ b/packages/system/seaweedfs/charts/seaweedfs/README.md @@ -145,6 +145,191 @@ stringData: seaweedfs_s3_config: '{"identities":[{"name":"anvAdmin","credentials":[{"accessKey":"snu8yoP6QAlY0ne4","secretKey":"PNzBcmeLNEdR0oviwm04NQAicOrDH1Km"}],"actions":["Admin","Read","Write"]},{"name":"anvReadOnly","credentials":[{"accessKey":"SCigFee6c5lbi04A","secretKey":"kgFhbT38R8WUYVtiFQ1OiSVOrYr3NKku"}],"actions":["Read"]}]}' ``` +## Admin Component + +The admin component provides a modern web-based administration interface for managing SeaweedFS clusters. It includes: + +- **Dashboard**: Real-time cluster status and metrics +- **Volume Management**: Monitor volume servers, capacity, and health +- **File Browser**: Browse and manage files in the filer +- **Maintenance Operations**: Trigger maintenance tasks via workers +- **Object Store Management**: Create and manage buckets with web interface + +### Enabling Admin + +To enable the admin interface, add the following to your values.yaml: + +```yaml +admin: + enabled: true + port: 23646 + grpcPort: 33646 # For worker connections + adminUser: "admin" + adminPassword: "your-secure-password" # Leave empty to disable auth + + # Optional: persist admin data + data: + type: "persistentVolumeClaim" + size: "10Gi" + storageClass: "your-storage-class" + + # Optional: enable ingress + ingress: + enabled: true + host: "admin.seaweedfs.local" + className: "nginx" +``` + +The admin interface will be available at `http://:23646` (or via ingress). Workers connect to the admin server via gRPC on port `33646`. + +### Admin Authentication + +If `adminPassword` is set, the admin interface requires authentication: +- Username: Value of `adminUser` (default: `admin`) +- Password: Value of `adminPassword` + +If `adminPassword` is empty or not set, the admin interface runs without authentication (not recommended for production). + +### Admin Data Persistence + +The admin component can store configuration and maintenance data. You can configure storage in several ways: + +- **emptyDir** (default): Data is lost when pod restarts +- **persistentVolumeClaim**: Data persists across pod restarts +- **hostPath**: Data stored on the host filesystem +- **existingClaim**: Use an existing PVC + +## Worker Component + +Workers are maintenance agents that execute cluster maintenance tasks such as vacuum, volume balancing, and erasure coding. Workers connect to the admin server via gRPC and receive task assignments. + +### Enabling Workers + +To enable workers, add the following to your values.yaml: + +```yaml +worker: + enabled: true + replicas: 2 # Scale based on workload + capabilities: "vacuum,balance,erasure_coding" # Tasks this worker can handle + maxConcurrent: 3 # Maximum concurrent tasks per worker + + # Working directory for task execution + # Default: "/tmp/seaweedfs-worker" + # Note: /tmp is ephemeral - use persistent storage (hostPath/existingClaim) for long-running tasks + workingDir: "/tmp/seaweedfs-worker" + + # Optional: configure admin server address + # If not specified, auto-discovers from admin service in the same namespace by looking for + # a service named "-admin" (e.g., "seaweedfs-admin"). + # Auto-discovery only works if the admin is in the same namespace and same Helm release. + # For cross-namespace or separate release scenarios, explicitly set this value. + # Example: If main SeaweedFS is deployed in "production" namespace: + # adminServer: "seaweedfs-admin.production.svc:33646" + adminServer: "" + + # Workers need storage for task execution + # Note: Workers use a Deployment, which does not support `volumeClaimTemplates` + # for dynamic PVC creation per pod. To use persistent storage, you must + # pre-provision a PersistentVolumeClaim and use `type: "existingClaim"`. + data: + type: "emptyDir" # Options: "emptyDir", "hostPath", or "existingClaim" + hostPathPrefix: /storage # For hostPath + # claimName: "worker-pvc" # For existingClaim with pre-provisioned PVC + + # Resource limits for worker pods + resources: + requests: + cpu: "500m" + memory: "512Mi" + limits: + cpu: "2" + memory: "2Gi" +``` + +### Worker Capabilities + +Workers can be configured with different capabilities: +- **vacuum**: Reclaim deleted file space +- **balance**: Balance volumes across volume servers +- **erasure_coding**: Handle erasure coding operations + +You can configure workers with all capabilities or create specialized worker pools with specific capabilities. + +### Worker Deployment Strategy + +For production deployments, consider: + +1. **Multiple Workers**: Deploy 2+ worker replicas for high availability +2. **Resource Allocation**: Workers need sufficient CPU/memory for maintenance tasks +3. **Storage**: Workers need temporary storage for vacuum and balance operations (size depends on volume size) +4. **Specialized Workers**: Create separate worker deployments for different capabilities if needed + +Example specialized worker configuration: + +For specialized worker pools, deploy separate Helm releases with different capabilities: + +**values-worker-vacuum.yaml** (for vacuum operations): +```yaml +# Disable all other components, enable only workers +master: + enabled: false +volume: + enabled: false +filer: + enabled: false +s3: + enabled: false +admin: + enabled: false + +worker: + enabled: true + replicas: 2 + capabilities: "vacuum" + maxConcurrent: 2 + # REQUIRED: Point to the admin service of your main SeaweedFS release + # Replace with the namespace where your main seaweedfs is deployed + # Example: If deploying in namespace "production": + # adminServer: "seaweedfs-admin.production.svc:33646" + adminServer: "seaweedfs-admin..svc:33646" +``` + +**values-worker-balance.yaml** (for balance operations): +```yaml +# Disable all other components, enable only workers +master: + enabled: false +volume: + enabled: false +filer: + enabled: false +s3: + enabled: false +admin: + enabled: false + +worker: + enabled: true + replicas: 1 + capabilities: "balance" + maxConcurrent: 1 + # REQUIRED: Point to the admin service of your main SeaweedFS release + # Replace with the namespace where your main seaweedfs is deployed + # Example: If deploying in namespace "production": + # adminServer: "seaweedfs-admin.production.svc:33646" + adminServer: "seaweedfs-admin..svc:33646" +``` + +Deploy the specialized workers as separate releases: +```bash +# Deploy vacuum workers +helm install seaweedfs-worker-vacuum seaweedfs/seaweedfs -f values-worker-vacuum.yaml + +# Deploy balance workers +helm install seaweedfs-worker-balance seaweedfs/seaweedfs -f values-worker-balance.yaml +``` + ## Enterprise For enterprise users, please visit [seaweedfs.com](https://seaweedfs.com) for the SeaweedFS Enterprise Edition, diff --git a/packages/system/seaweedfs/charts/seaweedfs/dashboards/seaweedfs-grafana-dashboard.json b/packages/system/seaweedfs/charts/seaweedfs/dashboards/seaweedfs-grafana-dashboard.json index 30b43f86..ca08b7d0 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/dashboards/seaweedfs-grafana-dashboard.json +++ b/packages/system/seaweedfs/charts/seaweedfs/dashboards/seaweedfs-grafana-dashboard.json @@ -1595,6 +1595,101 @@ "title": "S3 Bucket Traffic Sent", "type": "timeseries" }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 41 + }, + "id": 86, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "expr": "sum(rate(SeaweedFS_s3_request_total{namespace=\"$NAMESPACE\"}[$__interval])) by (bucket)", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "{{bucket}}", + "refId": "A" + } + ], + "title": "S3 API Calls per Bucket", + "type": "timeseries" + }, { "datasource": { "type": "prometheus", @@ -1659,8 +1754,8 @@ }, "gridPos": { "h": 7, - "w": 24, - "x": 0, + "w": 12, + "x": 12, "y": 41 }, "id": 72, @@ -3266,6 +3361,209 @@ ], "title": "Filer Go Routines", "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 48 + }, + "id": 89, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "max(SeaweedFS_s3_bucket_size_bytes{namespace=\"$NAMESPACE\"}) by (bucket)", + "legendFormat": "{{bucket}} (logical)", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "max(SeaweedFS_s3_bucket_physical_size_bytes{namespace=\"$NAMESPACE\"}) by (bucket)", + "legendFormat": "{{bucket}} (physical)", + "range": true, + "refId": "B" + } + ], + "title": "S3 Bucket Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 48 + }, + "id": 90, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "max(SeaweedFS_s3_bucket_object_count{namespace=\"$NAMESPACE\"}) by (bucket)", + "legendFormat": "{{bucket}}", + "range": true, + "refId": "A" + } + ], + "title": "S3 Bucket Object Count", + "type": "timeseries" } ], "refresh": "", @@ -3356,4 +3654,4 @@ "uid": "a24009d7-cbda-4443-a132-1cc1c4677304", "version": 1, "weekStart": "" -} +} \ No newline at end of file diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-ingress.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-ingress.yaml new file mode 100644 index 00000000..216ef8a8 --- /dev/null +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-ingress.yaml @@ -0,0 +1,52 @@ +{{- if and .Values.admin.enabled .Values.admin.ingress.enabled }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion }} +apiVersion: networking.k8s.io/v1beta1 +{{- else }} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: ingress-{{ template "seaweedfs.name" . }}-admin + namespace: {{ .Release.Namespace }} + annotations: + {{- if and (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) .Values.admin.ingress.className }} + kubernetes.io/ingress.class: {{ .Values.admin.ingress.className }} + {{- end }} + {{- with .Values.admin.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: admin +spec: + {{- if and (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) .Values.admin.ingress.className }} + ingressClassName: {{ .Values.admin.ingress.className | quote }} + {{- end }} + tls: + {{ .Values.admin.ingress.tls | default list | toYaml | nindent 6}} + rules: + - {{- if .Values.admin.ingress.host }} + host: {{ .Values.admin.ingress.host | quote }} + {{- end }} + http: + paths: + - path: {{ .Values.admin.ingress.path | quote }} + {{- if semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion }} + pathType: {{ .Values.admin.ingress.pathType | quote }} + {{- end }} + backend: +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} + service: + name: {{ template "seaweedfs.name" . }}-admin + port: + number: {{ .Values.admin.port }} +{{- else }} + serviceName: {{ template "seaweedfs.name" . }}-admin + servicePort: {{ .Values.admin.port }} +{{- end }} +{{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-secret.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-secret.yaml new file mode 100644 index 00000000..bc104456 --- /dev/null +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-secret.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.admin.enabled .Values.admin.secret.adminPassword (not .Values.admin.secret.existingSecret) }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ template "seaweedfs.name" . }}-admin-secret + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/resource-policy": keep + "helm.sh/hook": "pre-install,pre-upgrade" + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: admin +data: + adminUser: {{ .Values.admin.secret.adminUser | b64enc }} + adminPassword: {{ .Values.admin.secret.adminPassword | b64enc }} +{{- end}} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-service.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-service.yaml new file mode 100644 index 00000000..825049a4 --- /dev/null +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-service.yaml @@ -0,0 +1,39 @@ +{{- if .Values.admin.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "seaweedfs.name" . }}-admin + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: admin +{{- if .Values.admin.service.annotations }} + annotations: + {{- toYaml .Values.admin.service.annotations | nindent 4 }} +{{- end }} +spec: + type: {{ .Values.admin.service.type }} + ports: + - name: "http" + port: {{ .Values.admin.port }} + targetPort: {{ .Values.admin.port }} + protocol: TCP + - name: "grpc" + port: {{ .Values.admin.grpcPort }} + targetPort: {{ .Values.admin.grpcPort }} + protocol: TCP + {{- if .Values.admin.metricsPort }} + - name: "metrics" + port: {{ .Values.admin.metricsPort }} + targetPort: {{ .Values.admin.metricsPort }} + protocol: TCP + {{- end }} + selector: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: admin +{{- end }} + diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-servicemonitor.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-servicemonitor.yaml new file mode 100644 index 00000000..271197a3 --- /dev/null +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-servicemonitor.yaml @@ -0,0 +1,33 @@ +{{- if .Values.admin.enabled }} +{{- if .Values.admin.metricsPort }} +{{- if .Values.global.monitoring.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "seaweedfs.name" . }}-admin + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: admin + {{- with .Values.global.monitoring.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- with .Values.admin.serviceMonitor.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + endpoints: + - interval: 30s + port: metrics + scrapeTimeout: 5s + selector: + matchLabels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + app.kubernetes.io/component: admin +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-statefulset.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-statefulset.yaml new file mode 100644 index 00000000..208f73d1 --- /dev/null +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/admin/admin-statefulset.yaml @@ -0,0 +1,345 @@ +{{- if .Values.admin.enabled }} +{{- if and (not .Values.admin.masters) (not .Values.global.masterServer) (not .Values.master.enabled) }} +{{- fail "admin.masters or global.masterServer must be set if master.enabled is false" -}} +{{- end }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "seaweedfs.name" . }}-admin + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: admin +{{- if .Values.admin.annotations }} + annotations: + {{- toYaml .Values.admin.annotations | nindent 4 }} +{{- end }} +spec: + serviceName: {{ template "seaweedfs.name" . }}-admin + podManagementPolicy: {{ .Values.admin.podManagementPolicy }} + replicas: {{ .Values.admin.replicas }} + selector: + matchLabels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: admin + template: + metadata: + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: admin + {{ with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.admin.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + {{ with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.admin.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: {{ default .Values.global.restartPolicy .Values.admin.restartPolicy }} + {{- if .Values.admin.affinity }} + affinity: + {{ tpl .Values.admin.affinity . | nindent 8 | trim }} + {{- end }} + {{- if .Values.admin.topologySpreadConstraints }} + topologySpreadConstraints: + {{ tpl .Values.admin.topologySpreadConstraints . | nindent 8 | trim }} + {{- end }} + {{- if .Values.admin.tolerations }} + tolerations: + {{ tpl .Values.admin.tolerations . | nindent 8 | trim }} + {{- end }} + {{- include "seaweedfs.imagePullSecrets" . | nindent 6 }} + terminationGracePeriodSeconds: 30 + {{- if .Values.admin.priorityClassName }} + priorityClassName: {{ .Values.admin.priorityClassName | quote }} + {{- end }} + enableServiceLinks: false + {{- if .Values.admin.serviceAccountName }} + serviceAccountName: {{ .Values.admin.serviceAccountName | quote }} + {{- end }} + {{- if .Values.admin.initContainers }} + initContainers: + {{ tpl .Values.admin.initContainers . | nindent 8 | trim }} + {{- end }} + {{- if .Values.admin.podSecurityContext.enabled }} + securityContext: {{- omit .Values.admin.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + containers: + - name: seaweedfs + image: {{ template "admin.image" . }} + imagePullPolicy: {{ default "IfNotPresent" .Values.global.imagePullPolicy }} + {{- $adminAuthEnabled := or .Values.admin.secret.existingSecret .Values.admin.secret.adminPassword }} + {{- if and .Values.admin.secret.existingSecret (not .Values.admin.secret.userKey) -}} + {{- fail "admin.secret.userKey must be set when admin.secret.existingSecret is provided" -}} + {{- end -}} + {{- if and .Values.admin.secret.existingSecret (not .Values.admin.secret.pwKey) -}} + {{- fail "admin.secret.pwKey must be set when admin.secret.existingSecret is provided" -}} + {{- end -}} + {{- $adminSecretName := .Values.admin.secret.existingSecret | default (printf "%s-admin-secret" (include "seaweedfs.name" .)) }} + env: + {{- if $adminAuthEnabled }} + - name: SEAWEEDFS_ADMIN_USER + valueFrom: + secretKeyRef: + name: {{ $adminSecretName }} + key: {{ if .Values.admin.secret.existingSecret }}{{ .Values.admin.secret.userKey }}{{ else }}adminUser{{ end }} + - name: SEAWEEDFS_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ $adminSecretName }} + key: {{ if .Values.admin.secret.existingSecret }}{{ .Values.admin.secret.pwKey }}{{ else }}adminPassword{{ end }} + {{- end }} + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: SEAWEEDFS_FULLNAME + value: "{{ template "seaweedfs.name" . }}" + {{- if .Values.admin.extraEnvironmentVars }} + {{- range $key, $value := .Values.admin.extraEnvironmentVars }} + - name: {{ $key }} + {{- if kindIs "string" $value }} + value: {{ $value | quote }} + {{- else }} + valueFrom: + {{ toYaml $value | nindent 16 | trim }} + {{- end -}} + {{- end }} + {{- end }} + {{- if .Values.global.extraEnvironmentVars }} + {{- range $key, $value := .Values.global.extraEnvironmentVars }} + - name: {{ $key }} + {{- if kindIs "string" $value }} + value: {{ $value | quote }} + {{- else }} + valueFrom: + {{ toYaml $value | nindent 16 | trim }} + {{- end -}} + {{- end }} + {{- end }} + command: + - "/bin/sh" + - "-ec" + - | + exec /usr/bin/weed \ + {{- if or (eq .Values.admin.logs.type "hostPath") (eq .Values.admin.logs.type "persistentVolumeClaim") (eq .Values.admin.logs.type "emptyDir") (eq .Values.admin.logs.type "existingClaim") }} + -logdir=/logs \ + {{- else }} + -logtostderr=true \ + {{- end }} + {{- if .Values.admin.loggingOverrideLevel }} + -v={{ .Values.admin.loggingOverrideLevel }} \ + {{- else }} + -v={{ .Values.global.loggingLevel }} \ + {{- end }} + admin \ + -port={{ .Values.admin.port }} \ + -port.grpc={{ .Values.admin.grpcPort }} \ + {{- if or (eq .Values.admin.data.type "hostPath") (eq .Values.admin.data.type "persistentVolumeClaim") (eq .Values.admin.data.type "emptyDir") (eq .Values.admin.data.type "existingClaim") }} + -dataDir=/data \ + {{- else if .Values.admin.dataDir }} + -dataDir={{ .Values.admin.dataDir }} \ + {{- end }} + {{- if $adminAuthEnabled }} + -adminUser="${SEAWEEDFS_ADMIN_USER}" \ + -adminPassword="${SEAWEEDFS_ADMIN_PASSWORD}" \ + {{- end }} + {{- if .Values.admin.masters }} + -masters={{ .Values.admin.masters }}{{- if .Values.admin.extraArgs }} \{{ end }} + {{- else if .Values.global.masterServer }} + -masters={{ .Values.global.masterServer }}{{- if .Values.admin.extraArgs }} \{{ end }} + {{- else }} + -masters={{ range $index := until (.Values.master.replicas | int) }}${SEAWEEDFS_FULLNAME}-master-{{ $index }}.${SEAWEEDFS_FULLNAME}-master.{{ $.Release.Namespace }}:{{ $.Values.master.port }}{{ if lt $index (sub ($.Values.master.replicas | int) 1) }},{{ end }}{{ end }}{{- if .Values.admin.extraArgs }} \{{ end }} + {{- end }} + {{- range $index, $arg := .Values.admin.extraArgs }} + {{ $arg }}{{- if lt $index (sub (len $.Values.admin.extraArgs) 1) }} \{{ end }} + {{- end }} + volumeMounts: + {{- if or (eq .Values.admin.data.type "hostPath") (eq .Values.admin.data.type "persistentVolumeClaim") (eq .Values.admin.data.type "emptyDir") (eq .Values.admin.data.type "existingClaim") }} + - name: admin-data + mountPath: /data + {{- end }} + {{- if or (eq .Values.admin.logs.type "hostPath") (eq .Values.admin.logs.type "persistentVolumeClaim") (eq .Values.admin.logs.type "emptyDir") (eq .Values.admin.logs.type "existingClaim") }} + - name: admin-logs + mountPath: /logs + {{- end }} + {{- if .Values.global.enableSecurity }} + - name: security-config + readOnly: true + mountPath: /etc/seaweedfs/security.toml + subPath: security.toml + - name: ca-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/ca/ + - name: master-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/master/ + - name: volume-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/volume/ + - name: filer-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/filer/ + - name: client-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/client/ + - name: admin-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/admin/ + {{- end }} + {{ tpl .Values.admin.extraVolumeMounts . | nindent 12 | trim }} + ports: + - containerPort: {{ .Values.admin.port }} + name: http + - containerPort: {{ .Values.admin.grpcPort }} + name: grpc + {{- if .Values.admin.metricsPort }} + - containerPort: {{ .Values.admin.metricsPort }} + name: metrics + {{- end }} + {{- if .Values.admin.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.admin.readinessProbe.httpGet.path }} + port: http + scheme: {{ .Values.admin.readinessProbe.httpGet.scheme }} + initialDelaySeconds: {{ .Values.admin.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.admin.readinessProbe.periodSeconds }} + successThreshold: {{ .Values.admin.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.admin.readinessProbe.failureThreshold }} + timeoutSeconds: {{ .Values.admin.readinessProbe.timeoutSeconds }} + {{- end }} + {{- if .Values.admin.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.admin.livenessProbe.httpGet.path }} + port: http + scheme: {{ .Values.admin.livenessProbe.httpGet.scheme }} + initialDelaySeconds: {{ .Values.admin.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.admin.livenessProbe.periodSeconds }} + successThreshold: {{ .Values.admin.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.admin.livenessProbe.failureThreshold }} + timeoutSeconds: {{ .Values.admin.livenessProbe.timeoutSeconds }} + {{- end }} + {{- with .Values.admin.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.admin.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.admin.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.admin.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.admin.sidecars "context" $) | nindent 8 }} + {{- end }} + volumes: + {{- if eq .Values.admin.data.type "hostPath" }} + - name: admin-data + hostPath: + path: {{ .Values.admin.data.hostPathPrefix }}/seaweedfs-admin-data + type: DirectoryOrCreate + {{- end }} + {{- if eq .Values.admin.data.type "emptyDir" }} + - name: admin-data + emptyDir: {} + {{- end }} + {{- if eq .Values.admin.data.type "existingClaim" }} + - name: admin-data + persistentVolumeClaim: + claimName: {{ .Values.admin.data.claimName }} + {{- end }} + {{- if eq .Values.admin.logs.type "hostPath" }} + - name: admin-logs + hostPath: + path: {{ .Values.admin.logs.hostPathPrefix }}/logs/seaweedfs/admin + type: DirectoryOrCreate + {{- end }} + {{- if eq .Values.admin.logs.type "emptyDir" }} + - name: admin-logs + emptyDir: {} + {{- end }} + {{- if eq .Values.admin.logs.type "existingClaim" }} + - name: admin-logs + persistentVolumeClaim: + claimName: {{ .Values.admin.logs.claimName }} + {{- end }} + {{- if .Values.global.enableSecurity }} + - name: security-config + configMap: + name: {{ template "seaweedfs.name" . }}-security-config + - name: ca-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-ca-cert + - name: master-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-master-cert + - name: volume-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-volume-cert + - name: filer-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-filer-cert + - name: client-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-client-cert + - name: admin-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-admin-cert + {{- end }} + {{ tpl .Values.admin.extraVolumes . | indent 8 | trim }} + {{- if .Values.admin.nodeSelector }} + nodeSelector: + {{ tpl .Values.admin.nodeSelector . | indent 8 | trim }} + {{- end }} + {{- $pvc_exists := include "admin.pvc_exists" . -}} + {{- if $pvc_exists }} + volumeClaimTemplates: + {{- if eq .Values.admin.data.type "persistentVolumeClaim" }} + - metadata: + name: admin-data + {{- with .Values.admin.data.annotations }} + annotations: + {{- toYaml . | nindent 10 }} + {{- end }} + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: {{ .Values.admin.data.storageClass }} + resources: + requests: + storage: {{ .Values.admin.data.size }} + {{- end }} + {{- if eq .Values.admin.logs.type "persistentVolumeClaim" }} + - metadata: + name: admin-logs + {{- with .Values.admin.logs.annotations }} + annotations: + {{- toYaml . | nindent 10 }} + {{- end }} + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: {{ .Values.admin.logs.storageClass }} + resources: + requests: + storage: {{ .Values.admin.logs.size }} + {{- end }} + {{- end }} +{{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/cert/admin-cert.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/cert/admin-cert.yaml new file mode 100644 index 00000000..be526601 --- /dev/null +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/cert/admin-cert.yaml @@ -0,0 +1,43 @@ +{{- if and .Values.global.enableSecurity (not .Values.certificates.externalCertificates.enabled)}} +apiVersion: cert-manager.io/v1{{ if .Values.global.certificates.alphacrds }}alpha1{{ end }} +kind: Certificate +metadata: + name: {{ template "seaweedfs.name" . }}-admin-cert + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: admin + {{- if .Values.admin.annotations }} + annotations: + {{- toYaml .Values.admin.annotations | nindent 4 }} + {{- end }} +spec: + secretName: {{ template "seaweedfs.name" . }}-admin-cert + issuerRef: + name: {{ template "seaweedfs.name" . }}-ca-issuer + kind: Issuer + commonName: {{ .Values.certificates.commonName }} + subject: + organizations: + - "SeaweedFS CA" + dnsNames: + - '*.{{ template "seaweedfs.name" . }}-admin' + - '*.{{ template "seaweedfs.name" . }}-admin.{{ .Release.Namespace }}' + - '*.{{ template "seaweedfs.name" . }}-admin.{{ .Release.Namespace }}.svc' + - '*.{{ template "seaweedfs.name" . }}-admin.{{ .Release.Namespace }}.svc.cluster.local' +{{- if .Values.certificates.ipAddresses }} + ipAddresses: + {{- range .Values.certificates.ipAddresses }} + - {{ . }} + {{- end }} +{{- end }} + privateKey: + algorithm: {{ .Values.certificates.keyAlgorithm }} + size: {{ .Values.certificates.keySize }} + duration: {{ .Values.certificates.duration }} + renewBefore: {{ .Values.certificates.renewBefore }} +{{- end }} + diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/cert/worker-cert.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/cert/worker-cert.yaml new file mode 100644 index 00000000..85edeb33 --- /dev/null +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/cert/worker-cert.yaml @@ -0,0 +1,43 @@ +{{- if and .Values.global.enableSecurity (not .Values.certificates.externalCertificates.enabled)}} +apiVersion: cert-manager.io/v1{{ if .Values.global.certificates.alphacrds }}alpha1{{ end }} +kind: Certificate +metadata: + name: {{ template "seaweedfs.name" . }}-worker-cert + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: worker + {{- if .Values.worker.annotations }} + annotations: + {{- toYaml .Values.worker.annotations | nindent 4 }} + {{- end }} +spec: + secretName: {{ template "seaweedfs.name" . }}-worker-cert + issuerRef: + name: {{ template "seaweedfs.name" . }}-ca-issuer + kind: Issuer + commonName: {{ .Values.certificates.commonName }} + subject: + organizations: + - "SeaweedFS CA" + dnsNames: + - '*.{{ template "seaweedfs.name" . }}-worker' + - '*.{{ template "seaweedfs.name" . }}-worker.{{ .Release.Namespace }}' + - '*.{{ template "seaweedfs.name" . }}-worker.{{ .Release.Namespace }}.svc' + - '*.{{ template "seaweedfs.name" . }}-worker.{{ .Release.Namespace }}.svc.cluster.local' +{{- if .Values.certificates.ipAddresses }} + ipAddresses: + {{- range .Values.certificates.ipAddresses }} + - {{ . }} + {{- end }} +{{- end }} + privateKey: + algorithm: {{ .Values.certificates.keyAlgorithm }} + size: {{ .Values.certificates.keySize }} + duration: {{ .Values.certificates.duration }} + renewBefore: {{ .Values.certificates.renewBefore }} +{{- end }} + diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-statefulset.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-statefulset.yaml index 2b8c2744..e29239c3 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-statefulset.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/filer/filer-statefulset.yaml @@ -220,7 +220,7 @@ spec: -s3.auditLogConfig=/etc/sw/filer_s3_auditLogConfig.json \ {{- end }} {{- end }} - -master={{ if .Values.global.masterServer }}{{.Values.global.masterServer}}{{ else }}{{ range $index := until (.Values.master.replicas | int) }}${SEAWEEDFS_FULLNAME}-master-{{ $index }}.${SEAWEEDFS_FULLNAME}-master.{{ $.Release.Namespace }}:{{ $.Values.master.port }}{{ if lt $index (sub ($.Values.master.replicas | int) 1) }},{{ end }}{{ end }}{{ end }} \ + -master={{ include "seaweedfs.masterServerArg" . }} \ {{- range .Values.filer.extraArgs }} {{ . }} \ {{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/master/master-statefulset.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/master/master-statefulset.yaml index a7067345..50e0e97d 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/master/master-statefulset.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/master/master-statefulset.yaml @@ -184,7 +184,7 @@ spec: -garbageThreshold={{ .Values.master.garbageThreshold }} \ {{- end }} -ip=${POD_NAME}.${SEAWEEDFS_FULLNAME}-master.{{ .Release.Namespace }} \ - -peers={{ range $index := until (.Values.master.replicas | int) }}${SEAWEEDFS_FULLNAME}-master-{{ $index }}.${SEAWEEDFS_FULLNAME}-master.{{ $.Release.Namespace }}:{{ $.Values.master.port }}{{ if lt $index (sub ($.Values.master.replicas | int) 1) }},{{ end }}{{ end }} \ + -peers={{ include "seaweedfs.masterServers" . }} \ {{- range .Values.master.extraArgs }} {{ . }} \ {{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-ingress.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-ingress.yaml index 899773ae..e884f4fc 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-ingress.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-ingress.yaml @@ -4,6 +4,13 @@ {{- /* Determine service name based on deployment mode */}} {{- $serviceName := ternary (printf "%s-all-in-one" (include "seaweedfs.name" .)) (printf "%s-s3" (include "seaweedfs.name" .)) .Values.allInOne.enabled }} {{- $s3Port := .Values.allInOne.s3.port | default .Values.s3.port }} +{{- /* Build hosts list - support both legacy .host (string) and new .hosts (array) for backwards compatibility */}} +{{- $hosts := list }} +{{- if kindIs "slice" .Values.s3.ingress.host }} + {{- $hosts = .Values.s3.ingress.host }} +{{- else if .Values.s3.ingress.host }} + {{- $hosts = list .Values.s3.ingress.host }} +{{- end }} {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} apiVersion: networking.k8s.io/v1 {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion }} @@ -30,6 +37,25 @@ spec: tls: {{ .Values.s3.ingress.tls | default list | toYaml | nindent 6}} rules: +{{- if $hosts }} +{{- range $hosts }} + - host: {{ . | quote }} + http: + paths: + - path: {{ $.Values.s3.ingress.path | quote }} + pathType: {{ $.Values.s3.ingress.pathType | quote }} + backend: +{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $serviceName }} + port: + number: {{ $s3Port }} +{{- else }} + serviceName: {{ $serviceName }} + servicePort: {{ $s3Port }} +{{- end }} +{{- end }} +{{- else }} - http: paths: - path: {{ .Values.s3.ingress.path | quote }} @@ -44,7 +70,5 @@ spec: serviceName: {{ $serviceName }} servicePort: {{ $s3Port }} {{- end }} -{{- if .Values.s3.ingress.host }} - host: {{ .Values.s3.ingress.host | quote }} {{- end }} {{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/shared/_helpers.tpl b/packages/system/seaweedfs/charts/seaweedfs/templates/shared/_helpers.tpl index d22d1422..557bb9d4 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/shared/_helpers.tpl +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/shared/_helpers.tpl @@ -83,6 +83,26 @@ Inject extra environment vars in the format key:value, if populated {{- end -}} {{- end -}} +{{/* Return the proper admin image */}} +{{- define "admin.image" -}} +{{- if .Values.admin.imageOverride -}} +{{- $imageOverride := .Values.admin.imageOverride -}} +{{- printf "%s" $imageOverride -}} +{{- else -}} +{{- include "common.image" . }} +{{- end -}} +{{- end -}} + +{{/* Return the proper worker image */}} +{{- define "worker.image" -}} +{{- if .Values.worker.imageOverride -}} +{{- $imageOverride := .Values.worker.imageOverride -}} +{{- printf "%s" $imageOverride -}} +{{- else -}} +{{- include "common.image" . }} +{{- end -}} +{{- end -}} + {{/* Return the proper volume image */}} {{- define "volume.image" -}} {{- if .Values.volume.imageOverride -}} @@ -136,6 +156,15 @@ Inject extra environment vars in the format key:value, if populated {{- end -}} {{- end -}} +{{/* check if any Admin PVC exists */}} +{{- define "admin.pvc_exists" -}} +{{- if or (eq .Values.admin.data.type "persistentVolumeClaim") (eq .Values.admin.logs.type "persistentVolumeClaim") -}} +{{- printf "true" -}} +{{- else -}} +{{- printf "" -}} +{{- end -}} +{{- end -}} + {{/* check if any InitContainers exist for Volumes */}} {{- define "volume.initContainers_exists" -}} {{- if or (not (empty .Values.volume.idx )) (not (empty .Values.volume.initContainers )) -}} @@ -246,3 +275,28 @@ If allInOne is enabled, point to the all-in-one service; otherwise, point to the {{- end -}} {{- printf "%s%s.%s:%d" (include "seaweedfs.name" .) $serviceNameSuffix .Release.Namespace (int .Values.filer.port) -}} {{- end -}} + +{{/* +Generate comma-separated list of master server addresses. +Usage: {{ include "seaweedfs.masterServers" . }} +Output example: ${SEAWEEDFS_FULLNAME}-master-0.${SEAWEEDFS_FULLNAME}-master.namespace:9333,${SEAWEEDFS_FULLNAME}-master-1... +*/}} +{{- define "seaweedfs.masterServers" -}} +{{- $fullname := include "seaweedfs.name" . -}} +{{- range $index := until (.Values.master.replicas | int) -}} +{{- if $index }},{{ end -}} +${SEAWEEDFS_FULLNAME}-master-{{ $index }}.${SEAWEEDFS_FULLNAME}-master.{{ $.Release.Namespace }}:{{ $.Values.master.port }} +{{- end -}} +{{- end -}} + +{{/* +Generate master server argument value, using global.masterServer if set, otherwise the generated list. +Usage: {{ include "seaweedfs.masterServerArg" . }} +*/}} +{{- define "seaweedfs.masterServerArg" -}} +{{- if .Values.global.masterServer -}} +{{- .Values.global.masterServer -}} +{{- else -}} +{{- include "seaweedfs.masterServers" . -}} +{{- end -}} +{{- end -}} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/shared/security-configmap.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/shared/security-configmap.yaml index 6f229c59..f7fb69ea 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/shared/security-configmap.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/shared/security-configmap.yaml @@ -65,6 +65,14 @@ data: cert = "/usr/local/share/ca-certificates/filer/tls.crt" key = "/usr/local/share/ca-certificates/filer/tls.key" + [grpc.admin] + cert = "/usr/local/share/ca-certificates/admin/tls.crt" + key = "/usr/local/share/ca-certificates/admin/tls.key" + + [grpc.worker] + cert = "/usr/local/share/ca-certificates/worker/tls.crt" + key = "/usr/local/share/ca-certificates/worker/tls.key" + # use this for any place needs a grpc client # i.e., "weed backup|benchmark|filer.copy|filer.replicate|mount|s3|upload" [grpc.client] diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-statefulset.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-statefulset.yaml index 1a8964a5..045b95c2 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-statefulset.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/volume/volume-statefulset.yaml @@ -176,6 +176,9 @@ spec: {{- if $volume.dataCenter }} -dataCenter={{ $volume.dataCenter }} \ {{- end }} + {{- if $volume.id }} + -id={{ $volume.id }} \ + {{- end }} -ip.bind={{ $volume.ipBind }} \ -readMode={{ $volume.readMode }} \ {{- if $volume.whiteList }} @@ -196,7 +199,7 @@ spec: -minFreeSpacePercent={{ $volume.minFreeSpacePercent }} \ -ip=${POD_NAME}.${SEAWEEDFS_FULLNAME}-{{ $volumeName }}.{{ $.Release.Namespace }} \ -compactionMBps={{ $volume.compactionMBps }} \ - -mserver={{ if $.Values.global.masterServer }}{{ $.Values.global.masterServer}}{{ else }}{{ range $index := until ($.Values.master.replicas | int) }}${SEAWEEDFS_FULLNAME}-master-{{ $index }}.${SEAWEEDFS_FULLNAME}-master.{{ $.Release.Namespace }}:{{ $.Values.master.port }}{{ if lt $index (sub ($.Values.master.replicas | int) 1) }},{{ end }}{{ end }}{{ end }} + -master={{ include "seaweedfs.masterServerArg" $ }} \ {{- range $volume.extraArgs }} {{ . }} \ {{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-deployment.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-deployment.yaml new file mode 100644 index 00000000..7a6e0524 --- /dev/null +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-deployment.yaml @@ -0,0 +1,288 @@ +{{- if .Values.worker.enabled }} +{{- if and (not .Values.worker.adminServer) (not .Values.admin.enabled) }} +{{- fail "worker.adminServer must be set if admin.enabled is false within the same release" -}} +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "seaweedfs.name" . }}-worker + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: worker +{{- if .Values.worker.annotations }} + annotations: + {{- toYaml .Values.worker.annotations | nindent 4 }} +{{- end }} +spec: + replicas: {{ .Values.worker.replicas }} + selector: + matchLabels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: worker + template: + metadata: + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: worker + {{ with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.worker.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + {{ with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.worker.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: {{ default .Values.global.restartPolicy .Values.worker.restartPolicy }} + {{- if .Values.worker.affinity }} + affinity: + {{ tpl .Values.worker.affinity . | nindent 8 | trim }} + {{- end }} + {{- if .Values.worker.topologySpreadConstraints }} + topologySpreadConstraints: + {{ tpl .Values.worker.topologySpreadConstraints . | nindent 8 | trim }} + {{- end }} + {{- if .Values.worker.tolerations }} + tolerations: + {{ tpl .Values.worker.tolerations . | nindent 8 | trim }} + {{- end }} + {{- include "seaweedfs.imagePullSecrets" . | nindent 6 }} + terminationGracePeriodSeconds: 60 + {{- if .Values.worker.priorityClassName }} + priorityClassName: {{ .Values.worker.priorityClassName | quote }} + {{- end }} + enableServiceLinks: false + {{- if .Values.worker.serviceAccountName }} + serviceAccountName: {{ .Values.worker.serviceAccountName | quote }} + {{- end }} + {{- if .Values.worker.initContainers }} + initContainers: + {{ tpl .Values.worker.initContainers . | nindent 8 | trim }} + {{- end }} + {{- if .Values.worker.podSecurityContext.enabled }} + securityContext: {{- omit .Values.worker.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + containers: + - name: seaweedfs + image: {{ template "worker.image" . }} + imagePullPolicy: {{ default "IfNotPresent" .Values.global.imagePullPolicy }} + env: + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: SEAWEEDFS_FULLNAME + value: "{{ template "seaweedfs.name" . }}" + {{- if .Values.worker.extraEnvironmentVars }} + {{- range $key, $value := .Values.worker.extraEnvironmentVars }} + - name: {{ $key }} + {{- if kindIs "string" $value }} + value: {{ $value | quote }} + {{- else }} + valueFrom: + {{ toYaml $value | nindent 16 | trim }} + {{- end -}} + {{- end }} + {{- end }} + {{- if .Values.global.extraEnvironmentVars }} + {{- range $key, $value := .Values.global.extraEnvironmentVars }} + - name: {{ $key }} + {{- if kindIs "string" $value }} + value: {{ $value | quote }} + {{- else }} + valueFrom: + {{ toYaml $value | nindent 16 | trim }} + {{- end -}} + {{- end }} + {{- end }} + command: + - "/bin/sh" + - "-ec" + - | + exec /usr/bin/weed \ + {{- if or (eq .Values.worker.logs.type "hostPath") (eq .Values.worker.logs.type "emptyDir") (eq .Values.worker.logs.type "existingClaim") }} + -logdir=/logs \ + {{- else }} + -logtostderr=true \ + {{- end }} + {{- if .Values.worker.loggingOverrideLevel }} + -v={{ .Values.worker.loggingOverrideLevel }} \ + {{- else }} + -v={{ .Values.global.loggingLevel }} \ + {{- end }} + worker \ + {{- if .Values.worker.adminServer }} + -admin={{ .Values.worker.adminServer }} \ + {{- else }} + -admin={{ template "seaweedfs.name" . }}-admin.{{ .Release.Namespace }}:{{ .Values.admin.port }}{{ if .Values.admin.grpcPort }}.{{ .Values.admin.grpcPort }}{{ end }} \ + {{- end }} + -capabilities={{ .Values.worker.capabilities }} \ + -maxConcurrent={{ .Values.worker.maxConcurrent }} \ + -workingDir={{ .Values.worker.workingDir }}{{- if or .Values.worker.metricsPort .Values.worker.extraArgs }} \{{ end }} + {{- if .Values.worker.metricsPort }} + -metricsPort={{ .Values.worker.metricsPort }}{{- if .Values.worker.extraArgs }} \{{ end }} + {{- end }} + {{- range $index, $arg := .Values.worker.extraArgs }} + {{ $arg }}{{- if lt $index (sub (len $.Values.worker.extraArgs) 1) }} \{{ end }} + {{- end }} + volumeMounts: + {{- if or (eq .Values.worker.data.type "hostPath") (eq .Values.worker.data.type "emptyDir") (eq .Values.worker.data.type "existingClaim") }} + - name: worker-data + mountPath: {{ .Values.worker.workingDir }} + {{- end }} + {{- if or (eq .Values.worker.logs.type "hostPath") (eq .Values.worker.logs.type "emptyDir") (eq .Values.worker.logs.type "existingClaim") }} + - name: worker-logs + mountPath: /logs + {{- end }} + {{- if .Values.global.enableSecurity }} + - name: security-config + readOnly: true + mountPath: /etc/seaweedfs/security.toml + subPath: security.toml + - name: ca-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/ca/ + - name: master-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/master/ + - name: volume-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/volume/ + - name: filer-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/filer/ + - name: client-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/client/ + - name: worker-cert + readOnly: true + mountPath: /usr/local/share/ca-certificates/worker/ + {{- end }} + {{ tpl .Values.worker.extraVolumeMounts . | nindent 12 | trim }} + ports: + {{- if .Values.worker.metricsPort }} + - containerPort: {{ .Values.worker.metricsPort }} + name: metrics + {{- end }} + {{- with .Values.worker.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.worker.livenessProbe.enabled }} + livenessProbe: + {{- if .Values.worker.livenessProbe.httpGet }} + httpGet: + path: {{ .Values.worker.livenessProbe.httpGet.path }} + port: {{ .Values.worker.livenessProbe.httpGet.port }} + {{- else if .Values.worker.livenessProbe.tcpSocket }} + tcpSocket: + port: {{ .Values.worker.livenessProbe.tcpSocket.port }} + {{- end }} + initialDelaySeconds: {{ .Values.worker.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.worker.livenessProbe.periodSeconds }} + successThreshold: {{ .Values.worker.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.worker.livenessProbe.failureThreshold }} + timeoutSeconds: {{ .Values.worker.livenessProbe.timeoutSeconds }} + {{- end }} + {{- if .Values.worker.readinessProbe.enabled }} + readinessProbe: + {{- if .Values.worker.readinessProbe.httpGet }} + httpGet: + path: {{ .Values.worker.readinessProbe.httpGet.path }} + port: {{ .Values.worker.readinessProbe.httpGet.port }} + {{- else if .Values.worker.readinessProbe.tcpSocket }} + tcpSocket: + port: {{ .Values.worker.readinessProbe.tcpSocket.port }} + {{- end }} + initialDelaySeconds: {{ .Values.worker.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.worker.readinessProbe.periodSeconds }} + successThreshold: {{ .Values.worker.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.worker.readinessProbe.failureThreshold }} + timeoutSeconds: {{ .Values.worker.readinessProbe.timeoutSeconds }} + {{- end }} + {{- if .Values.worker.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.worker.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.worker.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.worker.sidecars "context" $) | nindent 8 }} + {{- end }} + volumes: + {{- if eq .Values.worker.data.type "hostPath" }} + - name: worker-data + hostPath: + path: {{ .Values.worker.data.hostPathPrefix }}/seaweedfs-worker-data + type: DirectoryOrCreate + {{- end }} + {{- if eq .Values.worker.data.type "emptyDir" }} + - name: worker-data + emptyDir: {} + {{- end }} + {{- if eq .Values.worker.data.type "existingClaim" }} + - name: worker-data + persistentVolumeClaim: + claimName: {{ .Values.worker.data.claimName }} + {{- end }} + {{- if eq .Values.worker.logs.type "hostPath" }} + - name: worker-logs + hostPath: + path: {{ .Values.worker.logs.hostPathPrefix }}/logs/seaweedfs/worker + type: DirectoryOrCreate + {{- end }} + {{- if eq .Values.worker.logs.type "emptyDir" }} + - name: worker-logs + emptyDir: {} + {{- end }} + {{- if eq .Values.worker.logs.type "existingClaim" }} + - name: worker-logs + persistentVolumeClaim: + claimName: {{ .Values.worker.logs.claimName }} + {{- end }} + {{- if .Values.global.enableSecurity }} + - name: security-config + configMap: + name: {{ template "seaweedfs.name" . }}-security-config + - name: ca-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-ca-cert + - name: master-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-master-cert + - name: volume-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-volume-cert + - name: filer-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-filer-cert + - name: client-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-client-cert + - name: worker-cert + secret: + secretName: {{ template "seaweedfs.name" . }}-worker-cert + {{- end }} + {{ tpl .Values.worker.extraVolumes . | indent 8 | trim }} + {{- if .Values.worker.nodeSelector }} + nodeSelector: + {{ tpl .Values.worker.nodeSelector . | indent 8 | trim }} + {{- end }} +{{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-service.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-service.yaml new file mode 100644 index 00000000..cf9885e2 --- /dev/null +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-service.yaml @@ -0,0 +1,26 @@ +{{- if .Values.worker.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "seaweedfs.name" . }}-worker + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: worker +spec: + clusterIP: None # Headless service + {{- if .Values.worker.metricsPort }} + ports: + - name: "metrics" + port: {{ .Values.worker.metricsPort }} + targetPort: {{ .Values.worker.metricsPort }} + protocol: TCP + {{- end }} + selector: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: worker +{{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-servicemonitor.yaml b/packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-servicemonitor.yaml new file mode 100644 index 00000000..7f9590da --- /dev/null +++ b/packages/system/seaweedfs/charts/seaweedfs/templates/worker/worker-servicemonitor.yaml @@ -0,0 +1,33 @@ +{{- if .Values.worker.enabled }} +{{- if .Values.worker.metricsPort }} +{{- if .Values.global.monitoring.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "seaweedfs.name" . }}-worker + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: worker + {{- with .Values.global.monitoring.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- with .Values.worker.serviceMonitor.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + endpoints: + - interval: 30s + port: metrics + scrapeTimeout: 5s + selector: + matchLabels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + app.kubernetes.io/component: worker +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/seaweedfs/charts/seaweedfs/values.yaml b/packages/system/seaweedfs/charts/seaweedfs/values.yaml index b71e1071..a2419805 100644 --- a/packages/system/seaweedfs/charts/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/charts/seaweedfs/values.yaml @@ -401,6 +401,10 @@ volume: # Volume server's rack name rack: null + # Stable identifier for the volume server, independent of IP address + # Useful for Kubernetes environments with hostPath volumes to maintain stable identity + id: null + # Volume server's data center name dataCenter: null @@ -1008,7 +1012,7 @@ s3: ingress: enabled: false className: "" - # host: false for "*" hostname + # host: false for "*" hostname, or an array for multiple hostnames host: "seaweedfs.cluster.local" path: "/" pathType: Prefix @@ -1088,6 +1092,247 @@ sftp: failureThreshold: 100 timeoutSeconds: 10 +admin: + enabled: false + imageOverride: null + restartPolicy: null + replicas: 1 + port: 23646 # Default admin port + grpcPort: 33646 # Default gRPC port for worker connections + metricsPort: 9327 + loggingOverrideLevel: null + + # Admin authentication + secret: + # Name of an existing secret containing admin credentials. If set, adminUser and adminPassword below are ignored. + existingSecret: "" + # Key in the existing secret for the admin username. Required if existingSecret is set. + userKey: "" + # Key in the existing secret for the admin password. Required if existingSecret is set. + pwKey: "" + adminUser: "admin" + adminPassword: "" # If empty, authentication is disabled. + + # Data directory for admin configuration and maintenance data + dataDir: "" # If empty, configuration is kept in memory only + + # Master servers to connect to + # If empty, uses global.masterServer or auto-discovers from master statefulset + masters: "" + + # Custom command line arguments to add to the admin command + # Example: ["-customFlag", "value", "-anotherFlag"] + extraArgs: [] + + # Storage configuration + data: + type: "emptyDir" # Options: "hostPath", "persistentVolumeClaim", "emptyDir", "existingClaim" + size: "10Gi" + storageClass: "" + hostPathPrefix: /storage + claimName: "" + annotations: {} + + logs: + type: "emptyDir" # Options: "hostPath", "persistentVolumeClaim", "emptyDir", "existingClaim" + size: "5Gi" + storageClass: "" + hostPathPrefix: /storage + claimName: "" + annotations: {} + + # Additional resources + sidecars: [] + initContainers: "" + extraVolumes: "" + extraVolumeMounts: "" + podLabels: {} + podAnnotations: {} + annotations: {} + + ## Set podManagementPolicy + podManagementPolicy: Parallel + + # Affinity Settings + # Commenting out or setting as empty the affinity variable, will allow + # deployment to single node services such as Minikube + affinity: | + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: admin + topologyKey: kubernetes.io/hostname + + # Topology Spread Constraints Settings + # This should map directly to the value of the topologySpreadConstraints + # for a PodSpec. By Default no constraints are set. + topologySpreadConstraints: "" + + resources: {} + tolerations: "" + nodeSelector: "" + priorityClassName: "" + serviceAccountName: "" + podSecurityContext: {} + containerSecurityContext: {} + + extraEnvironmentVars: {} + + # Health checks + livenessProbe: + enabled: true + httpGet: + path: /health + scheme: HTTP + initialDelaySeconds: 20 + periodSeconds: 60 + successThreshold: 1 + failureThreshold: 5 + timeoutSeconds: 10 + + readinessProbe: + enabled: true + httpGet: + path: /health + scheme: HTTP + initialDelaySeconds: 15 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 10 + + ingress: + enabled: false + className: "nginx" + # host: false for "*" hostname + host: "admin.seaweedfs.local" + path: "/" + pathType: Prefix + annotations: {} + tls: [] + + service: + type: ClusterIP + annotations: {} + + # ServiceMonitor annotations (separate from pod/deployment annotations) + serviceMonitor: + annotations: {} + +worker: + enabled: false + imageOverride: null + restartPolicy: null + replicas: 1 + loggingOverrideLevel: null + metricsPort: 9327 + + # Admin server to connect to + adminServer: "" + + + # Worker capabilities - comma-separated list + # Available: vacuum, balance, erasure_coding + # Default: "vacuum,balance,erasure_coding" (all capabilities) + capabilities: "vacuum,balance,erasure_coding" + + # Maximum number of concurrent tasks + maxConcurrent: 3 + + # Working directory for task execution + workingDir: "/tmp/seaweedfs-worker" + + # Custom command line arguments to add to the worker command + # Example: ["-customFlag", "value", "-anotherFlag"] + extraArgs: [] + + # Storage configuration for working directory + # Note: Workers use Deployment, so use "emptyDir", "hostPath", or "existingClaim" + # Do NOT use "persistentVolumeClaim" - use "existingClaim" with pre-provisioned PVC instead + data: + type: "emptyDir" # Options: "hostPath", "emptyDir", "existingClaim" + hostPathPrefix: /storage + claimName: "" # For existingClaim type + + logs: + type: "emptyDir" # Options: "hostPath", "emptyDir", "existingClaim" + hostPathPrefix: /storage + claimName: "" # For existingClaim type + + # Additional resources + sidecars: [] + initContainers: "" + extraVolumes: "" + extraVolumeMounts: "" + podLabels: {} + podAnnotations: {} + annotations: {} + + # Affinity Settings + # Commenting out or setting as empty the affinity variable, will allow + # deployment to single node services such as Minikube + affinity: | + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/name: {{ template "seaweedfs.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: worker + topologyKey: kubernetes.io/hostname + + # Topology Spread Constraints Settings + # This should map directly to the value of the topologySpreadConstraints + # for a PodSpec. By Default no constraints are set. + topologySpreadConstraints: "" + + resources: + requests: + cpu: "500m" + memory: "512Mi" + limits: + cpu: "2" + memory: "2Gi" + tolerations: "" + nodeSelector: "" + priorityClassName: "" + serviceAccountName: "" + podSecurityContext: {} + containerSecurityContext: {} + + extraEnvironmentVars: {} + + # Health checks for worker pods + # Workers expose /health (liveness) and /ready (readiness) endpoints on the metricsPort + livenessProbe: + enabled: true + httpGet: + path: /health + port: metrics + initialDelaySeconds: 30 + periodSeconds: 60 + successThreshold: 1 + failureThreshold: 5 + timeoutSeconds: 10 + + readinessProbe: + enabled: true + httpGet: + path: /ready + port: metrics + initialDelaySeconds: 20 + periodSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 10 + + # ServiceMonitor annotations (separate from pod/deployment annotations) + serviceMonitor: + annotations: {} + # All-in-one deployment configuration allInOne: enabled: false From a9de6bf07ea0850b0852d7d000c7b343f6c4e035 Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:29:29 +0300 Subject: [PATCH 34/45] [seaweed] Use upstream images Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> (cherry picked from commit bcd4b8976aa523e95f05ad5ffb791380ca160977) --- packages/system/seaweedfs/Makefile | 17 +---------------- .../seaweedfs/images/seaweedfs/Dockerfile | 2 -- 2 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 packages/system/seaweedfs/images/seaweedfs/Dockerfile diff --git a/packages/system/seaweedfs/Makefile b/packages/system/seaweedfs/Makefile index 43f4053b..d1f21a80 100644 --- a/packages/system/seaweedfs/Makefile +++ b/packages/system/seaweedfs/Makefile @@ -8,23 +8,8 @@ update: mkdir -p charts version=$$(git ls-remote --tags --sort="v:refname" https://github.com/seaweedfs/seaweedfs | grep -v '\^{}' | grep 'refs/tags/[0-9]' | awk -F'/' 'END{print $$3}') && \ curl -sSL https://github.com/seaweedfs/seaweedfs/archive/refs/tags/$${version}.tar.gz | \ - tar xzvf - --strip 3 -C charts seaweedfs-$${version}/k8s/charts/seaweedfs && \ - sed -i.bak "/ARG VERSION/ s|=.*|=$${version}|g" images/seaweedfs/Dockerfile && \ - rm -f images/seaweedfs/Dockerfile.bak + tar xzvf - --strip 3 -C charts seaweedfs-$${version}/k8s/charts/seaweedfs patch --no-backup-if-mismatch -p4 < patches/resize-api-server-annotation.diff patch --no-backup-if-mismatch -p4 < patches/s3-traffic-distribution.patch #patch --no-backup-if-mismatch -p4 < patches/retention-policy-delete.yaml -image: - docker buildx build images/seaweedfs \ - --tag $(REGISTRY)/seaweedfs:$(call settag,$(TAG)) \ - --cache-from type=registry,ref=$(REGISTRY)/seaweedfs:latest \ - --cache-to type=inline \ - --metadata-file images/seaweedfs.json \ - $(BUILDX_ARGS) - REGISTRY="$(REGISTRY)" \ - yq -i '.seaweedfs.image.registry = strenv(REGISTRY)' values.yaml - TAG=$(TAG)@$$(yq e '."containerimage.digest"' images/seaweedfs.json -o json -r) \ - yq -i '.seaweedfs.image.tag = strenv(TAG)' values.yaml - yq -i '.global.imageName = "seaweedfs"' values.yaml - rm -f images/seaweedfs.json diff --git a/packages/system/seaweedfs/images/seaweedfs/Dockerfile b/packages/system/seaweedfs/images/seaweedfs/Dockerfile deleted file mode 100644 index 7177c70f..00000000 --- a/packages/system/seaweedfs/images/seaweedfs/Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -ARG VERSION=4.02 -FROM chrislusf/seaweedfs:${VERSION} From 8b7bc9c1e1dfcd88833aa3aacfbd3b8603a8b5ba Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Sat, 6 Dec 2025 00:15:02 +0400 Subject: [PATCH 35/45] [backups] Implement core backup Plan controller (#1640) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What this PR does This patch creates a new backups.cozystack.io API group which includes backup-related resources owned exclusively by Cozystack core. A cronjob-like controller is implemented to create backup jobs that will be handled by appropriate third-party or external controllers. ### Release note ```release-note [backups] Implement the core backup API and an accompanying controller. ``` ## Summary by CodeRabbit * **New Features** * Adds a full backups system: create scheduled Plans (cron), run BackupJobs with lifecycle phases (Pending→Running→Succeeded/Failed), and produce Backup artifacts with metadata. * Adds RestoreJobs to restore from stored Backups and track restore progress/status. * Exposes new API resources (Plan, BackupJob, Backup, RestoreJob) so backups and restores can be declared and observed via the platform. ✏️ Tip: You can customize this high-level summary in your review settings. (cherry picked from commit 67ecf3d0f66b329e98a1c593db74a0cc27a0a1a8) Signed-off-by: Timofei Larkin --- api/.gitattributes | 1 + api/backups/v1alpha1/DESIGN.md | 421 +++++++++++++++ api/backups/v1alpha1/backup_types.go | 102 ++++ api/backups/v1alpha1/backupjob_types.go | 93 ++++ api/backups/v1alpha1/groupversion_info.go | 53 ++ api/backups/v1alpha1/plan_types.go | 82 +++ api/backups/v1alpha1/restorejob_types.go | 80 +++ api/backups/v1alpha1/zz_generated.deepcopy.go | 478 ++++++++++++++++++ go.mod | 1 + go.sum | 2 + hack/update-codegen.sh | 16 +- .../backupcontroller/factory/backupjob.go | 28 + internal/backupcontroller/plan_controller.go | 90 ++++ .../definitions/.gitattributes | 1 + .../backups.cozystack.io_backupjobs.yaml | 231 +++++++++ .../backups.cozystack.io_backups.yaml | 234 +++++++++ .../backups.cozystack.io_plans.yaml | 135 +++++ .../backups.cozystack.io_restorejobs.yaml | 168 ++++++ 18 files changed, 2213 insertions(+), 3 deletions(-) create mode 100644 api/.gitattributes create mode 100644 api/backups/v1alpha1/DESIGN.md create mode 100644 api/backups/v1alpha1/backup_types.go create mode 100644 api/backups/v1alpha1/backupjob_types.go create mode 100644 api/backups/v1alpha1/groupversion_info.go create mode 100644 api/backups/v1alpha1/plan_types.go create mode 100644 api/backups/v1alpha1/restorejob_types.go create mode 100644 api/backups/v1alpha1/zz_generated.deepcopy.go create mode 100644 internal/backupcontroller/factory/backupjob.go create mode 100644 internal/backupcontroller/plan_controller.go create mode 100644 packages/system/backup-controller/definitions/.gitattributes create mode 100644 packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml create mode 100644 packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml create mode 100644 packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml create mode 100644 packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml diff --git a/api/.gitattributes b/api/.gitattributes new file mode 100644 index 00000000..39e882c9 --- /dev/null +++ b/api/.gitattributes @@ -0,0 +1 @@ +zz_generated_deepcopy.go linguist-generated diff --git a/api/backups/v1alpha1/DESIGN.md b/api/backups/v1alpha1/DESIGN.md new file mode 100644 index 00000000..455dab59 --- /dev/null +++ b/api/backups/v1alpha1/DESIGN.md @@ -0,0 +1,421 @@ +# Cozystack Backups – Core API & Contracts (Draft) + +## 1. Overview + +Cozystack’s backup subsystem provides a generic, composable way to back up and restore managed applications: + +* Every **application instance** can have one or more **backup plans**. +* Backups are stored in configurable **storage locations**. +* The mechanics of *how* a backup/restore is performed are delegated to **strategy drivers**, each implementing driver-specific **BackupStrategy** CRDs. + +The core API: + +* Orchestrates **when** backups happen and **where** they’re stored. +* Tracks **what** backups exist and their status. +* Defines contracts with drivers via shared resources (`BackupJob`, `Backup`, `RestoreJob`). + +It does **not** implement the backup logic itself. + +This document covers only the **core** API and its contracts with drivers, not driver implementations. + +--- + +## 2. Goals and non-goals + +### Goals + +* Provide a **stable core API** for: + + * Declaring **backup plans** per application. + * Configuring **storage targets** (S3, in-cluster bucket, etc.). + * Tracking **backup artifacts**. + * Initiating and tracking **restores**. +* Allow multiple **strategy drivers** to plug in, each supporting specific kinds of applications and strategies. +* Let application/product authors implement backup for their kinds by: + + * Creating **Plan** objects referencing a **driver-specific strategy**. + * Not having to write a backup engine themselves. + +### Non-goals + +* Implement backup logic for any specific application or storage backend. +* Define the internal structure of driver-specific strategy CRDs. +* Handle tenant-facing UI/UX (that’s built on top of these APIs). + +--- + +## 3. Architecture + +High-level components: + +* **Core backups controller(s)** (Cozystack-owned): + + * Group: `backups.cozystack.io` + * Own: + + * `Plan` + * `BackupJob` + * `Backup` + * `RestoreJob` + * Responsibilities: + + * Schedule backups based on `Plan`. + * Create `BackupJob` objects when due. + * Provide stable contracts for drivers to: + + * Perform backups and create `Backup`s. + * Perform restores based on `Backup`s. + +* **Strategy drivers** (pluggable, possibly third-party): + + * Their own API groups, e.g. `jobdriver.backups.cozystack.io`. + * Own **strategy CRDs** (e.g. `JobBackupStrategy`). + * Implement controllers that: + + * Watch `BackupJob` / `RestoreJob`. + * Match runs whose `strategyRef` GVK they support. + * Execute backup/restore logic. + * Create and update `Backup` and run statuses. + +Strategy drivers and core communicate entirely via Kubernetes objects; there are no webhook/HTTP calls between them. + +* **Storage drivers** (pluggable, possibly third-party): + + * **TBD** + +--- + +## 4. Core API resources + +### 4.1 Plan + +**Group/Kind** +`backups.cozystack.io/v1alpha1, Kind=Plan` + +**Purpose** +Describe **when**, **how**, and **where** to back up a specific managed application. + +**Key fields (spec)** + +```go +type PlanSpec struct { + // Application to back up. + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + + // Where backups should be stored. + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + + // Driver-specific BackupStrategy to use. + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + + // When backups should run. + Schedule PlanSchedule `json:"schedule"` +} +``` + +`PlanSchedule` (initially) supports only cron: + +```go +type PlanScheduleType string + +const ( + PlanScheduleTypeEmpty PlanScheduleType = "" + PlanScheduleTypeCron PlanScheduleType = "cron" +) +``` + +```go +type PlanSchedule struct { + // Type is the schedule type. Currently only "cron" is supported. + // Defaults to "cron". + Type PlanScheduleType `json:"type,omitempty"` + + // Cron expression (required for cron type). + Cron string `json:"cron,omitempty"` +} +``` + +**Plan reconciliation contract** + +Core Plan controller: + +1. **Read schedule** from `spec.schedule` and compute the next fire time. +2. When due: + + * Create a `BackupJob` in the same namespace: + + * `spec.planRef.name = plan.Name` + * `spec.applicationRef = plan.spec.applicationRef` + * `spec.storageRef = plan.spec.storageRef` + * `spec.strategyRef = plan.spec.strategyRef` + * `spec.triggeredBy = "Plan"` + * Set `ownerReferences` so the `BackupJob` is owned by the `Plan`. + +The Plan controller does **not**: + +* Execute backups itself. +* Modify driver resources or `Backup` objects. +* Touch `BackupJob.spec` after creation. + +--- + +### 4.2 Storage + +**API Shape** + +TBD + +**Storage usage** + +* `Plan` and `BackupJob` reference `Storage` via `TypedLocalObjectReference`. +* Drivers read `Storage` to know how/where to store or read artifacts. +* Core treats `Storage` spec as opaque; it does not directly talk to S3 or buckets. + +--- + +### 4.3 BackupJob + +**Group/Kind** +`backups.cozystack.io/v1alpha1, Kind=BackupJob` + +**Purpose** +Represent a single **execution** of a backup operation, typically created when a `Plan` fires or when a user triggers an ad-hoc backup. + +**Key fields (spec)** + +```go +type BackupJobSpec struct { + // Plan that triggered this run, if any. + PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"` + + // Application to back up. + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + + // Storage to use. + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + + // Driver-specific BackupStrategy to use. + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + + // Informational: what triggered this run ("Plan", "Manual", etc.). + TriggeredBy string `json:"triggeredBy,omitempty"` +} +``` + +**Key fields (status)** + +```go +type BackupJobStatus struct { + Phase BackupJobPhase `json:"phase,omitempty"` + BackupRef *corev1.LocalObjectReference `json:"backupRef,omitempty"` + StartedAt *metav1.Time `json:"startedAt,omitempty"` + CompletedAt *metav1.Time `json:"completedAt,omitempty"` + Message string `json:"message,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} +``` + +`BackupJobPhase` is one of: `Pending`, `Running`, `Succeeded`, `Failed`. + +**BackupJob contract with drivers** + +* Core **creates** `BackupJob` and must treat `spec` as immutable afterwards. +* Each driver controller: + + * Watches `BackupJob`. + * Reconciles runs where `spec.strategyRef.apiGroup/kind` matches its **strategy type(s)**. +* Driver responsibilities: + + 1. On first reconcile: + + * Set `status.startedAt` if unset. + * Set `status.phase = Running`. + 2. Resolve inputs: + + * Read `Strategy` (driver-owned CRD), `Storage`, `Application`, optionally `Plan`. + 3. Execute backup logic (implementation-specific). + 4. On success: + + * Create a `Backup` resource (see below). + * Set `status.backupRef` to the created `Backup`. + * Set `status.completedAt`. + * Set `status.phase = Succeeded`. + 5. On failure: + + * Set `status.completedAt`. + * Set `status.phase = Failed`. + * Set `status.message` and conditions. + +Drivers must **not** modify `BackupJob.spec` or delete `BackupJob` themselves. + +--- + +### 4.4 Backup + +**Group/Kind** +`backups.cozystack.io/v1alpha1, Kind=Backup` + +**Purpose** +Represent a single **backup artifact** for a given application, decoupled from a particular run. usable as a stable, listable “thing you can restore from”. + +**Key fields (spec)** + +```go +type BackupSpec struct { + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"` + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + TakenAt metav1.Time `json:"takenAt"` + DriverMetadata map[string]string `json:"driverMetadata,omitempty"` +} +``` + +**Key fields (status)** + +```go +type BackupStatus struct { + Phase BackupPhase `json:"phase,omitempty"` // Pending, Ready, Failed, etc. + Artifact *BackupArtifact `json:"artifact,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} +``` + +`BackupArtifact` describes the artifact (URI, size, checksum). + +**Backup contract with drivers** + +* On successful completion of a `BackupJob`, the **driver**: + + * Creates a `Backup` in the same namespace (typically owned by the `BackupJob`). + * Populates `spec` fields with: + + * The application, storage, strategy references. + * `takenAt`. + * Optional `driverMetadata`. + * Sets `status` with: + + * `phase = Ready` (or equivalent when fully usable). + * `artifact` describing the stored object. +* Core: + + * Treats `Backup` spec as mostly immutable and opaque. + * Uses it to: + + * List backups for a given application/plan. + * Anchor `RestoreJob` operations. + * Implement higher-level policies (retention) if needed. + +--- + +### 4.5 RestoreJob + +**Group/Kind** +`backups.cozystack.io/v1alpha1, Kind=RestoreJob` + +**Purpose** +Represent a single **restore operation** from a `Backup`, either back into the same application or into a new target application. + +**Key fields (spec)** + +```go +type RestoreJobSpec struct { + // Backup to restore from. + BackupRef corev1.LocalObjectReference `json:"backupRef"` + + // Target application; if omitted, drivers SHOULD restore into + // backup.spec.applicationRef. + TargetApplicationRef *corev1.TypedLocalObjectReference `json:"targetApplicationRef,omitempty"` +} +``` + +**Key fields (status)** + +```go +type RestoreJobStatus struct { + Phase RestoreJobPhase `json:"phase,omitempty"` // Pending, Running, Succeeded, Failed + StartedAt *metav1.Time `json:"startedAt,omitempty"` + CompletedAt *metav1.Time `json:"completedAt,omitempty"` + Message string `json:"message,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} +``` + +**RestoreJob contract with drivers** + +* RestoreJob is created either manually or by core. +* Driver controller: + + 1. Watches `RestoreJob`. + 2. On reconcile: + + * Fetches the referenced `Backup`. + * Determines effective: + + * **Strategy**: `backup.spec.strategyRef`. + * **Storage**: `backup.spec.storageRef`. + * **Target application**: `spec.targetApplicationRef` or `backup.spec.applicationRef`. + * If effective strategy’s GVK is one of its supported strategy types → driver is responsible. + 3. Behaviour: + + * On first reconcile, set `status.startedAt` and `phase = Running`. + * Resolve `Backup`, `Storage`, `Strategy`, target application. + * Execute restore logic (implementation-specific). + * On success: + + * Set `status.completedAt`. + * Set `status.phase = Succeeded`. + * On failure: + + * Set `status.completedAt`. + * Set `status.phase = Failed`. + * Set `status.message` and conditions. + +Drivers must not modify `RestoreJob.spec` or delete `RestoreJob`. + +--- + +## 5. Strategy drivers (high-level) + +Strategy drivers are separate controllers that: + +* Define their own **strategy CRDs** (e.g. `JobBackupStrategy`) in their own API groups: + + * e.g. `jobdriver.backups.cozystack.io/v1alpha1, Kind=JobBackupStrategy` +* Implement the **BackupJob contract**: + + * Watch `BackupJob`. + * Filter by `spec.strategyRef.apiGroup/kind`. + * Execute backup logic. + * Create/update `Backup`. +* Implement the **RestoreJob contract**: + + * Watch `RestoreJob`. + * Resolve `Backup`, then effective `strategyRef`. + * Filter by effective strategy GVK. + * Execute restore logic. + +The core backups API **does not** dictate: + +* The fields and structure of driver strategy specs. +* How drivers implement backup/restore internally (Jobs, snapshots, native operator CRDs, etc.). + +Drivers are interchangeable as long as they respect: + +* The `BackupJob` and `RestoreJob` contracts. +* The shapes and semantics of `Backup` objects. + +--- + +## 6. Summary + +The Cozystack backups core API: + +* Uses a single group, `backups.cozystack.io`, for all core CRDs. +* Cleanly separates: + + * **When & where** (Plan + Storage) – core-owned. + * **What backup artifacts exist** (Backup) – driver-created but cluster-visible. + * **Execution lifecycle** (BackupJob, RestoreJob) – shared contract boundary. +* Allows multiple strategy drivers to implement backup/restore logic without entangling their implementation with the core API. + diff --git a/api/backups/v1alpha1/backup_types.go b/api/backups/v1alpha1/backup_types.go new file mode 100644 index 00000000..ff6a8612 --- /dev/null +++ b/api/backups/v1alpha1/backup_types.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// Package v1alpha1 defines backups.cozystack.io API types. +// +// Group: backups.cozystack.io +// Version: v1alpha1 +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// BackupPhase represents the lifecycle phase of a Backup. +type BackupPhase string + +const ( + BackupPhaseEmpty BackupPhase = "" + BackupPhasePending BackupPhase = "Pending" + BackupPhaseReady BackupPhase = "Ready" + BackupPhaseFailed BackupPhase = "Failed" +) + +// BackupArtifact describes the stored backup object (tarball, snapshot, etc.). +type BackupArtifact struct { + // URI is a driver-/storage-specific URI pointing to the backup artifact. + // For example: s3://bucket/prefix/file.tar.gz + URI string `json:"uri"` + + // SizeBytes is the size of the artifact in bytes, if known. + // +optional + SizeBytes int64 `json:"sizeBytes,omitempty"` + + // Checksum is the checksum of the artifact, if computed. + // For example: "sha256:". + // +optional + Checksum string `json:"checksum,omitempty"` +} + +// BackupSpec describes an immutable backup artifact produced by a BackupJob. +type BackupSpec struct { + // ApplicationRef refers to the application that was backed up. + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + + // PlanRef refers to the Plan that produced this backup, if any. + // For manually triggered backups, this can be omitted. + // +optional + PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"` + + // StorageRef refers to the Storage object that describes where the backup + // artifact is stored. + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + + // StrategyRef refers to the driver-specific BackupStrategy that was used + // to create this backup. This allows the driver to later perform restores. + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + + // TakenAt is the time at which the backup was taken (as reported by the + // driver). It may differ slightly from metadata.creationTimestamp. + TakenAt metav1.Time `json:"takenAt"` + + // DriverMetadata holds driver-specific, opaque metadata associated with + // this backup (for example snapshot IDs, schema versions, etc.). + // This data is not interpreted by the core backup controllers. + // +optional + DriverMetadata map[string]string `json:"driverMetadata,omitempty"` +} + +// BackupStatus represents the observed state of a Backup. +type BackupStatus struct { + // Phase is a simple, high-level summary of the backup's state. + // Typical values are: Pending, Ready, Failed. + // +optional + Phase BackupPhase `json:"phase,omitempty"` + + // Artifact describes the stored backup object, if available. + // +optional + Artifact *BackupArtifact `json:"artifact,omitempty"` + + // Conditions represents the latest available observations of a Backup's state. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true + +// Backup represents a single backup artifact for a given application. +type Backup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BackupSpec `json:"spec,omitempty"` + Status BackupStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// BackupList contains a list of Backups. +type BackupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Backup `json:"items"` +} diff --git a/api/backups/v1alpha1/backupjob_types.go b/api/backups/v1alpha1/backupjob_types.go new file mode 100644 index 00000000..56b4ec89 --- /dev/null +++ b/api/backups/v1alpha1/backupjob_types.go @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// Package v1alpha1 defines backups.cozystack.io API types. +// +// Group: backups.cozystack.io +// Version: v1alpha1 +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// BackupJobPhase represents the lifecycle phase of a BackupJob. +type BackupJobPhase string + +const ( + BackupJobPhaseEmpty BackupJobPhase = "" + BackupJobPhasePending BackupJobPhase = "Pending" + BackupJobPhaseRunning BackupJobPhase = "Running" + BackupJobPhaseSucceeded BackupJobPhase = "Succeeded" + BackupJobPhaseFailed BackupJobPhase = "Failed" +) + +// BackupJobSpec describes the execution of a single backup operation. +type BackupJobSpec struct { + // PlanRef refers to the Plan that requested this backup run. + // For ad-hoc/manual backups, this can be omitted. + // +optional + PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"` + + // ApplicationRef holds a reference to the managed application whose state + // is being backed up. + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + + // StorageRef holds a reference to the Storage object that describes where + // the backup will be stored. + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + + // StrategyRef holds a reference to the driver-specific BackupStrategy object + // that describes how the backup should be created. + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` +} + +// BackupJobStatus represents the observed state of a BackupJob. +type BackupJobStatus struct { + // Phase is a high-level summary of the run's state. + // Typical values: Pending, Running, Succeeded, Failed. + // +optional + Phase BackupJobPhase `json:"phase,omitempty"` + + // BackupRef refers to the Backup object created by this run, if any. + // +optional + BackupRef *corev1.LocalObjectReference `json:"backupRef,omitempty"` + + // StartedAt is the time at which the backup run started. + // +optional + StartedAt *metav1.Time `json:"startedAt,omitempty"` + + // CompletedAt is the time at which the backup run completed (successfully + // or otherwise). + // +optional + CompletedAt *metav1.Time `json:"completedAt,omitempty"` + + // Message is a human-readable message indicating details about why the + // backup run is in its current phase, if any. + // +optional + Message string `json:"message,omitempty"` + + // Conditions represents the latest available observations of a BackupJob's state. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true + +// BackupJob represents a single execution of a backup. +// It is typically created by a Plan controller when a schedule fires. +type BackupJob struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BackupJobSpec `json:"spec,omitempty"` + Status BackupJobStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// BackupJobList contains a list of BackupJobs. +type BackupJobList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BackupJob `json:"items"` +} diff --git a/api/backups/v1alpha1/groupversion_info.go b/api/backups/v1alpha1/groupversion_info.go new file mode 100644 index 00000000..a524c86d --- /dev/null +++ b/api/backups/v1alpha1/groupversion_info.go @@ -0,0 +1,53 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1alpha1 contains API Schema definitions for the v1alpha1 API group. +// +kubebuilder:object:generate=true +// +groupName=backups.cozystack.io +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + // GroupVersion is group version used to register these objects. + GroupVersion = schema.GroupVersion{Group: "backups.cozystack.io", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes( + GroupVersion, + &Plan{}, + &PlanList{}, + &BackupJob{}, + &BackupJobList{}, + &Backup{}, + &BackupList{}, + &RestoreJob{}, + &RestoreJobList{}, + ) + metav1.AddToGroupVersion(scheme, GroupVersion) + return nil +} diff --git a/api/backups/v1alpha1/plan_types.go b/api/backups/v1alpha1/plan_types.go new file mode 100644 index 00000000..e73608aa --- /dev/null +++ b/api/backups/v1alpha1/plan_types.go @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// Package v1alpha1 defines backups.cozystack.io API types. +// +// Group: backups.cozystack.io +// Version: v1alpha1 +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type PlanScheduleType string + +const ( + PlanScheduleTypeEmpty PlanScheduleType = "" + PlanScheduleTypeCron PlanScheduleType = "cron" +) + +// Condtions +const ( + PlanConditionError = "Error" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// Plan describes the schedule, method and storage location for the +// backup of a given target application. +type Plan struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PlanSpec `json:"spec,omitempty"` + Status PlanStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// PlanList contains a list of backup Plans. +type PlanList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Plan `json:"items"` +} + +// PlanSpec references the storage, the strategy, the application to be +// backed up and specifies the timetable on which the backups will run. +type PlanSpec struct { + // ApplicationRef holds a reference to the managed application, + // whose state and configuration must be backed up. + ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"` + + // StorageRef holds a reference to the Storage object that + // describes the location where the backup will be stored. + StorageRef corev1.TypedLocalObjectReference `json:"storageRef"` + + // StrategyRef holds a reference to the Strategy object that + // describes, how a backup copy is to be created. + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + + // Schedule specifies when backup copies are created. + Schedule PlanSchedule `json:"schedule"` +} + +// PlanSchedule specifies when backup copies are created. +type PlanSchedule struct { + // Type is the type of schedule specification. Supported values are + // [`cron`]. If omitted, defaults to `cron`. + // +optional + Type PlanScheduleType `json:"type,omitempty"` + + // Cron contains the cron spec for scheduling backups. Must be + // specified if the schedule type is `cron`. Since only `cron` is + // supported, omitting this field is not allowed. + // +optional + Cron string `json:"cron,omitempty"` +} + +type PlanStatus struct { + Conditions []metav1.Condition `json:"conditions,omitempty"` +} diff --git a/api/backups/v1alpha1/restorejob_types.go b/api/backups/v1alpha1/restorejob_types.go new file mode 100644 index 00000000..ddab2ff3 --- /dev/null +++ b/api/backups/v1alpha1/restorejob_types.go @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// Package v1alpha1 defines backups.cozystack.io API types. +// +// Group: backups.cozystack.io +// Version: v1alpha1 +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RestoreJobPhase represents the lifecycle phase of a RestoreJob. +type RestoreJobPhase string + +const ( + RestoreJobPhaseEmpty RestoreJobPhase = "" + RestoreJobPhasePending RestoreJobPhase = "Pending" + RestoreJobPhaseRunning RestoreJobPhase = "Running" + RestoreJobPhaseSucceeded RestoreJobPhase = "Succeeded" + RestoreJobPhaseFailed RestoreJobPhase = "Failed" +) + +// RestoreJobSpec describes the execution of a single restore operation. +type RestoreJobSpec struct { + // BackupRef refers to the Backup that should be restored. + BackupRef corev1.LocalObjectReference `json:"backupRef"` + + // TargetApplicationRef refers to the application into which the backup + // should be restored. If omitted, the driver SHOULD restore into the same + // application as referenced by backup.spec.applicationRef. + // +optional + TargetApplicationRef *corev1.TypedLocalObjectReference `json:"targetApplicationRef,omitempty"` +} + +// RestoreJobStatus represents the observed state of a RestoreJob. +type RestoreJobStatus struct { + // Phase is a high-level summary of the run's state. + // Typical values: Pending, Running, Succeeded, Failed. + // +optional + Phase RestoreJobPhase `json:"phase,omitempty"` + + // StartedAt is the time at which the restore run started. + // +optional + StartedAt *metav1.Time `json:"startedAt,omitempty"` + + // CompletedAt is the time at which the restore run completed (successfully + // or otherwise). + // +optional + CompletedAt *metav1.Time `json:"completedAt,omitempty"` + + // Message is a human-readable message indicating details about why the + // restore run is in its current phase, if any. + // +optional + Message string `json:"message,omitempty"` + + // Conditions represents the latest available observations of a RestoreJob's state. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true + +// RestoreJob represents a single execution of a restore from a Backup. +type RestoreJob struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RestoreJobSpec `json:"spec,omitempty"` + Status RestoreJobStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RestoreJobList contains a list of RestoreJobs. +type RestoreJobList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RestoreJob `json:"items"` +} diff --git a/api/backups/v1alpha1/zz_generated.deepcopy.go b/api/backups/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..3fd7005c --- /dev/null +++ b/api/backups/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,478 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2025 The Cozystack Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Backup) DeepCopyInto(out *Backup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backup. +func (in *Backup) DeepCopy() *Backup { + if in == nil { + return nil + } + out := new(Backup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Backup) 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 *BackupArtifact) DeepCopyInto(out *BackupArtifact) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupArtifact. +func (in *BackupArtifact) DeepCopy() *BackupArtifact { + if in == nil { + return nil + } + out := new(BackupArtifact) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupJob) DeepCopyInto(out *BackupJob) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJob. +func (in *BackupJob) DeepCopy() *BackupJob { + if in == nil { + return nil + } + out := new(BackupJob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupJob) 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 *BackupJobList) DeepCopyInto(out *BackupJobList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BackupJob, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJobList. +func (in *BackupJobList) DeepCopy() *BackupJobList { + if in == nil { + return nil + } + out := new(BackupJobList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupJobList) 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 *BackupJobSpec) DeepCopyInto(out *BackupJobSpec) { + *out = *in + if in.PlanRef != nil { + in, out := &in.PlanRef, &out.PlanRef + *out = new(v1.LocalObjectReference) + **out = **in + } + in.ApplicationRef.DeepCopyInto(&out.ApplicationRef) + in.StorageRef.DeepCopyInto(&out.StorageRef) + in.StrategyRef.DeepCopyInto(&out.StrategyRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJobSpec. +func (in *BackupJobSpec) DeepCopy() *BackupJobSpec { + if in == nil { + return nil + } + out := new(BackupJobSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupJobStatus) DeepCopyInto(out *BackupJobStatus) { + *out = *in + if in.BackupRef != nil { + in, out := &in.BackupRef, &out.BackupRef + *out = new(v1.LocalObjectReference) + **out = **in + } + if in.StartedAt != nil { + in, out := &in.StartedAt, &out.StartedAt + *out = (*in).DeepCopy() + } + if in.CompletedAt != nil { + in, out := &in.CompletedAt, &out.CompletedAt + *out = (*in).DeepCopy() + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJobStatus. +func (in *BackupJobStatus) DeepCopy() *BackupJobStatus { + if in == nil { + return nil + } + out := new(BackupJobStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupList) DeepCopyInto(out *BackupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Backup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupList. +func (in *BackupList) DeepCopy() *BackupList { + if in == nil { + return nil + } + out := new(BackupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupList) 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 *BackupSpec) DeepCopyInto(out *BackupSpec) { + *out = *in + in.ApplicationRef.DeepCopyInto(&out.ApplicationRef) + if in.PlanRef != nil { + in, out := &in.PlanRef, &out.PlanRef + *out = new(v1.LocalObjectReference) + **out = **in + } + in.StorageRef.DeepCopyInto(&out.StorageRef) + in.StrategyRef.DeepCopyInto(&out.StrategyRef) + in.TakenAt.DeepCopyInto(&out.TakenAt) + if in.DriverMetadata != nil { + in, out := &in.DriverMetadata, &out.DriverMetadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSpec. +func (in *BackupSpec) DeepCopy() *BackupSpec { + if in == nil { + return nil + } + out := new(BackupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupStatus) DeepCopyInto(out *BackupStatus) { + *out = *in + if in.Artifact != nil { + in, out := &in.Artifact, &out.Artifact + *out = new(BackupArtifact) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupStatus. +func (in *BackupStatus) DeepCopy() *BackupStatus { + if in == nil { + return nil + } + out := new(BackupStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Plan) DeepCopyInto(out *Plan) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Plan. +func (in *Plan) DeepCopy() *Plan { + if in == nil { + return nil + } + out := new(Plan) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Plan) 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 *PlanList) DeepCopyInto(out *PlanList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Plan, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanList. +func (in *PlanList) DeepCopy() *PlanList { + if in == nil { + return nil + } + out := new(PlanList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PlanList) 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 *PlanSchedule) DeepCopyInto(out *PlanSchedule) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanSchedule. +func (in *PlanSchedule) DeepCopy() *PlanSchedule { + if in == nil { + return nil + } + out := new(PlanSchedule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlanSpec) DeepCopyInto(out *PlanSpec) { + *out = *in + in.ApplicationRef.DeepCopyInto(&out.ApplicationRef) + in.StorageRef.DeepCopyInto(&out.StorageRef) + in.StrategyRef.DeepCopyInto(&out.StrategyRef) + out.Schedule = in.Schedule +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanSpec. +func (in *PlanSpec) DeepCopy() *PlanSpec { + if in == nil { + return nil + } + out := new(PlanSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RestoreJob) DeepCopyInto(out *RestoreJob) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJob. +func (in *RestoreJob) DeepCopy() *RestoreJob { + if in == nil { + return nil + } + out := new(RestoreJob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RestoreJob) 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 *RestoreJobList) DeepCopyInto(out *RestoreJobList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RestoreJob, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJobList. +func (in *RestoreJobList) DeepCopy() *RestoreJobList { + if in == nil { + return nil + } + out := new(RestoreJobList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RestoreJobList) 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 *RestoreJobSpec) DeepCopyInto(out *RestoreJobSpec) { + *out = *in + out.BackupRef = in.BackupRef + if in.TargetApplicationRef != nil { + in, out := &in.TargetApplicationRef, &out.TargetApplicationRef + *out = new(v1.TypedLocalObjectReference) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJobSpec. +func (in *RestoreJobSpec) DeepCopy() *RestoreJobSpec { + if in == nil { + return nil + } + out := new(RestoreJobSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RestoreJobStatus) DeepCopyInto(out *RestoreJobStatus) { + *out = *in + if in.StartedAt != nil { + in, out := &in.StartedAt, &out.StartedAt + *out = (*in).DeepCopy() + } + if in.CompletedAt != nil { + in, out := &in.CompletedAt, &out.CompletedAt + *out = (*in).DeepCopy() + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJobStatus. +func (in *RestoreJobStatus) DeepCopy() *RestoreJobStatus { + if in == nil { + return nil + } + out := new(RestoreJobStatus) + in.DeepCopyInto(out) + return out +} diff --git a/go.mod b/go.mod index a843d2f3..6ae20909 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 github.com/prometheus/client_golang v1.19.1 + github.com/robfig/cron/v3 v3.0.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 diff --git a/go.sum b/go.sum index a32e6b3b..88c81e7b 100644 --- a/go.sum +++ b/go.sum @@ -147,6 +147,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index e181fc76..0069651e 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -23,6 +23,11 @@ CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code- API_KNOWN_VIOLATIONS_DIR="${API_KNOWN_VIOLATIONS_DIR:-"${SCRIPT_ROOT}/api/api-rules"}" UPDATE_API_KNOWN_VIOLATIONS="${UPDATE_API_KNOWN_VIOLATIONS:-true}" CONTROLLER_GEN="go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.4" +TMPDIR=$(mktemp -d) +COZY_CONTROLLER_CRDDIR=packages/system/cozystack-controller/crds +COZY_RD_CRDDIR=packages/system/cozystack-resource-definition-crd/definition +BACKUPS_CORE_CRDDIR=packages/system/backup-controller/definitions +trap 'rm -rf ${TMPDIR}' EXIT source "${CODEGEN_PKG}/kube_codegen.sh" @@ -53,6 +58,11 @@ kube::codegen::gen_openapi \ "${SCRIPT_ROOT}/pkg/apis" $CONTROLLER_GEN object:headerFile="hack/boilerplate.go.txt" paths="./api/..." -$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=packages/system/cozystack-controller/crds -mv packages/system/cozystack-controller/crds/cozystack.io_cozystackresourcedefinitions.yaml \ - packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml +$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=${TMPDIR} + +mv ${TMPDIR}/cozystack.io_cozystackresourcedefinitions.yaml \ + ${COZY_RD_CRDDIR}/cozystack.io_cozystackresourcedefinitions.yaml + +mv ${TMPDIR}/backups.cozystack.io*.yaml ${BACKUPS_CORE_CRDDIR}/ + +mv ${TMPDIR}/*.yaml ${COZY_CONTROLLER_CRDDIR}/ diff --git a/internal/backupcontroller/factory/backupjob.go b/internal/backupcontroller/factory/backupjob.go new file mode 100644 index 00000000..722e5d99 --- /dev/null +++ b/internal/backupcontroller/factory/backupjob.go @@ -0,0 +1,28 @@ +package factory + +import ( + "fmt" + "time" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func BackupJob(p *backupsv1alpha1.Plan, scheduledFor time.Time) *backupsv1alpha1.BackupJob { + job := &backupsv1alpha1.BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%d", p.Name, scheduledFor.Unix()/60), + Namespace: p.Namespace, + }, + Spec: backupsv1alpha1.BackupJobSpec{ + PlanRef: &corev1.LocalObjectReference{ + Name: p.Name, + }, + ApplicationRef: *p.Spec.ApplicationRef.DeepCopy(), + StorageRef: *p.Spec.StorageRef.DeepCopy(), + StrategyRef: *p.Spec.StrategyRef.DeepCopy(), + }, + } + return job +} diff --git a/internal/backupcontroller/plan_controller.go b/internal/backupcontroller/plan_controller.go new file mode 100644 index 00000000..fca5703b --- /dev/null +++ b/internal/backupcontroller/plan_controller.go @@ -0,0 +1,90 @@ +package backupcontroller + +import ( + "context" + "fmt" + "time" + + cron "github.com/robfig/cron/v3" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + "github.com/cozystack/cozystack/internal/backupcontroller/factory" +) + +const ( + minRequeueDelay = 30 * time.Second + startingDeadlineSeconds = 300 * time.Second +) + +// PlanReconciler reconciles a Plan object +type PlanReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *PlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + log.V(2).Info("reconciling") + + p := &backupsv1alpha1.Plan{} + + if err := r.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: req.Name}, p); err != nil { + if apierrors.IsNotFound(err) { + log.V(3).Info("Plan not found") + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + tCheck := time.Now().Add(-startingDeadlineSeconds) + sch, err := cron.ParseStandard(p.Spec.Schedule.Cron) + if err != nil { + errWrapped := fmt.Errorf("could not parse cron %s: %w", p.Spec.Schedule.Cron, err) + log.Error(err, "could not parse cron", "cron", p.Spec.Schedule.Cron) + meta.SetStatusCondition(&p.Status.Conditions, metav1.Condition{ + Type: backupsv1alpha1.PlanConditionError, + Status: metav1.ConditionTrue, + Reason: "Failed to parse cron spec", + Message: errWrapped.Error(), + }) + if err := r.Status().Update(ctx, p); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + tNext := sch.Next(tCheck) + + if time.Now().Before(tNext) { + return ctrl.Result{RequeueAfter: tNext.Sub(time.Now())}, nil + } + + job := factory.BackupJob(p, tNext) + if err := controllerutil.SetControllerReference(p, job, r.Scheme); err != nil { + return ctrl.Result{}, err + } + + if err := r.Create(ctx, job); err != nil { + if apierrors.IsAlreadyExists(err) { + return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil + } + return ctrl.Result{}, err + } + + return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil +} + +// SetupWithManager registers our controller with the Manager and sets up watches. +func (r *PlanReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&backupsv1alpha1.Plan{}). + Complete(r) +} diff --git a/packages/system/backup-controller/definitions/.gitattributes b/packages/system/backup-controller/definitions/.gitattributes new file mode 100644 index 00000000..aae64e23 --- /dev/null +++ b/packages/system/backup-controller/definitions/.gitattributes @@ -0,0 +1 @@ +* linguist-generated diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml new file mode 100644 index 00000000..794d8121 --- /dev/null +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml @@ -0,0 +1,231 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: backupjobs.backups.cozystack.io +spec: + group: backups.cozystack.io + names: + kind: BackupJob + listKind: BackupJobList + plural: backupjobs + singular: backupjob + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + BackupJob represents a single execution of a backup. + It is typically created by a Plan controller when a schedule fires. + properties: + apiVersion: + 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 + kind: + 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 + metadata: + type: object + spec: + description: BackupJobSpec describes the execution of a single backup + operation. + properties: + applicationRef: + description: |- + ApplicationRef holds a reference to the managed application whose state + is being backed up. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + planRef: + description: |- + PlanRef refers to the Plan that requested this backup run. + For ad-hoc/manual backups, this can be omitted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + storageRef: + description: |- + StorageRef holds a reference to the Storage object that describes where + the backup will be stored. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + strategyRef: + description: |- + StrategyRef holds a reference to the driver-specific BackupStrategy object + that describes how the backup should be created. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + required: + - applicationRef + - storageRef + - strategyRef + type: object + status: + description: BackupJobStatus represents the observed state of a BackupJob. + properties: + backupRef: + description: BackupRef refers to the Backup object created by this + run, if any. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + completedAt: + description: |- + CompletedAt is the time at which the backup run completed (successfully + or otherwise). + format: date-time + type: string + conditions: + description: Conditions represents the latest available observations + of a BackupJob's state. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + message: + description: |- + Message is a human-readable message indicating details about why the + backup run is in its current phase, if any. + type: string + phase: + description: |- + Phase is a high-level summary of the run's state. + Typical values: Pending, Running, Succeeded, Failed. + type: string + startedAt: + description: StartedAt is the time at which the backup run started. + format: date-time + type: string + type: object + type: object + served: true + storage: true diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml new file mode 100644 index 00000000..1bae551f --- /dev/null +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml @@ -0,0 +1,234 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: backups.backups.cozystack.io +spec: + group: backups.cozystack.io + names: + kind: Backup + listKind: BackupList + plural: backups + singular: backup + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Backup represents a single backup artifact for a given application. + properties: + apiVersion: + 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 + kind: + 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 + metadata: + type: object + spec: + description: BackupSpec describes an immutable backup artifact produced + by a BackupJob. + properties: + applicationRef: + description: ApplicationRef refers to the application that was backed + up. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + driverMetadata: + additionalProperties: + type: string + description: |- + DriverMetadata holds driver-specific, opaque metadata associated with + this backup (for example snapshot IDs, schema versions, etc.). + This data is not interpreted by the core backup controllers. + type: object + planRef: + description: |- + PlanRef refers to the Plan that produced this backup, if any. + For manually triggered backups, this can be omitted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + storageRef: + description: |- + StorageRef refers to the Storage object that describes where the backup + artifact is stored. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + strategyRef: + description: |- + StrategyRef refers to the driver-specific BackupStrategy that was used + to create this backup. This allows the driver to later perform restores. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + takenAt: + description: |- + TakenAt is the time at which the backup was taken (as reported by the + driver). It may differ slightly from metadata.creationTimestamp. + format: date-time + type: string + required: + - applicationRef + - storageRef + - strategyRef + - takenAt + type: object + status: + description: BackupStatus represents the observed state of a Backup. + properties: + artifact: + description: Artifact describes the stored backup object, if available. + properties: + checksum: + description: |- + Checksum is the checksum of the artifact, if computed. + For example: "sha256:". + type: string + sizeBytes: + description: SizeBytes is the size of the artifact in bytes, if + known. + format: int64 + type: integer + uri: + description: |- + URI is a driver-/storage-specific URI pointing to the backup artifact. + For example: s3://bucket/prefix/file.tar.gz + type: string + required: + - uri + type: object + conditions: + description: Conditions represents the latest available observations + of a Backup's state. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + phase: + description: |- + Phase is a simple, high-level summary of the backup's state. + Typical values are: Pending, Ready, Failed. + type: string + type: object + type: object + served: true + storage: true diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml new file mode 100644 index 00000000..b41d8e45 --- /dev/null +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml @@ -0,0 +1,135 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: plans.backups.cozystack.io +spec: + group: backups.cozystack.io + names: + kind: Plan + listKind: PlanList + plural: plans + singular: plan + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Plan describes the schedule, method and storage location for the + backup of a given target application. + properties: + apiVersion: + 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 + kind: + 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 + metadata: + type: object + spec: + description: |- + PlanSpec references the storage, the strategy, the application to be + backed up and specifies the timetable on which the backups will run. + properties: + applicationRef: + description: |- + ApplicationRef holds a reference to the managed application, + whose state and configuration must be backed up. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + schedule: + description: Schedule specifies when backup copies are created. + properties: + cron: + description: |- + Cron contains the cron spec for scheduling backups. Must be + specified if the schedule type is `cron`. Since only `cron` is + supported, omitting this field is not allowed. + type: string + type: + description: |- + Type is the type of schedule specification. Supported values are + [`cron`]. If omitted, defaults to `cron`. + type: string + type: object + storageRef: + description: |- + StorageRef holds a reference to the Storage object that + describes the location where the backup will be stored. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + strategyRef: + description: |- + StrategyRef holds a reference to the Strategy object that + describes, how a backup copy is to be created. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + required: + - applicationRef + - schedule + - storageRef + - strategyRef + type: object + type: object + served: true + storage: true diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml new file mode 100644 index 00000000..607a69b2 --- /dev/null +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml @@ -0,0 +1,168 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: restorejobs.backups.cozystack.io +spec: + group: backups.cozystack.io + names: + kind: RestoreJob + listKind: RestoreJobList + plural: restorejobs + singular: restorejob + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: RestoreJob represents a single execution of a restore from a + Backup. + properties: + apiVersion: + 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 + kind: + 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 + metadata: + type: object + spec: + description: RestoreJobSpec describes the execution of a single restore + operation. + properties: + backupRef: + description: BackupRef refers to the Backup that should be restored. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetApplicationRef: + description: |- + TargetApplicationRef refers to the application into which the backup + should be restored. If omitted, the driver SHOULD restore into the same + application as referenced by backup.spec.applicationRef. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + required: + - backupRef + type: object + status: + description: RestoreJobStatus represents the observed state of a RestoreJob. + properties: + completedAt: + description: |- + CompletedAt is the time at which the restore run completed (successfully + or otherwise). + format: date-time + type: string + conditions: + description: Conditions represents the latest available observations + of a RestoreJob's state. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + message: + description: |- + Message is a human-readable message indicating details about why the + restore run is in its current phase, if any. + type: string + phase: + description: |- + Phase is a high-level summary of the run's state. + Typical values: Pending, Running, Succeeded, Failed. + type: string + startedAt: + description: StartedAt is the time at which the restore run started. + format: date-time + type: string + type: object + type: object + served: true + storage: true From 789aca4c843bbac8284ce20e87919815622a72d1 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Sun, 7 Dec 2025 16:20:09 +0400 Subject: [PATCH 36/45] [backups] Build and deploy backup controller (#1685) ## What this PR does This patch adds compilation and docker build steps for the backup controller as well as adding a Helm chart to deploy it as part of the PaaS bundles. ### Release note ```release-note [backups] Build and deploy backup controller ``` (cherry picked from commit 89897914facf25c4b9ebb4e102ed22a728d3fdbe) Signed-off-by: Timofei Larkin --- Makefile | 1 + cmd/backup-controller/main.go | 174 ++++++++++++++++++ internal/backupcontroller/plan_controller.go | 14 ++ packages/core/platform/bundles/paas-full.yaml | 6 + .../core/platform/bundles/paas-hosted.yaml | 5 + packages/system/backup-controller/Chart.yaml | 3 + packages/system/backup-controller/Makefile | 18 ++ .../images/backup-controller/Dockerfile | 23 +++ .../backup-controller/templates/crds.yaml | 4 + .../templates/deployment.yaml | 57 ++++++ .../templates/rbac-bind.yaml | 12 ++ .../backup-controller/templates/rbac.yaml | 11 ++ .../backup-controller/templates/sa.yaml | 4 + packages/system/backup-controller/values.yaml | 14 ++ 14 files changed, 346 insertions(+) create mode 100644 cmd/backup-controller/main.go create mode 100644 packages/system/backup-controller/Chart.yaml create mode 100644 packages/system/backup-controller/Makefile create mode 100644 packages/system/backup-controller/images/backup-controller/Dockerfile create mode 100644 packages/system/backup-controller/templates/crds.yaml create mode 100644 packages/system/backup-controller/templates/deployment.yaml create mode 100644 packages/system/backup-controller/templates/rbac-bind.yaml create mode 100644 packages/system/backup-controller/templates/rbac.yaml create mode 100644 packages/system/backup-controller/templates/sa.yaml create mode 100644 packages/system/backup-controller/values.yaml diff --git a/Makefile b/Makefile index 56322b04..fc841ce7 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ build: build-deps make -C packages/extra/monitoring image make -C packages/system/cozystack-api image make -C packages/system/cozystack-controller image + make -C packages/system/backup-controller image make -C packages/system/lineage-controller-webhook image make -C packages/system/cilium image make -C packages/system/linstor image diff --git a/cmd/backup-controller/main.go b/cmd/backup-controller/main.go new file mode 100644 index 00000000..d8436659 --- /dev/null +++ b/cmd/backup-controller/main.go @@ -0,0 +1,174 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "crypto/tls" + "flag" + "os" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + "github.com/cozystack/cozystack/internal/backupcontroller" + // +kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(backupsv1alpha1.AddToScheme(scheme)) + // +kubebuilder:scaffold:scheme +} + +func main() { + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + var secureMetrics bool + var enableHTTP2 bool + var tlsOpts []func(*tls.Config) + flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ + "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + flag.BoolVar(&secureMetrics, "metrics-secure", true, + "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") + flag.BoolVar(&enableHTTP2, "enable-http2", false, + "If set, HTTP/2 will be enabled for the metrics and webhook servers") + opts := zap.Options{ + Development: false, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + // if the enable-http2 flag is false (the default), http/2 should be disabled + // due to its vulnerabilities. More specifically, disabling http/2 will + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and + // Rapid Reset CVEs. For more information see: + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 + disableHTTP2 := func(c *tls.Config) { + setupLog.Info("disabling http/2") + c.NextProtos = []string{"http/1.1"} + } + + if !enableHTTP2 { + tlsOpts = append(tlsOpts, disableHTTP2) + } + + webhookServer := webhook.NewServer(webhook.Options{ + TLSOpts: tlsOpts, + }) + + // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. + // More info: + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/server + // - https://book.kubebuilder.io/reference/metrics.html + metricsServerOptions := metricsserver.Options{ + BindAddress: metricsAddr, + SecureServing: secureMetrics, + TLSOpts: tlsOpts, + } + + if secureMetrics { + // FilterProvider is used to protect the metrics endpoint with authn/authz. + // These configurations ensure that only authorized users and service accounts + // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/filters#WithAuthenticationAndAuthorization + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization + + // TODO(user): If CertDir, CertName, and KeyName are not specified, controller-runtime will automatically + // generate self-signed certificates for the metrics server. While convenient for development and testing, + // this setup is not recommended for production. + } + + // Configure rate limiting for the Kubernetes client + config := ctrl.GetConfigOrDie() + config.QPS = 50.0 // Increased from default 5.0 + config.Burst = 100 // Increased from default 10 + + mgr, err := ctrl.NewManager(config, ctrl.Options{ + Scheme: scheme, + Metrics: metricsServerOptions, + WebhookServer: webhookServer, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "core.backups.cozystack.io", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + if err = (&backupcontroller.PlanReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Plan") + os.Exit(1) + } + + // +kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} diff --git a/internal/backupcontroller/plan_controller.go b/internal/backupcontroller/plan_controller.go index fca5703b..046fdce4 100644 --- a/internal/backupcontroller/plan_controller.go +++ b/internal/backupcontroller/plan_controller.go @@ -61,6 +61,20 @@ func (r *PlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. } return ctrl.Result{}, nil } + + // Clear error condition if cron parsing succeeds + if condition := meta.FindStatusCondition(p.Status.Conditions, backupsv1alpha1.PlanConditionError); condition != nil && condition.Status == metav1.ConditionTrue { + meta.SetStatusCondition(&p.Status.Conditions, metav1.Condition{ + Type: backupsv1alpha1.PlanConditionError, + Status: metav1.ConditionFalse, + Reason: "Cron spec is valid", + Message: "The cron schedule has been successfully parsed", + }) + if err := r.Status().Update(ctx, p); err != nil { + return ctrl.Result{}, err + } + } + tNext := sch.Next(tCheck) if time.Now().Before(tNext) { diff --git a/packages/core/platform/bundles/paas-full.yaml b/packages/core/platform/bundles/paas-full.yaml index 4382bd62..1c83c49c 100644 --- a/packages/core/platform/bundles/paas-full.yaml +++ b/packages/core/platform/bundles/paas-full.yaml @@ -112,6 +112,12 @@ releases: disableTelemetry: true {{- end }} +- name: backup-controller + releaseName: backup-controller + chart: cozy-backup-controller + namespace: cozy-backup-controller + dependsOn: [cilium,kubeovn] + - name: lineage-controller-webhook releaseName: lineage-controller-webhook chart: cozy-lineage-controller-webhook diff --git a/packages/core/platform/bundles/paas-hosted.yaml b/packages/core/platform/bundles/paas-hosted.yaml index 560578c7..c85bcb7b 100644 --- a/packages/core/platform/bundles/paas-hosted.yaml +++ b/packages/core/platform/bundles/paas-hosted.yaml @@ -56,6 +56,11 @@ releases: disableTelemetry: true {{- end }} +- name: backup-controller + releaseName: backup-controller + chart: cozy-backup-controller + namespace: cozy-backup-controller + - name: lineage-controller-webhook releaseName: lineage-controller-webhook chart: cozy-lineage-controller-webhook diff --git a/packages/system/backup-controller/Chart.yaml b/packages/system/backup-controller/Chart.yaml new file mode 100644 index 00000000..fd135712 --- /dev/null +++ b/packages/system/backup-controller/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-backup-controller +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/backup-controller/Makefile b/packages/system/backup-controller/Makefile new file mode 100644 index 00000000..56d58f20 --- /dev/null +++ b/packages/system/backup-controller/Makefile @@ -0,0 +1,18 @@ +NAME=backup-controller +NAMESPACE=cozy-backup-controller + +include ../../../scripts/common-envs.mk +include ../../../scripts/package.mk + +image: image-backup-controller + +image-backup-controller: + docker buildx build -f images/backup-controller/Dockerfile ../../.. \ + --tag $(REGISTRY)/backup-controller:$(call settag,$(TAG)) \ + --cache-from type=registry,ref=$(REGISTRY)/backup-controller:latest \ + --cache-to type=inline \ + --metadata-file images/backup-controller.json \ + $(BUILDX_ARGS) + IMAGE="$(REGISTRY)/backup-controller:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/backup-controller.json -o json -r)" \ + yq -i '.backupController.image = strenv(IMAGE)' values.yaml + rm -f images/backup-controller.json diff --git a/packages/system/backup-controller/images/backup-controller/Dockerfile b/packages/system/backup-controller/images/backup-controller/Dockerfile new file mode 100644 index 00000000..cdf53c7b --- /dev/null +++ b/packages/system/backup-controller/images/backup-controller/Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.24-alpine AS builder + +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /workspace + +COPY go.mod go.sum ./ +RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go mod download + +COPY api api/ +COPY pkg pkg/ +COPY cmd cmd/ +COPY internal internal/ + +RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags="-extldflags=-static" -o /backup-controller cmd/backup-controller/main.go + +FROM scratch + +COPY --from=builder /backup-controller /backup-controller +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt + +ENTRYPOINT ["/backup-controller"] diff --git a/packages/system/backup-controller/templates/crds.yaml b/packages/system/backup-controller/templates/crds.yaml new file mode 100644 index 00000000..2cf3183b --- /dev/null +++ b/packages/system/backup-controller/templates/crds.yaml @@ -0,0 +1,4 @@ +{{- range $path, $_ := .Files.Glob "definitions/*" }} +--- +{{ $.Files.Get $path }} +{{- end }} diff --git a/packages/system/backup-controller/templates/deployment.yaml b/packages/system/backup-controller/templates/deployment.yaml new file mode 100644 index 00000000..a8c3d07e --- /dev/null +++ b/packages/system/backup-controller/templates/deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backup-controller + labels: + app: backup-controller +spec: + replicas: {{ .Values.backupController.replicas }} + selector: + matchLabels: + app: backup-controller + template: + metadata: + labels: + app: backup-controller + spec: + tolerations: + - key: "node-role.kubernetes.io/control-plane" + operator: "Exists" + effect: "NoSchedule" + - key: "node-role.kubernetes.io/master" + operator: "Exists" + effect: "NoSchedule" + serviceAccountName: backup-controller + containers: + - name: backup-controller + image: "{{ .Values.backupController.image }}" + args: + - --leader-elect + {{- if .Values.backupController.metrics.enable }} + - --metrics-bind-address={{ .Values.backupController.metrics.bindAddress }} + {{- end }} + {{- if .Values.backupController.debug }} + - --zap-log-level=debug + {{- else }} + - --zap-log-level=info + {{- end }} + ports: + - name: metrics + containerPort: {{ split ":" .Values.backupController.metrics.bindAddress | mustLast }} + - name: health + containerPort: 8081 + readinessProbe: + httpGet: + path: /readyz + port: health + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /healthz + port: health + initialDelaySeconds: 15 + periodSeconds: 20 + {{- with .Values.backupController.resources }} + resources: {{- . | toYaml | nindent 10 }} + {{- end }} diff --git a/packages/system/backup-controller/templates/rbac-bind.yaml b/packages/system/backup-controller/templates/rbac-bind.yaml new file mode 100644 index 00000000..cbe9b5b8 --- /dev/null +++ b/packages/system/backup-controller/templates/rbac-bind.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: backups.cozystack.io:core-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: backups.cozystack.io:core-controller +subjects: +- kind: ServiceAccount + name: backup-controller + namespace: {{ .Release.Namespace }} diff --git a/packages/system/backup-controller/templates/rbac.yaml b/packages/system/backup-controller/templates/rbac.yaml new file mode 100644 index 00000000..71c4af69 --- /dev/null +++ b/packages/system/backup-controller/templates/rbac.yaml @@ -0,0 +1,11 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: backups.cozystack.io:core-controller +rules: +- apiGroups: ["backups.cozystack.io"] + resources: ["plans"] + verbs: ["get", "list", "watch"] +- apiGroups: ["backups.cozystack.io"] + resources: ["backupjobs"] + verbs: ["create", "get", "list", "watch"] diff --git a/packages/system/backup-controller/templates/sa.yaml b/packages/system/backup-controller/templates/sa.yaml new file mode 100644 index 00000000..4a180dd2 --- /dev/null +++ b/packages/system/backup-controller/templates/sa.yaml @@ -0,0 +1,4 @@ +kind: ServiceAccount +apiVersion: v1 +metadata: + name: backup-controller diff --git a/packages/system/backup-controller/values.yaml b/packages/system/backup-controller/values.yaml new file mode 100644 index 00000000..557c201a --- /dev/null +++ b/packages/system/backup-controller/values.yaml @@ -0,0 +1,14 @@ +backupController: + image: "" + replicas: 2 + debug: false + metrics: + enabled: true + bindAddress: ":8443" + resources: + requests: + cpu: 10m + memory: 64Mi + limits: + cpu: 500m + memory: 128Mi From 8dc1d538746f4af1b72a285fdc13b537bafa20ce Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Fri, 12 Dec 2025 18:02:30 +0400 Subject: [PATCH 37/45] [backups] Scaffold a backup strategy API group (#1687) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What this PR does This patch adds the boilerplate for the `strategy.backups.cozystack.io` API group that will contain reference backup/restore strategy implementations that will live in Cozystack core. ### Release note ```release-note [backups] Scaffold the `strategy.backups.cozystack.io` API group to provide a well-defined point for adding reference strategy implementations. ``` ## Summary by CodeRabbit * **New Features** * Introduced v1alpha1 backup strategy API group registration, enabling support for backup strategy resources within the system. ✏️ Tip: You can customize this high-level summary in your review settings. (cherry picked from commit 03a71eb8dea3433cae05c8364210abafdfa8d223) Signed-off-by: Timofei Larkin --- .../strategy/v1alpha1/groupversion_info.go | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 api/backups/strategy/v1alpha1/groupversion_info.go diff --git a/api/backups/strategy/v1alpha1/groupversion_info.go b/api/backups/strategy/v1alpha1/groupversion_info.go new file mode 100644 index 00000000..6cbfc8c2 --- /dev/null +++ b/api/backups/strategy/v1alpha1/groupversion_info.go @@ -0,0 +1,37 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1alpha1 contains API Schema definitions for the v1alpha1 API group. +// +kubebuilder:object:generate=true +// +groupName=strategy.backups.cozystack.io +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + GroupVersion = schema.GroupVersion{Group: "strategy.backups.cozystack.io", Version: "v1alpha1"} + SchemeBuilder = runtime.NewSchemeBuilder(addGroupVersion) + AddToScheme = SchemeBuilder.AddToScheme +) + +func addGroupVersion(scheme *runtime.Scheme) error { + metav1.AddToGroupVersion(scheme, GroupVersion) + return nil +} From 1ca75ee10d1467dc74045faa6b2479a1bce04b91 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Wed, 10 Dec 2025 13:37:13 +0400 Subject: [PATCH 38/45] [backups] Fix malformed glob and split in template (#1708) ## What this PR does A malformed glob ("*" instead of "*.yaml") captured the .gitattributes file and sent it to the templating engine. Using `split` instead of `splitList` on a string returned a map instead of a list, so templating broke on `mustLast`. This patch corrects the errors. ### Release note ```release-note [backups] Fix template-breaking errors in the backup-controller Helm chart. ``` (cherry picked from commit 4f5ae287f59ec7195a9404a72eddba5004065043) Signed-off-by: Timofei Larkin --- packages/system/backup-controller/templates/crds.yaml | 2 +- packages/system/backup-controller/templates/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/system/backup-controller/templates/crds.yaml b/packages/system/backup-controller/templates/crds.yaml index 2cf3183b..32b438c1 100644 --- a/packages/system/backup-controller/templates/crds.yaml +++ b/packages/system/backup-controller/templates/crds.yaml @@ -1,4 +1,4 @@ -{{- range $path, $_ := .Files.Glob "definitions/*" }} +{{- range $path, $_ := .Files.Glob "definitions/*.yaml" }} --- {{ $.Files.Get $path }} {{- end }} diff --git a/packages/system/backup-controller/templates/deployment.yaml b/packages/system/backup-controller/templates/deployment.yaml index a8c3d07e..f23f3f81 100644 --- a/packages/system/backup-controller/templates/deployment.yaml +++ b/packages/system/backup-controller/templates/deployment.yaml @@ -37,7 +37,7 @@ spec: {{- end }} ports: - name: metrics - containerPort: {{ split ":" .Values.backupController.metrics.bindAddress | mustLast }} + containerPort: {{ splitList ":" .Values.backupController.metrics.bindAddress | mustLast }} - name: health containerPort: 8081 readinessProbe: From f214f2983b4c38a376bee5c088e2138ea5e4f849 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Sun, 14 Dec 2025 04:04:20 +0400 Subject: [PATCH 39/45] [backups] Add indices to core backup resources (#1719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What this PR does This adds custom indexable fields on core backup resources to enable filtering by backed-up application. This will later be useful for displaying backup resources in the dashboard: it will be possible to filter them with a field selector to display only those backup resources that are relevant to a given application. ### Release note ```release-note [backups] Enable filtering backup resources by backed up application for per-app views of backups. ``` ## Summary by CodeRabbit ## Release Notes * **New Features** * Introduced new Backup, BackupJob, Plan, and RestoreJob API resources for enhanced backup management. * Added selectable fields to backup resources, enabling efficient filtering and querying by application reference. * Added status subresource with conditions to Plan resources for improved status tracking and observability. * **Chores** * Updated repository metadata configuration. ✏️ Tip: You can customize this high-level summary in your review settings. (cherry picked from commit 1ec14d6bd688ef1b4c37d36c99f367e97ecc2aaf) Signed-off-by: Timofei Larkin --- api/backups/v1alpha1/backup_types.go | 16 +++++ api/backups/v1alpha1/backupjob_types.go | 16 +++++ api/backups/v1alpha1/groupversion_info.go | 24 ++----- api/backups/v1alpha1/plan_types.go | 16 +++++ api/backups/v1alpha1/restorejob_types.go | 11 ++++ api/backups/v1alpha1/zz_generated.deepcopy.go | 23 +++++++ .../definitions/.gitattributes | 1 + .../backups.cozystack.io_backupjobs.yaml | 4 ++ .../backups.cozystack.io_backups.yaml | 4 ++ .../backups.cozystack.io_plans.yaml | 65 +++++++++++++++++++ 10 files changed, 160 insertions(+), 20 deletions(-) diff --git a/api/backups/v1alpha1/backup_types.go b/api/backups/v1alpha1/backup_types.go index ff6a8612..2f18b34f 100644 --- a/api/backups/v1alpha1/backup_types.go +++ b/api/backups/v1alpha1/backup_types.go @@ -8,8 +8,19 @@ package v1alpha1 import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) +func init() { + SchemeBuilder.Register(func(s *runtime.Scheme) error { + s.AddKnownTypes(GroupVersion, + &Backup{}, + &BackupList{}, + ) + return nil + }) +} + // BackupPhase represents the lifecycle phase of a Backup. type BackupPhase string @@ -81,7 +92,12 @@ type BackupStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } +// The field indexing on applicationRef will be needed later to display per-app backup resources. + // +kubebuilder:object:root=true +// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.apiGroup` +// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.kind` +// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.name` // Backup represents a single backup artifact for a given application. type Backup struct { diff --git a/api/backups/v1alpha1/backupjob_types.go b/api/backups/v1alpha1/backupjob_types.go index 56b4ec89..1230b209 100644 --- a/api/backups/v1alpha1/backupjob_types.go +++ b/api/backups/v1alpha1/backupjob_types.go @@ -8,8 +8,19 @@ package v1alpha1 import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) +func init() { + SchemeBuilder.Register(func(s *runtime.Scheme) error { + s.AddKnownTypes(GroupVersion, + &BackupJob{}, + &BackupJobList{}, + ) + return nil + }) +} + // BackupJobPhase represents the lifecycle phase of a BackupJob. type BackupJobPhase string @@ -71,7 +82,12 @@ type BackupJobStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } +// The field indexing on applicationRef will be needed later to display per-app backup resources. + // +kubebuilder:object:root=true +// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.apiGroup` +// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.kind` +// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.name` // BackupJob represents a single execution of a backup. // It is typically created by a Plan controller when a schedule fires. diff --git a/api/backups/v1alpha1/groupversion_info.go b/api/backups/v1alpha1/groupversion_info.go index a524c86d..8cdd663a 100644 --- a/api/backups/v1alpha1/groupversion_info.go +++ b/api/backups/v1alpha1/groupversion_info.go @@ -26,28 +26,12 @@ import ( ) var ( - // GroupVersion is group version used to register these objects. - GroupVersion = schema.GroupVersion{Group: "backups.cozystack.io", Version: "v1alpha1"} - - // SchemeBuilder is used to add go types to the GroupVersionKind scheme. - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - - // AddToScheme adds the types in this group-version to the given scheme. - AddToScheme = SchemeBuilder.AddToScheme + GroupVersion = schema.GroupVersion{Group: "backups.cozystack.io", Version: "v1alpha1"} + SchemeBuilder = runtime.NewSchemeBuilder(addGroupVersion) + AddToScheme = SchemeBuilder.AddToScheme ) -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes( - GroupVersion, - &Plan{}, - &PlanList{}, - &BackupJob{}, - &BackupJobList{}, - &Backup{}, - &BackupList{}, - &RestoreJob{}, - &RestoreJobList{}, - ) +func addGroupVersion(scheme *runtime.Scheme) error { metav1.AddToGroupVersion(scheme, GroupVersion) return nil } diff --git a/api/backups/v1alpha1/plan_types.go b/api/backups/v1alpha1/plan_types.go index e73608aa..c59d5eab 100644 --- a/api/backups/v1alpha1/plan_types.go +++ b/api/backups/v1alpha1/plan_types.go @@ -8,8 +8,19 @@ package v1alpha1 import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) +func init() { + SchemeBuilder.Register(func(s *runtime.Scheme) error { + s.AddKnownTypes(GroupVersion, + &Plan{}, + &PlanList{}, + ) + return nil + }) +} + type PlanScheduleType string const ( @@ -22,8 +33,13 @@ const ( PlanConditionError = "Error" ) +// The field indexing on applicationRef will be needed later to display per-app backup resources. + // +kubebuilder:object:root=true // +kubebuilder:subresource:status +// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.apiGroup` +// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.kind` +// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.name` // Plan describes the schedule, method and storage location for the // backup of a given target application. diff --git a/api/backups/v1alpha1/restorejob_types.go b/api/backups/v1alpha1/restorejob_types.go index ddab2ff3..0a64b7cf 100644 --- a/api/backups/v1alpha1/restorejob_types.go +++ b/api/backups/v1alpha1/restorejob_types.go @@ -8,8 +8,19 @@ package v1alpha1 import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) +func init() { + SchemeBuilder.Register(func(s *runtime.Scheme) error { + s.AddKnownTypes(GroupVersion, + &RestoreJob{}, + &RestoreJobList{}, + ) + return nil + }) +} + // RestoreJobPhase represents the lifecycle phase of a RestoreJob. type RestoreJobPhase string diff --git a/api/backups/v1alpha1/zz_generated.deepcopy.go b/api/backups/v1alpha1/zz_generated.deepcopy.go index 3fd7005c..fe825947 100644 --- a/api/backups/v1alpha1/zz_generated.deepcopy.go +++ b/api/backups/v1alpha1/zz_generated.deepcopy.go @@ -281,6 +281,7 @@ func (in *Plan) DeepCopyInto(out *Plan) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Plan. @@ -367,6 +368,28 @@ func (in *PlanSpec) DeepCopy() *PlanSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlanStatus) DeepCopyInto(out *PlanStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanStatus. +func (in *PlanStatus) DeepCopy() *PlanStatus { + if in == nil { + return nil + } + out := new(PlanStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RestoreJob) DeepCopyInto(out *RestoreJob) { *out = *in diff --git a/packages/system/backup-controller/definitions/.gitattributes b/packages/system/backup-controller/definitions/.gitattributes index aae64e23..6de56dcb 100644 --- a/packages/system/backup-controller/definitions/.gitattributes +++ b/packages/system/backup-controller/definitions/.gitattributes @@ -1 +1,2 @@ * linguist-generated +.gitattributes -linguist-generated diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml index 794d8121..367ca8e6 100644 --- a/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml @@ -227,5 +227,9 @@ spec: type: string type: object type: object + selectableFields: + - jsonPath: .spec.applicationRef.apiGroup + - jsonPath: .spec.applicationRef.kind + - jsonPath: .spec.applicationRef.name served: true storage: true diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml index 1bae551f..13d9bc62 100644 --- a/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml @@ -230,5 +230,9 @@ spec: type: string type: object type: object + selectableFields: + - jsonPath: .spec.applicationRef.apiGroup + - jsonPath: .spec.applicationRef.kind + - jsonPath: .spec.applicationRef.name served: true storage: true diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml index b41d8e45..1c6829cc 100644 --- a/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml @@ -130,6 +130,71 @@ spec: - storageRef - strategyRef type: object + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object type: object + selectableFields: + - jsonPath: .spec.applicationRef.apiGroup + - jsonPath: .spec.applicationRef.kind + - jsonPath: .spec.applicationRef.name served: true storage: true + subresources: + status: {} From 64b7984e512a71a43621f4756096cf110431bac5 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Mon, 15 Dec 2025 10:53:47 +0400 Subject: [PATCH 40/45] [backups] Stub the Job backup strategy controller (#1720) ## What this PR does This PR introduces a bare-bones Job backup strategy API type and stubs out all the boilerplate for a new controller that will handle this strategy, as well as any others in the `strategy.backups.cozystack.io` API group. ### Release note ```release-note [backups] Create stubs and minimal implmentations for controllers for the strategy.backups.cozystack.io API group. ``` (cherry picked from commit 1f0b5ff9ac0d9d8896af46f8a19501c8b728671d) Signed-off-by: Timofei Larkin --- api/backups/strategy/v1alpha1/job_types.go | 63 + .../v1alpha1/zz_generated.deepcopy.go | 123 + cmd/backupstrategy-controller/main.go | 174 + hack/update-codegen.sh | 3 + .../jobstrategy_controller.go | 31 + .../backupstrategy-controller/Chart.yaml | 3 + .../system/backupstrategy-controller/Makefile | 18 + .../definitions/.gitattributes | 1 + .../strategy.backups.cozystack.io_jobs.yaml | 8010 +++++++++++++++++ .../backupstrategy-controller/Dockerfile | 23 + .../templates/crds.yaml | 4 + .../templates/deployment.yaml | 57 + .../templates/rbac-bind.yaml | 12 + .../templates/rbac.yaml | 11 + .../templates/sa.yaml | 4 + .../backupstrategy-controller/values.yaml | 14 + 16 files changed, 8551 insertions(+) create mode 100644 api/backups/strategy/v1alpha1/job_types.go create mode 100644 api/backups/strategy/v1alpha1/zz_generated.deepcopy.go create mode 100644 cmd/backupstrategy-controller/main.go create mode 100644 internal/backupcontroller/jobstrategy_controller.go create mode 100644 packages/system/backupstrategy-controller/Chart.yaml create mode 100644 packages/system/backupstrategy-controller/Makefile create mode 100644 packages/system/backupstrategy-controller/definitions/.gitattributes create mode 100644 packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_jobs.yaml create mode 100644 packages/system/backupstrategy-controller/images/backupstrategy-controller/Dockerfile create mode 100644 packages/system/backupstrategy-controller/templates/crds.yaml create mode 100644 packages/system/backupstrategy-controller/templates/deployment.yaml create mode 100644 packages/system/backupstrategy-controller/templates/rbac-bind.yaml create mode 100644 packages/system/backupstrategy-controller/templates/rbac.yaml create mode 100644 packages/system/backupstrategy-controller/templates/sa.yaml create mode 100644 packages/system/backupstrategy-controller/values.yaml diff --git a/api/backups/strategy/v1alpha1/job_types.go b/api/backups/strategy/v1alpha1/job_types.go new file mode 100644 index 00000000..583b478a --- /dev/null +++ b/api/backups/strategy/v1alpha1/job_types.go @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// Package v1alpha1 defines strategy.backups.cozystack.io API types. +// +// Group: strategy.backups.cozystack.io +// Version: v1alpha1 +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + SchemeBuilder.Register(func(s *runtime.Scheme) error { + s.AddKnownTypes(GroupVersion, + &Job{}, + &JobList{}, + ) + return nil + }) +} + +const ( + JobStrategyKind = "Job" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster + +// Job defines a backup strategy using a one-shot Job +type Job struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec JobSpec `json:"spec,omitempty"` + Status JobStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// JobList contains a list of backup Jobs. +type JobList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Job `json:"items"` +} + +// JobSpec specifies the desired behavior of a backup job. +type JobSpec struct { + // Template holds a PodTemplateSpec with the right shape to + // run a single pod to completion and create a tarball with + // a given apps data. Helm-like Go templates are supported. + // The values of the source application are available under + // `.Values`. `.Release.Name` and `.Release.Namespace` are + // also exported. + Template corev1.PodTemplateSpec `json:"template"` +} + +type JobStatus struct { + Conditions []metav1.Condition `json:"conditions,omitempty"` +} diff --git a/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go b/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..5c300bfe --- /dev/null +++ b/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,123 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2025 The Cozystack Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Job) DeepCopyInto(out *Job) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Job. +func (in *Job) DeepCopy() *Job { + if in == nil { + return nil + } + out := new(Job) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Job) 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 *JobList) DeepCopyInto(out *JobList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Job, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobList. +func (in *JobList) DeepCopy() *JobList { + if in == nil { + return nil + } + out := new(JobList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *JobList) 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 *JobSpec) DeepCopyInto(out *JobSpec) { + *out = *in + in.Template.DeepCopyInto(&out.Template) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobSpec. +func (in *JobSpec) DeepCopy() *JobSpec { + if in == nil { + return nil + } + out := new(JobSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JobStatus) DeepCopyInto(out *JobStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobStatus. +func (in *JobStatus) DeepCopy() *JobStatus { + if in == nil { + return nil + } + out := new(JobStatus) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/backupstrategy-controller/main.go b/cmd/backupstrategy-controller/main.go new file mode 100644 index 00000000..c44f0dc8 --- /dev/null +++ b/cmd/backupstrategy-controller/main.go @@ -0,0 +1,174 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "crypto/tls" + "flag" + "os" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + "github.com/cozystack/cozystack/internal/backupcontroller" + // +kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(backupsv1alpha1.AddToScheme(scheme)) + // +kubebuilder:scaffold:scheme +} + +func main() { + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + var secureMetrics bool + var enableHTTP2 bool + var tlsOpts []func(*tls.Config) + flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ + "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + flag.BoolVar(&secureMetrics, "metrics-secure", true, + "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") + flag.BoolVar(&enableHTTP2, "enable-http2", false, + "If set, HTTP/2 will be enabled for the metrics and webhook servers") + opts := zap.Options{ + Development: false, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + // if the enable-http2 flag is false (the default), http/2 should be disabled + // due to its vulnerabilities. More specifically, disabling http/2 will + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and + // Rapid Reset CVEs. For more information see: + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 + disableHTTP2 := func(c *tls.Config) { + setupLog.Info("disabling http/2") + c.NextProtos = []string{"http/1.1"} + } + + if !enableHTTP2 { + tlsOpts = append(tlsOpts, disableHTTP2) + } + + webhookServer := webhook.NewServer(webhook.Options{ + TLSOpts: tlsOpts, + }) + + // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. + // More info: + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/server + // - https://book.kubebuilder.io/reference/metrics.html + metricsServerOptions := metricsserver.Options{ + BindAddress: metricsAddr, + SecureServing: secureMetrics, + TLSOpts: tlsOpts, + } + + if secureMetrics { + // FilterProvider is used to protect the metrics endpoint with authn/authz. + // These configurations ensure that only authorized users and service accounts + // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/filters#WithAuthenticationAndAuthorization + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization + + // TODO(user): If CertDir, CertName, and KeyName are not specified, controller-runtime will automatically + // generate self-signed certificates for the metrics server. While convenient for development and testing, + // this setup is not recommended for production. + } + + // Configure rate limiting for the Kubernetes client + config := ctrl.GetConfigOrDie() + config.QPS = 50.0 // Increased from default 5.0 + config.Burst = 100 // Increased from default 10 + + mgr, err := ctrl.NewManager(config, ctrl.Options{ + Scheme: scheme, + Metrics: metricsServerOptions, + WebhookServer: webhookServer, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "strategy.backups.cozystack.io", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + if err = (&backupcontroller.BackupJobStrategyReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Job") + os.Exit(1) + } + + // +kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 0069651e..0e43f717 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -27,6 +27,8 @@ TMPDIR=$(mktemp -d) COZY_CONTROLLER_CRDDIR=packages/system/cozystack-controller/crds COZY_RD_CRDDIR=packages/system/cozystack-resource-definition-crd/definition BACKUPS_CORE_CRDDIR=packages/system/backup-controller/definitions +BACKUPSTRATEGY_CRDDIR=packages/system/backupstrategy-controller/definitions + trap 'rm -rf ${TMPDIR}' EXIT source "${CODEGEN_PKG}/kube_codegen.sh" @@ -64,5 +66,6 @@ mv ${TMPDIR}/cozystack.io_cozystackresourcedefinitions.yaml \ ${COZY_RD_CRDDIR}/cozystack.io_cozystackresourcedefinitions.yaml mv ${TMPDIR}/backups.cozystack.io*.yaml ${BACKUPS_CORE_CRDDIR}/ +mv ${TMPDIR}/strategy.backups.cozystack.io*.yaml ${BACKUPSTRATEGY_CRDDIR}/ mv ${TMPDIR}/*.yaml ${COZY_CONTROLLER_CRDDIR}/ diff --git a/internal/backupcontroller/jobstrategy_controller.go b/internal/backupcontroller/jobstrategy_controller.go new file mode 100644 index 00000000..4da02e0b --- /dev/null +++ b/internal/backupcontroller/jobstrategy_controller.go @@ -0,0 +1,31 @@ +package backupcontroller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" +) + +// BackupJobStrategyReconciler reconciles BackupJob with a strategy referencing +// Job.strategy.backups.cozystack.io objects. +type BackupJobStrategyReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *BackupJobStrategyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + return ctrl.Result{}, nil +} + +// SetupWithManager registers our controller with the Manager and sets up watches. +func (r *BackupJobStrategyReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&backupsv1alpha1.BackupJob{}). + Complete(r) +} diff --git a/packages/system/backupstrategy-controller/Chart.yaml b/packages/system/backupstrategy-controller/Chart.yaml new file mode 100644 index 00000000..fd135712 --- /dev/null +++ b/packages/system/backupstrategy-controller/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-backup-controller +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/backupstrategy-controller/Makefile b/packages/system/backupstrategy-controller/Makefile new file mode 100644 index 00000000..a5556838 --- /dev/null +++ b/packages/system/backupstrategy-controller/Makefile @@ -0,0 +1,18 @@ +NAME=backupstrategy-controller +NAMESPACE=cozy-backup-controller + +include ../../../scripts/common-envs.mk +include ../../../scripts/package.mk + +image: image-backupstrategy-controller + +image-backupstrategy-controller: + docker buildx build -f images/backupstrategy-controller/Dockerfile ../../.. \ + --tag $(REGISTRY)/backupstrategy-controller:$(call settag,$(TAG)) \ + --cache-from type=registry,ref=$(REGISTRY)/backupstrategy-controller:latest \ + --cache-to type=inline \ + --metadata-file images/backupstrategy-controller.json \ + $(BUILDX_ARGS) + IMAGE="$(REGISTRY)/backupstrategy-controller:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/backupstrategy-controller.json -o json -r)" \ + yq -i '.backupStrategyController.image = strenv(IMAGE)' values.yaml + rm -f images/backupstrategy-controller.json diff --git a/packages/system/backupstrategy-controller/definitions/.gitattributes b/packages/system/backupstrategy-controller/definitions/.gitattributes new file mode 100644 index 00000000..f581feca --- /dev/null +++ b/packages/system/backupstrategy-controller/definitions/.gitattributes @@ -0,0 +1 @@ +*.yaml linguist-generated diff --git a/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_jobs.yaml b/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_jobs.yaml new file mode 100644 index 00000000..c40ef22c --- /dev/null +++ b/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_jobs.yaml @@ -0,0 +1,8010 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: jobs.strategy.backups.cozystack.io +spec: + group: strategy.backups.cozystack.io + names: + kind: Job + listKind: JobList + plural: jobs + singular: job + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Job defines a backup strategy using a one-shot Job + properties: + apiVersion: + 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 + kind: + 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 + metadata: + type: object + spec: + description: JobSpec specifies the desired behavior of a backup job. + properties: + template: + description: |- + Template holds a PodTemplateSpec with the right shape to + run a single pod to completion and create a tarball with + a given apps data. Helm-like Go templates are supported. + The values of the source application are available under + `.Values`. `.Release.Name` and `.Release.Namespace` are + also exported. + properties: + metadata: + description: |- + Standard object's metadata. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + type: object + spec: + description: |- + Specification of the desired behavior of the pod. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + activeDeadlineSeconds: + description: |- + Optional duration in seconds the pod may be active on the node relative to + StartTime before the system will actively try to mark it failed and kill associated containers. + Value must be a positive integer. + format: int64 + type: integer + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules + for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching + the corresponding nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector + terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the + selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, + etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether + a service account token should be automatically mounted. + type: boolean + containers: + description: |- + List of containers belonging to the pod. + Containers cannot currently be added or removed. + There must be at least one container in a Pod. + Cannot be updated. + items: + description: A single application container that you want + to run within a pod. + properties: + args: + description: |- + Arguments to the entrypoint. + The container image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The container image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + items: + description: EnvFromSource represents the source of + a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend + to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + lifecycle: + description: |- + Actions that the management system should take in response to container lifecycle events. + Cannot be updated. + properties: + postStart: + description: |- + PostStart is called immediately after a container is created. If the handler fails, + the container is terminated and restarted according to its restart policy. + Other management of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + sleep: + description: Sleep represents the duration that + the container should sleep before being terminated. + properties: + seconds: + description: Seconds is the number of seconds + to sleep. + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + description: |- + Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept + for the backward compatibility. There are no validation of this field and + lifecycle hooks will fail in runtime when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: |- + PreStop is called immediately before a container is terminated due to an + API request or management event such as liveness/startup probe failure, + preemption, resource contention, etc. The handler is not called if the + container crashes or exits. The Pod's termination grace period countdown begins before the + PreStop hook is executed. Regardless of the outcome of the handler, the + container will eventually terminate within the Pod's termination grace + period (unless delayed by finalizers). Other management of the container blocks until the hook completes + or until the termination grace period is reached. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + sleep: + description: Sleep represents the duration that + the container should sleep before being terminated. + properties: + seconds: + description: Seconds is the number of seconds + to sleep. + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + description: |- + Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept + for the backward compatibility. There are no validation of this field and + lifecycle hooks will fail in runtime when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + name: + description: |- + Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: |- + List of ports to expose from the container. Not specifying a port here + DOES NOT prevent that port from being exposed. Any port which is + listening on the default "0.0.0.0" address inside a container will be + accessible from the network. + Modifying this array with strategic merge patch may corrupt the data. + For more information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + items: + description: ContainerPort represents a network port + in a single container. + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: |- + Number of port to expose on the host. + If specified, this must be a valid port number, 0 < x < 65536. + If HostNetwork is specified, this must match ContainerPort. + Most containers do not need this. + format: int32 + type: integer + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + default: TCP + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + resizePolicy: + description: Resources resize policy for the container. + items: + description: ContainerResizePolicy represents resource + resize policy for the container. + properties: + resourceName: + description: |- + Name of the resource to which this resource resize policy applies. + Supported values: cpu, memory. + type: string + restartPolicy: + description: |- + Restart policy to apply when specified resource is resized. + If not specified, it defaults to NotRequired. + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + description: |- + Compute Resources required by this container. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + restartPolicy: + description: |- + RestartPolicy defines the restart behavior of individual containers in a pod. + This field may only be set for init containers, and the only allowed value is "Always". + For non-init containers or when this field is not specified, + the restart behavior is defined by the Pod's restart policy and the container type. + Setting the RestartPolicy as "Always" for the init container will have the following effect: + this init container will be continually restarted on + exit until all regular containers have terminated. Once all regular + containers have completed, all init containers with restartPolicy "Always" + will be shut down. This lifecycle differs from normal init containers and + is often referred to as a "sidecar" container. Although this init + container still starts in the init container sequence, it does not wait + for the container to complete before proceeding to the next init + container. Instead, the next init container starts immediately after this + init container is started, or after any startupProbe has successfully + completed. + type: string + securityContext: + description: |- + SecurityContext defines the security options the container should be run with. + If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that + applies to the container. + type: string + role: + description: Role is a SELinux role label that + applies to the container. + type: string + type: + description: Type is a SELinux type label that + applies to the container. + type: string + user: + description: User is a SELinux user label that + applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + This cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + stdin: + description: |- + Whether this container should allocate a buffer for stdin in the container runtime. If this + is not set, reads from stdin in the container will always result in EOF. + Default is false. + type: boolean + stdinOnce: + description: |- + Whether the container runtime should close the stdin channel after it has been opened by + a single attach. When stdin is true the stdin stream will remain open across multiple attach + sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the + first client attaches to stdin, and then remains open and accepts data until the client disconnects, + at which time stdin is closed and remains closed until the container is restarted. If this + flag is false, a container processes that reads from stdin will never receive an EOF. + Default is false + type: boolean + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + tty: + description: |- + Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a + raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside of + the container that the device will be mapped + to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a + Volume within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + dnsConfig: + description: |- + Specifies the DNS parameters of a pod. + Parameters specified here will be merged to the generated DNS + configuration based on DNSPolicy. + properties: + nameservers: + description: |- + A list of DNS name server IP addresses. + This will be appended to the base nameservers generated from DNSPolicy. + Duplicated nameservers will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + description: |- + A list of DNS resolver options. + This will be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options given in Options + will override those that appear in the base DNSPolicy. + items: + description: PodDNSConfigOption defines DNS resolver + options of a pod. + properties: + name: + description: Required. + type: string + value: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + description: |- + A list of DNS search domains for host-name lookup. + This will be appended to the base search paths generated from DNSPolicy. + Duplicated search paths will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + description: |- + Set DNS policy for the pod. + Defaults to "ClusterFirst". + Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. + DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. + To have DNS options set along with hostNetwork, you have to specify DNS policy + explicitly to 'ClusterFirstWithHostNet'. + type: string + enableServiceLinks: + description: |- + EnableServiceLinks indicates whether information about services should be injected into pod's + environment variables, matching the syntax of Docker links. + Optional: Defaults to true. + type: boolean + ephemeralContainers: + description: |- + List of ephemeral containers run in this pod. Ephemeral containers may be run in an existing + pod to perform user-initiated actions such as debugging. This list cannot be specified when + creating a pod, and it cannot be modified by updating the pod spec. In order to add an + ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource. + items: + description: |- + An EphemeralContainer is a temporary container that you may add to an existing Pod for + user-initiated activities such as debugging. Ephemeral containers have no resource or + scheduling guarantees, and they will not be restarted when they exit or when a Pod is + removed or restarted. The kubelet may evict a Pod if an ephemeral container causes the + Pod to exceed its resource allocation. + + To add an ephemeral container, use the ephemeralcontainers subresource of an existing + Pod. Ephemeral containers may not be removed or restarted. + properties: + args: + description: |- + Arguments to the entrypoint. + The image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + items: + description: EnvFromSource represents the source of + a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend + to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + lifecycle: + description: Lifecycle is not allowed for ephemeral + containers. + properties: + postStart: + description: |- + PostStart is called immediately after a container is created. If the handler fails, + the container is terminated and restarted according to its restart policy. + Other management of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + sleep: + description: Sleep represents the duration that + the container should sleep before being terminated. + properties: + seconds: + description: Seconds is the number of seconds + to sleep. + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + description: |- + Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept + for the backward compatibility. There are no validation of this field and + lifecycle hooks will fail in runtime when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: |- + PreStop is called immediately before a container is terminated due to an + API request or management event such as liveness/startup probe failure, + preemption, resource contention, etc. The handler is not called if the + container crashes or exits. The Pod's termination grace period countdown begins before the + PreStop hook is executed. Regardless of the outcome of the handler, the + container will eventually terminate within the Pod's termination grace + period (unless delayed by finalizers). Other management of the container blocks until the hook completes + or until the termination grace period is reached. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + sleep: + description: Sleep represents the duration that + the container should sleep before being terminated. + properties: + seconds: + description: Seconds is the number of seconds + to sleep. + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + description: |- + Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept + for the backward compatibility. There are no validation of this field and + lifecycle hooks will fail in runtime when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: Probes are not allowed for ephemeral containers. + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + name: + description: |- + Name of the ephemeral container specified as a DNS_LABEL. + This name must be unique among all containers, init containers and ephemeral containers. + type: string + ports: + description: Ports are not allowed for ephemeral containers. + items: + description: ContainerPort represents a network port + in a single container. + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: |- + Number of port to expose on the host. + If specified, this must be a valid port number, 0 < x < 65536. + If HostNetwork is specified, this must match ContainerPort. + Most containers do not need this. + format: int32 + type: integer + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + default: TCP + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: Probes are not allowed for ephemeral containers. + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + resizePolicy: + description: Resources resize policy for the container. + items: + description: ContainerResizePolicy represents resource + resize policy for the container. + properties: + resourceName: + description: |- + Name of the resource to which this resource resize policy applies. + Supported values: cpu, memory. + type: string + restartPolicy: + description: |- + Restart policy to apply when specified resource is resized. + If not specified, it defaults to NotRequired. + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + description: |- + Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources + already allocated to the pod. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + restartPolicy: + description: |- + Restart policy for the container to manage the restart behavior of each + container within a pod. + This may only be set for init containers. You cannot set this field on + ephemeral containers. + type: string + securityContext: + description: |- + Optional: SecurityContext defines the security options the ephemeral container should be run with. + If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that + applies to the container. + type: string + role: + description: Role is a SELinux role label that + applies to the container. + type: string + type: + description: Type is a SELinux type label that + applies to the container. + type: string + user: + description: User is a SELinux user label that + applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: Probes are not allowed for ephemeral containers. + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + stdin: + description: |- + Whether this container should allocate a buffer for stdin in the container runtime. If this + is not set, reads from stdin in the container will always result in EOF. + Default is false. + type: boolean + stdinOnce: + description: |- + Whether the container runtime should close the stdin channel after it has been opened by + a single attach. When stdin is true the stdin stream will remain open across multiple attach + sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the + first client attaches to stdin, and then remains open and accepts data until the client disconnects, + at which time stdin is closed and remains closed until the container is restarted. If this + flag is false, a container processes that reads from stdin will never receive an EOF. + Default is false + type: boolean + targetContainerName: + description: |- + If set, the name of the container from PodSpec that this ephemeral container targets. + The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container. + If not set then the ephemeral container uses the namespaces configured in the Pod spec. + + The container runtime must implement support for this feature. If the runtime does not + support namespace targeting then the result of setting this field is undefined. + type: string + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + tty: + description: |- + Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a + raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside of + the container that the device will be mapped + to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. Subpath mounts are not allowed for ephemeral containers. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a + Volume within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + hostAliases: + description: |- + HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts + file if specified. + items: + description: |- + HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the + pod's hosts file. + properties: + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + x-kubernetes-list-type: atomic + ip: + description: IP address of the host file entry. + type: string + required: + - ip + type: object + type: array + x-kubernetes-list-map-keys: + - ip + x-kubernetes-list-type: map + hostIPC: + description: |- + Use the host's ipc namespace. + Optional: Default to false. + type: boolean + hostNetwork: + description: |- + Host networking requested for this pod. Use the host's network namespace. + If this option is set, the ports that will be used must be specified. + Default to false. + type: boolean + hostPID: + description: |- + Use the host's pid namespace. + Optional: Default to false. + type: boolean + hostUsers: + description: |- + Use the host's user namespace. + Optional: Default to true. + If set to true or not present, the pod will be run in the host user namespace, useful + for when the pod needs a feature only available to the host user namespace, such as + loading a kernel module with CAP_SYS_MODULE. + When set to false, a new userns is created for the pod. Setting false is useful for + mitigating container breakout vulnerabilities even allowing users to run their + containers as root without actually having root privileges on the host. + This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature. + type: boolean + hostname: + description: |- + Specifies the hostname of the Pod + If not specified, the pod's hostname will be set to a system-defined value. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + initContainers: + description: |- + List of initialization containers belonging to the pod. + Init containers are executed in order prior to containers being started. If any + init container fails, the pod is considered to have failed and is handled according + to its restartPolicy. The name for an init container or normal container must be + unique among all containers. + Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. + The resourceRequirements of an init container are taken into account during scheduling + by finding the highest request/limit for each resource type, and then using the max of + of that value or the sum of the normal containers. Limits are applied to init containers + in a similar fashion. + Init containers cannot currently be added or removed. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + items: + description: A single application container that you want + to run within a pod. + properties: + args: + description: |- + Arguments to the entrypoint. + The container image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The container image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + items: + description: EnvFromSource represents the source of + a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: An optional identifier to prepend + to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret must + be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + lifecycle: + description: |- + Actions that the management system should take in response to container lifecycle events. + Cannot be updated. + properties: + postStart: + description: |- + PostStart is called immediately after a container is created. If the handler fails, + the container is terminated and restarted according to its restart policy. + Other management of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + sleep: + description: Sleep represents the duration that + the container should sleep before being terminated. + properties: + seconds: + description: Seconds is the number of seconds + to sleep. + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + description: |- + Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept + for the backward compatibility. There are no validation of this field and + lifecycle hooks will fail in runtime when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: |- + PreStop is called immediately before a container is terminated due to an + API request or management event such as liveness/startup probe failure, + preemption, resource contention, etc. The handler is not called if the + container crashes or exits. The Pod's termination grace period countdown begins before the + PreStop hook is executed. Regardless of the outcome of the handler, the + container will eventually terminate within the Pod's termination grace + period (unless delayed by finalizers). Other management of the container blocks until the hook completes + or until the termination grace period is reached. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + sleep: + description: Sleep represents the duration that + the container should sleep before being terminated. + properties: + seconds: + description: Seconds is the number of seconds + to sleep. + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + description: |- + Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept + for the backward compatibility. There are no validation of this field and + lifecycle hooks will fail in runtime when tcp handler is specified. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + name: + description: |- + Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: |- + List of ports to expose from the container. Not specifying a port here + DOES NOT prevent that port from being exposed. Any port which is + listening on the default "0.0.0.0" address inside a container will be + accessible from the network. + Modifying this array with strategic merge patch may corrupt the data. + For more information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + items: + description: ContainerPort represents a network port + in a single container. + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: |- + Number of port to expose on the host. + If specified, this must be a valid port number, 0 < x < 65536. + If HostNetwork is specified, this must match ContainerPort. + Most containers do not need this. + format: int32 + type: integer + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + default: TCP + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + resizePolicy: + description: Resources resize policy for the container. + items: + description: ContainerResizePolicy represents resource + resize policy for the container. + properties: + resourceName: + description: |- + Name of the resource to which this resource resize policy applies. + Supported values: cpu, memory. + type: string + restartPolicy: + description: |- + Restart policy to apply when specified resource is resized. + If not specified, it defaults to NotRequired. + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + description: |- + Compute Resources required by this container. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + restartPolicy: + description: |- + RestartPolicy defines the restart behavior of individual containers in a pod. + This field may only be set for init containers, and the only allowed value is "Always". + For non-init containers or when this field is not specified, + the restart behavior is defined by the Pod's restart policy and the container type. + Setting the RestartPolicy as "Always" for the init container will have the following effect: + this init container will be continually restarted on + exit until all regular containers have terminated. Once all regular + containers have completed, all init containers with restartPolicy "Always" + will be shut down. This lifecycle differs from normal init containers and + is often referred to as a "sidecar" container. Although this init + container still starts in the init container sequence, it does not wait + for the container to complete before proceeding to the next init + container. Instead, the next init container starts immediately after this + init container is started, or after any startupProbe has successfully + completed. + type: string + securityContext: + description: |- + SecurityContext defines the security options the container should be run with. + If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that + applies to the container. + type: string + role: + description: Role is a SELinux role label that + applies to the container. + type: string + type: + description: Type is a SELinux type label that + applies to the container. + type: string + user: + description: User is a SELinux user label that + applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + This cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + stdin: + description: |- + Whether this container should allocate a buffer for stdin in the container runtime. If this + is not set, reads from stdin in the container will always result in EOF. + Default is false. + type: boolean + stdinOnce: + description: |- + Whether the container runtime should close the stdin channel after it has been opened by + a single attach. When stdin is true the stdin stream will remain open across multiple attach + sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the + first client attaches to stdin, and then remains open and accepts data until the client disconnects, + at which time stdin is closed and remains closed until the container is restarted. If this + flag is false, a container processes that reads from stdin will never receive an EOF. + Default is false + type: boolean + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + tty: + description: |- + Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. + items: + description: volumeDevice describes a mapping of a + raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside of + the container that the device will be mapped + to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of a + Volume within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeName: + description: |- + NodeName indicates in which node this pod is scheduled. + If empty, this pod is a candidate for scheduling by the scheduler defined in schedulerName. + Once this field is set, the kubelet for this node becomes responsible for the lifecycle of this pod. + This field should not be used to express a desire for the pod to be scheduled on a specific node. + https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodename + type: string + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. + More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + type: object + x-kubernetes-map-type: atomic + os: + description: |- + Specifies the OS of the containers in the pod. + Some pod and container fields are restricted if this is set. + + If the OS field is set to linux, the following fields must be unset: + -securityContext.windowsOptions + + If the OS field is set to windows, following fields must be unset: + - spec.hostPID + - spec.hostIPC + - spec.hostUsers + - spec.securityContext.appArmorProfile + - spec.securityContext.seLinuxOptions + - spec.securityContext.seccompProfile + - spec.securityContext.fsGroup + - spec.securityContext.fsGroupChangePolicy + - spec.securityContext.sysctls + - spec.shareProcessNamespace + - spec.securityContext.runAsUser + - spec.securityContext.runAsGroup + - spec.securityContext.supplementalGroups + - spec.securityContext.supplementalGroupsPolicy + - spec.containers[*].securityContext.appArmorProfile + - spec.containers[*].securityContext.seLinuxOptions + - spec.containers[*].securityContext.seccompProfile + - spec.containers[*].securityContext.capabilities + - spec.containers[*].securityContext.readOnlyRootFilesystem + - spec.containers[*].securityContext.privileged + - spec.containers[*].securityContext.allowPrivilegeEscalation + - spec.containers[*].securityContext.procMount + - spec.containers[*].securityContext.runAsUser + - spec.containers[*].securityContext.runAsGroup + properties: + name: + description: |- + Name is the name of the operating system. The currently supported values are linux and windows. + Additional value may be defined in future and can be one of: + https://github.com/opencontainers/runtime-spec/blob/master/config.md#platform-specific-configuration + Clients should expect to handle additional values and treat unrecognized values in this field as os: null + type: string + required: + - name + type: object + overhead: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. + This field will be autopopulated at admission time by the RuntimeClass admission controller. If + the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. + The RuntimeClass admission controller will reject Pod create requests which have the overhead already + set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value + defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. + More info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead/README.md + type: object + preemptionPolicy: + description: |- + PreemptionPolicy is the Policy for preempting pods with lower priority. + One of Never, PreemptLowerPriority. + Defaults to PreemptLowerPriority if unset. + type: string + priority: + description: |- + The priority value. Various system components use this field to find the + priority of the pod. When Priority Admission Controller is enabled, it + prevents users from setting this field. The admission controller populates + this field from PriorityClassName. + The higher the value, the higher the priority. + format: int32 + type: integer + priorityClassName: + description: |- + If specified, indicates the pod's priority. "system-node-critical" and + "system-cluster-critical" are two special keywords which indicate the + highest priorities with the former being the highest priority. Any other + name must be defined by creating a PriorityClass object with that name. + If not specified, the pod priority will be default or zero if there is no + default. + type: string + readinessGates: + description: |- + If specified, all readiness gates will be evaluated for pod readiness. + A pod is ready when all its containers are ready AND + all conditions specified in the readiness gates have status equal to "True" + More info: https://git.k8s.io/enhancements/keps/sig-network/580-pod-readiness-gates + items: + description: PodReadinessGate contains the reference to + a pod condition + properties: + conditionType: + description: ConditionType refers to a condition in + the pod's condition list with matching type. + type: string + required: + - conditionType + type: object + type: array + x-kubernetes-list-type: atomic + resourceClaims: + description: |- + ResourceClaims defines which ResourceClaims must be allocated + and reserved before the Pod is allowed to start. The resources + will be made available to those containers which consume them + by name. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. + items: + description: |- + PodResourceClaim references exactly one ResourceClaim, either directly + or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim + for the pod. + + It adds a name to it that uniquely identifies the ResourceClaim inside the Pod. + Containers that need access to the ResourceClaim reference it with this name. + properties: + name: + description: |- + Name uniquely identifies this resource claim inside the pod. + This must be a DNS_LABEL. + type: string + resourceClaimName: + description: |- + ResourceClaimName is the name of a ResourceClaim object in the same + namespace as this pod. + + Exactly one of ResourceClaimName and ResourceClaimTemplateName must + be set. + type: string + resourceClaimTemplateName: + description: |- + ResourceClaimTemplateName is the name of a ResourceClaimTemplate + object in the same namespace as this pod. + + The template will be used to create a new ResourceClaim, which will + be bound to this pod. When this pod is deleted, the ResourceClaim + will also be deleted. The pod name and resource name, along with a + generated component, will be used to form a unique name for the + ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses. + + This field is immutable and no changes will be made to the + corresponding ResourceClaim by the control plane after creating the + ResourceClaim. + + Exactly one of ResourceClaimName and ResourceClaimTemplateName must + be set. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + restartPolicy: + description: |- + Restart policy for all containers within the pod. + One of Always, OnFailure, Never. In some contexts, only a subset of those values may be permitted. + Default to Always. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy + type: string + runtimeClassName: + description: |- + RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used + to run this pod. If no RuntimeClass resource matches the named class, the pod will not be run. + If unset or empty, the "legacy" RuntimeClass will be used, which is an implicit class with an + empty definition that uses the default runtime handler. + More info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class + type: string + schedulerName: + description: |- + If specified, the pod will be dispatched by specified scheduler. + If not specified, the pod will be dispatched by default scheduler. + type: string + schedulingGates: + description: |- + SchedulingGates is an opaque list of values that if specified will block scheduling the pod. + If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the + scheduler will not attempt to schedule the pod. + + SchedulingGates can only be set at pod creation time, and be removed only afterwards. + items: + description: PodSchedulingGate is associated to a Pod to + guard its scheduling. + properties: + name: + description: |- + Name of the scheduling gate. + Each scheduling gate must have a unique name field. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + securityContext: + description: |- + SecurityContext holds pod-level security attributes and common container settings. + Optional: Defaults to empty. See type description for default values of each field. + properties: + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in + addition to the container's primary GID and fsGroup (if specified). If + the SupplementalGroupsPolicy feature is enabled, the + supplementalGroupsPolicy field determines whether these are in addition + to or instead of any group memberships defined in the container image. + If unspecified, no additional groups are added, though group memberships + defined in the container image may still be used, depending on the + supplementalGroupsPolicy field. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + description: |- + Defines how supplemental groups of the first container processes are calculated. + Valid values are "Merge" and "Strict". If not specified, "Merge" is used. + (Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled + and the container runtime must implement support for this feature. + Note that this field cannot be set when spec.os.name is windows. + type: string + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be + set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + serviceAccount: + description: |- + DeprecatedServiceAccount is a deprecated alias for ServiceAccountName. + Deprecated: Use serviceAccountName instead. + type: string + serviceAccountName: + description: |- + ServiceAccountName is the name of the ServiceAccount to use to run this pod. + More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + type: string + setHostnameAsFQDN: + description: |- + If true the pod's hostname will be configured as the pod's FQDN, rather than the leaf name (the default). + In Linux containers, this means setting the FQDN in the hostname field of the kernel (the nodename field of struct utsname). + In Windows containers, this means setting the registry value of hostname for the registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters to FQDN. + If a pod does not have FQDN, this has no effect. + Default to false. + type: boolean + shareProcessNamespace: + description: |- + Share a single process namespace between all of the containers in a pod. + When this is set containers will be able to view and signal processes from other containers + in the same pod, and the first process in each container will not be assigned PID 1. + HostPID and ShareProcessNamespace cannot both be set. + Optional: Default to false. + type: boolean + subdomain: + description: |- + If specified, the fully qualified Pod hostname will be "...svc.". + If not specified, the pod will not have a domainname at all. + type: string + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + If this value is nil, the default grace period will be used instead. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies how to spread + matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + + If this value is nil, the behavior is equivalent to the Honor policy. + This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + + If this value is nil, the behavior is equivalent to the Ignore policy. + This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + description: |- + List of volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: azureDisk represents an Azure Data Disk + mount on the host and bind mount to the pod. + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: + None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk + in the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in + the blob storage + type: string + fsType: + default: ext4 + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure + managed data disk (only in managed availability + set). defaults to shared' + type: string + readOnly: + default: false + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: azureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that + contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: cephFS represents a Ceph FS mount on the + host that shares a pod's lifetime + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + description: 'path is Optional: Used as the mounted + root, rather than the full Ceph tree, default + is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward API about + the pod that should populate this volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing the + pod field + properties: + fieldRef: + description: 'Required: Selects a field of + the pod: only annotations, labels, name, + namespace and uid are supported.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must + not be absolute or contain the ''..'' path. + Must be utf-8 encoded. The first item of + the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will not be copied to dataSource. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the dataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, when namespace isn't specified in dataSourceRef, + both fields (dataSource and dataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + When namespace is specified in dataSourceRef, + dataSource isn't set to the same value and must be empty. + There are three important differences between dataSource and dataSourceRef: + * While dataSource only allows two specific types of objects, dataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While dataSource ignores disallowed values (dropping them), dataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + * While dataSource only allows local objects, dataSourceRef allows objects + in any namespaces. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + namespace: + description: |- + Namespace is the namespace of resource being referenced + Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label query over + volumes to consider for binding. + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ + (Beta) Using this field requires the VolumeAttributesClass feature gate to be enabled (off by default). + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource + that is attached to a kubelet's host machine and then + exposed to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + lun: + description: 'lun is Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target + worldwide names (WWNs)' + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + properties: + driver: + description: driver is the name of the driver to + use for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this field holds + extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. + This is unique identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the + specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + image: + description: |- + image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + The volume is resolved at pod startup depending on which PullPolicy value is provided: + + - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + + The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. + A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. + The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. + The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. + The volume will be mounted read-only (ro) and non-executable files (noexec). + Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath). + The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. + properties: + pullPolicy: + description: |- + Policy for pulling OCI objects. Possible values are: + Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + type: string + reference: + description: |- + Required: Image or artifact reference to be used. + Behaves in the same way as pod.spec.containers[*].image. + Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support + iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support + iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + default: default + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI + target and initiator authentication + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon + Controller persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: portworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one resources + secrets, configmaps, and downward API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from the volume + root to write the bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information about the + configMap data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a + path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether + the ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information about + the downwardAPI data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects + a field of the pod: only annotations, + labels, name, namespace and uid + are supported.' + properties: + apiVersion: + description: Version of the + schema the FieldPath is written + in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified + API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file + to be created. Must not be absolute + or contain the ''..'' path. Must + be utf-8 encoded. The first item + of the relative path must not + start with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + description: secret information about the + secret data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a + path within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional field specify whether + the Secret or its key must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is information + about the serviceAccountToken data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + description: quobyte represents a Quobyte mount on the + host that shares a pod's lifetime + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + default: /etc/ceph/keyring + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: scaleIO represents a ScaleIO persistent + volume attached and mounted on Kubernetes nodes. + properties: + fsType: + default: xfs + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address of the + ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the + ScaleIO Protection Domain for the configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable SSL + communication with Gateway, default false + type: boolean + storageMode: + default: ThinProvisioned + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage + Pool associated with the protection domain. + type: string + system: + description: system is the name of the storage system + as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether the + Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: storageOS represents a StorageOS volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: vsphereVolume represents a vSphere volume + attached and mounted on kubelets host machine + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy + Based Management (SPBM) profile ID associated + with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy + Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies + vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - containers + type: object + type: object + required: + - template + type: object + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/backupstrategy-controller/images/backupstrategy-controller/Dockerfile b/packages/system/backupstrategy-controller/images/backupstrategy-controller/Dockerfile new file mode 100644 index 00000000..e896f473 --- /dev/null +++ b/packages/system/backupstrategy-controller/images/backupstrategy-controller/Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.24-alpine AS builder + +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /workspace + +COPY go.mod go.sum ./ +RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go mod download + +COPY api api/ +COPY pkg pkg/ +COPY cmd cmd/ +COPY internal internal/ + +RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags="-extldflags=-static" -o /backupstrategy-controller cmd/backupstrategy-controller/main.go + +FROM scratch + +COPY --from=builder /backupstrategy-controller /backupstrategy-controller +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt + +ENTRYPOINT ["/backupstrategy-controller"] diff --git a/packages/system/backupstrategy-controller/templates/crds.yaml b/packages/system/backupstrategy-controller/templates/crds.yaml new file mode 100644 index 00000000..32b438c1 --- /dev/null +++ b/packages/system/backupstrategy-controller/templates/crds.yaml @@ -0,0 +1,4 @@ +{{- range $path, $_ := .Files.Glob "definitions/*.yaml" }} +--- +{{ $.Files.Get $path }} +{{- end }} diff --git a/packages/system/backupstrategy-controller/templates/deployment.yaml b/packages/system/backupstrategy-controller/templates/deployment.yaml new file mode 100644 index 00000000..25874369 --- /dev/null +++ b/packages/system/backupstrategy-controller/templates/deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backupstrategy-controller + labels: + app: backupstrategy-controller +spec: + replicas: {{ .Values.backupStrategyController.replicas }} + selector: + matchLabels: + app: backupstrategy-controller + template: + metadata: + labels: + app: backupstrategy-controller + spec: + tolerations: + - key: "node-role.kubernetes.io/control-plane" + operator: "Exists" + effect: "NoSchedule" + - key: "node-role.kubernetes.io/master" + operator: "Exists" + effect: "NoSchedule" + serviceAccountName: backupstrategy-controller + containers: + - name: backupstrategy-controller + image: "{{ .Values.backupStrategyController.image }}" + args: + - --leader-elect + {{- if .Values.backupStrategyController.metrics.enable }} + - --metrics-bind-address={{ .Values.backupStrategyController.metrics.bindAddress }} + {{- end }} + {{- if .Values.backupStrategyController.debug }} + - --zap-log-level=debug + {{- else }} + - --zap-log-level=info + {{- end }} + ports: + - name: metrics + containerPort: {{ splitList ":" .Values.backupStrategyController.metrics.bindAddress | mustLast }} + - name: health + containerPort: 8081 + readinessProbe: + httpGet: + path: /readyz + port: health + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /healthz + port: health + initialDelaySeconds: 15 + periodSeconds: 20 + {{- with .Values.backupStrategyController.resources }} + resources: {{- . | toYaml | nindent 10 }} + {{- end }} diff --git a/packages/system/backupstrategy-controller/templates/rbac-bind.yaml b/packages/system/backupstrategy-controller/templates/rbac-bind.yaml new file mode 100644 index 00000000..03578cc2 --- /dev/null +++ b/packages/system/backupstrategy-controller/templates/rbac-bind.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: backups.cozystack.io:strategy-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: backups.cozystack.io:strategy-controller +subjects: +- kind: ServiceAccount + name: backupstrategy-controller + namespace: {{ .Release.Namespace }} diff --git a/packages/system/backupstrategy-controller/templates/rbac.yaml b/packages/system/backupstrategy-controller/templates/rbac.yaml new file mode 100644 index 00000000..85431a4b --- /dev/null +++ b/packages/system/backupstrategy-controller/templates/rbac.yaml @@ -0,0 +1,11 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: backups.cozystack.io:strategy-controller +rules: +- apiGroups: ["strategy.backups.cozystack.io"] + resources: ["*"] + verbs: ["get", "list", "watch"] +- apiGroups: ["backups.cozystack.io"] + resources: ["backupjobs", "restorejobs"] + verbs: ["get", "list", "watch"] diff --git a/packages/system/backupstrategy-controller/templates/sa.yaml b/packages/system/backupstrategy-controller/templates/sa.yaml new file mode 100644 index 00000000..d55ba978 --- /dev/null +++ b/packages/system/backupstrategy-controller/templates/sa.yaml @@ -0,0 +1,4 @@ +kind: ServiceAccount +apiVersion: v1 +metadata: + name: backupstrategy-controller diff --git a/packages/system/backupstrategy-controller/values.yaml b/packages/system/backupstrategy-controller/values.yaml new file mode 100644 index 00000000..6b04244a --- /dev/null +++ b/packages/system/backupstrategy-controller/values.yaml @@ -0,0 +1,14 @@ +backupStrategyController: + image: "" + replicas: 2 + debug: false + metrics: + enabled: true + bindAddress: ":8443" + resources: + requests: + cpu: 10m + memory: 64Mi + limits: + cpu: 500m + memory: 128Mi From 6cd06ced9ae6703e1df8134a32592d41ad97094c Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Wed, 7 Jan 2026 23:45:32 +0400 Subject: [PATCH 41/45] [backups,dashboard] User-facing UI (#1737) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What this PR does Add representation for backup Plans, BackupJobs and Backups in the dashboard and a readme describing, how to add further resources to the dashboard. ### Release note ```release-note [backups,dashboard] Add backup Plans, BackupJobs, and Backups to the dashboard. ``` ## Summary by CodeRabbit * **New Features** * Added a Backups section in the dashboard navigation with Plans, BackupJobs, and Backups. * Added detailed views for Plan, BackupJob, and Backup showing metadata, references, status, timings, and artifacts. * Enhanced list views with custom columns (application refs, phase/status, created/taken timestamps). * **Documentation** * Added a Dashboard integration guide documenting how to add resources, list/detail wiring, and verification steps. ✏️ Tip: You can customize this high-level summary in your review settings. (cherry picked from commit 67c526582a5af0cddfe79f8073b3830dc13d1ee1) Signed-off-by: Timofei Larkin --- internal/controller/dashboard/README.md | 355 ++++++++++++++ internal/controller/dashboard/sidebar.go | 45 +- .../controller/dashboard/static_helpers.go | 21 + .../controller/dashboard/static_refactored.go | 455 ++++++++++++++++++ 4 files changed, 871 insertions(+), 5 deletions(-) create mode 100644 internal/controller/dashboard/README.md diff --git a/internal/controller/dashboard/README.md b/internal/controller/dashboard/README.md new file mode 100644 index 00000000..c3b2152e --- /dev/null +++ b/internal/controller/dashboard/README.md @@ -0,0 +1,355 @@ +# Dashboard Resource Integration Guide + +This guide explains how to add a new Kubernetes resource to the Cozystack dashboard. The dashboard provides a unified interface for viewing and managing Kubernetes resources through custom table views, detail pages, and sidebar navigation. + +## Overview + +Adding a new resource to the dashboard requires three main components: + +1. **CustomColumnsOverride**: Defines how the resource appears in list/table views +2. **Factory**: Defines the detail page layout for individual resource instances +3. **Sidebar Entry**: Adds navigation to the resource in the sidebar menu + +## Prerequisites + +- The resource must have a Kubernetes CustomResourceDefinition (CRD) or be a built-in Kubernetes resource +- Know the resource's API group, version, kind, and plural name +- Understand the resource's spec structure to display relevant fields + +## Step-by-Step Guide + +### Step 1: Add CustomColumnsOverride + +The CustomColumnsOverride defines the columns shown in the resource list table and how clicking on a row navigates to the detail page. + +**Location**: `internal/controller/dashboard/static_refactored.go` in `CreateAllCustomColumnsOverrides()` + +**Example**: +```go +// Stock namespace backups cozystack io v1alpha1 plans +createCustomColumnsOverride("stock-namespace-/backups.cozystack.io/v1alpha1/plans", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "Plan", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/plan-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createApplicationRefColumn("Application"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), +}), +``` + +**Key Components**: +- **ID Format**: `stock-namespace-/{group}/{version}/{plural}` +- **Name Column**: Use `createCustomColumnWithJsonPath()` with: + - Badge value: Resource kind in PascalCase (e.g., "Plan", "Service") + - Link href: `/openapi-ui/{2}/{namespace}/factory/{resource-details}/{name}` +- **Additional Columns**: Use helper functions like: + - `createStringColumn()`: Simple string values + - `createTimestampColumn()`: Timestamp fields + - `createReadyColumn()`: Ready status from conditions + - Custom helpers for complex fields + +**Helper Functions Available**: +- `createCustomColumnWithJsonPath(name, jsonPath, badgeValue, badgeColor, linkHref)`: Column with badge and link +- `createStringColumn(name, jsonPath)`: Simple string column +- `createTimestampColumn(name, jsonPath)`: Timestamp with formatting +- `createReadyColumn()`: Ready status column +- `createBoolColumn(name, jsonPath)`: Boolean column +- `createArrayColumn(name, jsonPath)`: Array column + +### Step 2: Add Factory (Detail Page) + +The Factory defines the detail page layout when viewing an individual resource instance. + +**Location**: `internal/controller/dashboard/static_refactored.go` in `CreateAllFactories()` + +**Using Unified Factory Approach** (Recommended): +```go +// Resource details factory using unified approach +resourceConfig := UnifiedResourceConfig{ + Name: "resource-details", // Must match the href in CustomColumnsOverride + ResourceType: "factory", + Kind: "ResourceKind", // PascalCase + Plural: "resources", // lowercase plural + Title: "resource", // lowercase singular +} +resourceTabs := []any{ + map[string]any{ + "key": "details", + "label": "Details", + "children": []any{ + contentCard("details-card", map[string]any{ + "marginBottom": "24px", + }, []any{ + antdText("details-title", true, "Resource details", map[string]any{ + "fontSize": 20, + "marginBottom": "12px", + }), + spacer("details-spacer", 16), + antdRow("details-grid", []any{48, 12}, []any{ + antdCol("col-left", 12, []any{ + antdFlexVertical("col-left-stack", 24, []any{ + // Metadata fields: Name, Namespace, Created, etc. + }), + }), + antdCol("col-right", 12, []any{ + antdFlexVertical("col-right-stack", 24, []any{ + // Spec fields + }), + }), + }), + }), + }, + }, +} +resourceSpec := createUnifiedFactory(resourceConfig, resourceTabs, []any{"/api/clusters/{2}/k8s/apis/{group}/{version}/namespaces/{3}/{plural}/{6}"}) + +// Add to return list +return []*dashboardv1alpha1.Factory{ + // ... other factories + createFactory("resource-details", resourceSpec), +} +``` + +**Key Components**: +- **Factory Key**: Must match the href path segment (e.g., `plan-details` matches `/factory/plan-details/...`) +- **API Endpoint**: `/api/clusters/{2}/k8s/apis/{group}/{version}/namespaces/{3}/{plural}/{6}` + - `{2}`: Cluster name + - `{3}`: Namespace + - `{6}`: Resource name +- **Sidebar Tags**: Automatically set to `["{lowercase-kind}-sidebar"]` by `createUnifiedFactory()` +- **Tabs**: Define the detail page content (Details, YAML, etc.) + +**UI Helper Functions**: +- `contentCard(id, style, children)`: Container card +- `antdText(id, strong, text, style)`: Text element +- `antdFlexVertical(id, gap, children)`: Vertical flex container +- `antdRow(id, gutter, children)`: Row layout +- `antdCol(id, span, children)`: Column layout +- `parsedText(id, text, style)`: Text with JSON path parsing +- `parsedTextWithFormatter(id, text, formatter)`: Formatted text (e.g., timestamp) +- `spacer(id, space)`: Spacing element + +**Displaying Fields**: +```go +antdFlexVertical("field-block", 4, []any{ + antdText("field-label", true, "Field Label", nil), + parsedText("field-value", "{reqsJsonPath[0]['.spec.fieldName']['-']}", nil), +}), +``` + +### Step 3: Add Sidebar Entry + +The sidebar entry adds navigation to the resource in the sidebar menu. + +**Location**: `internal/controller/dashboard/sidebar.go` in `ensureSidebar()` + +**3a. Add to keysAndTags** (around line 110-116): +```go +// Add sidebar for {group} {kind} resource +keysAndTags["{plural}"] = []any{"{lowercase-kind}-sidebar"} +``` + +**3b. Add Sidebar Section** (if creating a new section, around line 169): +```go +// Add hardcoded {SectionName} section +menuItems = append(menuItems, map[string]any{ + "key": "{section-key}", + "label": "{SectionName}", + "children": []any{ + map[string]any{ + "key": "{plural}", + "label": "{ResourceLabel}", + "link": "/openapi-ui/{clusterName}/{namespace}/api-table/{group}/{version}/{plural}", + }, + }, +}), +``` + +**3c. Add Sidebar ID to targetIDs** (around line 220): +```go +"stock-project-factory-{lowercase-kind}-details", +``` + +**3d. Update Category Ordering** (if adding a new section): +- Update the comment around line 29 to include the new section +- Update `orderCategoryLabels()` function if needed +- Add skip condition in the category loop (around line 149) + +**Important Notes**: +- The sidebar tag (`{lowercase-kind}-sidebar`) must match what the Factory uses +- The link format: `/openapi-ui/{clusterName}/{namespace}/api-table/{group}/{version}/{plural}` +- All sidebars share the same `keysAndTags` and `menuItems`, so changes affect all sidebar instances + +### Step 4: Verify Integration + +1. **Check Factory-Sidebar Connection**: + - Factory uses `sidebarTags: ["{lowercase-kind}-sidebar"]` + - Sidebar has `keysAndTags["{plural}"] = []any{"{lowercase-kind}-sidebar"}` + - Sidebar ID `stock-project-factory-{lowercase-kind}-details` exists in `targetIDs` + +2. **Check Navigation Flow**: + - Sidebar link → List table (CustomColumnsOverride) + - List table Name column → Detail page (Factory) + - All paths use consistent naming + +3. **Test**: + - Verify the resource appears in the sidebar + - Verify the list table displays correctly + - Verify clicking a resource navigates to the detail page + - Verify the detail page displays all relevant fields + +## Common Patterns + +### Displaying Object References + +For fields that reference other resources (e.g., `applicationRef`, `storageRef`): + +```go +// In CustomColumnsOverride +createApplicationRefColumn("Application"), // Uses helper function + +// In Factory details tab +parsedText("application-ref-value", + "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}", + nil), +``` + +### Displaying Timestamps + +```go +antdFlexVertical("created-block", 4, []any{ + antdText("time-label", true, "Created", nil), + antdFlex("time-block", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "time-icon", + "text": "🌐", + }, + }, + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "formatter": "timestamp", + "id": "time-value", + "text": "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}", + }, + }, + }), +}), +``` + +### Displaying Namespace with Link + +```go +antdFlexVertical("meta-namespace-block", 8, []any{ + antdText("meta-namespace-label", true, "Namespace", nil), + antdFlex("header-row", 6, []any{ + // Badge component + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": "NS", + "title": "namespace", + "style": map[string]any{ + "backgroundColor": "#a25792ff", + // ... badge styles + }, + }, + }, + // Link component + map[string]any{ + "type": "antdLink", + "data": map[string]any{ + "id": "namespace-link", + "text": "{reqsJsonPath[0]['.metadata.namespace']['-']}", + "href": "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace", + }, + }, + }), +}), +``` + +## File Reference + +- **CustomColumnsOverride**: `internal/controller/dashboard/static_refactored.go` → `CreateAllCustomColumnsOverrides()` +- **Factory**: `internal/controller/dashboard/static_refactored.go` → `CreateAllFactories()` +- **Sidebar**: `internal/controller/dashboard/sidebar.go` → `ensureSidebar()` +- **Helper Functions**: `internal/controller/dashboard/static_helpers.go` +- **UI Helpers**: `internal/controller/dashboard/ui_helpers.go` +- **Unified Helpers**: `internal/controller/dashboard/unified_helpers.go` + +## AI Agent Prompt Template + +Use this template when asking an AI agent to add a new resource to the dashboard: + +``` +Please add support for the {ResourceKind} resource ({group}/{version}/{plural}) to the Cozystack dashboard. + +Resource Details: +- API Group: {group} +- Version: {version} +- Kind: {ResourceKind} +- Plural: {plural} +- Namespaced: {true/false} + +Requirements: +1. Add a CustomColumnsOverride in CreateAllCustomColumnsOverrides() with: + - ID: stock-namespace-/{group}/{version}/{plural} + - Name column with {ResourceKind} badge linking to /factory/{lowercase-kind}-details/{name} + - Additional columns: {list relevant columns} + +2. Add a Factory in CreateAllFactories() with: + - Key: {lowercase-kind}-details + - API endpoint: /api/clusters/{2}/k8s/apis/{group}/{version}/namespaces/{3}/{plural}/{6} + - Details tab showing all spec fields: + {list spec fields to display} + - Use createUnifiedFactory() approach + +3. Add sidebar entry in ensureSidebar(): + - Add keysAndTags entry: keysAndTags["{plural}"] = []any{"{lowercase-kind}-sidebar"} + - Add sidebar section: {specify section name or "add to existing section"} + - Add to targetIDs: "stock-project-factory-{lowercase-kind}-details" + +4. Ensure Factory sidebarTags matches the keysAndTags entry + +Please follow the existing patterns in the codebase, particularly the Plan resource implementation as a reference. +``` + +## Example: Plan Resource + +The Plan resource (`backups.cozystack.io/v1alpha1/plans`) serves as a complete reference implementation: + +- **CustomColumnsOverride**: [Diff](https://github.com/cozystack/cozystack/compare/1f0b5ff9ac0d9d8896af46f8a19501c8b728671d..88da2d1f642b6cf03873d368dfdc675de23f1513#diff-8309b1db3362715b3d94a8b0beae7e95d3ccaf248d4f8702aaa12fba398da895R374-R380) in `static_refactored.go` +- **Factory**: [Diff](https://github.com/cozystack/cozystack/compare/1f0b5ff9ac0d9d8896af46f8a19501c8b728671d..88da2d1f642b6cf03873d368dfdc675de23f1513#diff-8309b1db3362715b3d94a8b0beae7e95d3ccaf248d4f8702aaa12fba398da895R1443-R1558) in `static_refactored.go` +- **Sidebar**: [Diff](https://github.com/cozystack/cozystack/compare/1f0b5ff9ac0d9d8896af46f8a19501c8b728671d..88da2d1f642b6cf03873d368dfdc675de23f1513#diff-be79027f7179e457a8f10e225bb921a197ffa390eb8f916d8d21379fadd54a56) in `sidebar.go` +- **Helper Function**: `createApplicationRefColumn()` in `static_helpers.go` ([diff](https://github.com/cozystack/cozystack/compare/1f0b5ff9ac0d9d8896af46f8a19501c8b728671d..88da2d1f642b6cf03873d368dfdc675de23f1513#diff-f17bcccc089cac3a8e965b13b9ab26e678d45bfc9a58d842399f218703e06a08R1026-R1046)) + +Review [this implementation](https://github.com/cozystack/cozystack/compare/1f0b5ff9ac0d9d8896af46f8a19501c8b728671d..88da2d1f642b6cf03873d368dfdc675de23f1513) for a complete working example. + +## Troubleshooting + +### Resource doesn't appear in sidebar +- Check that `keysAndTags["{plural}"]` is set correctly +- Verify the sidebar section is added to `menuItems` +- Ensure the sidebar ID is in `targetIDs` + +### Clicking resource doesn't navigate to detail page +- Verify the CustomColumnsOverride href matches the Factory key +- Check that the Factory key is exactly `{lowercase-kind}-details` +- Ensure the Factory is added to the return list in `CreateAllFactories()` + +### Detail page shows wrong sidebar +- Verify Factory `sidebarTags` matches `keysAndTags["{plural}"]` +- Check that the sidebar ID `stock-project-factory-{lowercase-kind}-details` exists +- Ensure all sidebars are updated (they share the same `keysAndTags`) + +### Fields not displaying correctly +- Verify JSON paths are correct (use `.spec.fieldName` format) +- Check that `reqsJsonPath[0]` index is used for single resource views +- Ensure field names match the actual resource spec structure + +## Additional Resources + +- Kubernetes API Conventions: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md +- Dashboard API Types: `api/dashboard/v1alpha1/` +- Resource Types: `api/backups/v1alpha1/` (example) + diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index 97937921..c6f9d3ba 100644 --- a/internal/controller/dashboard/sidebar.go +++ b/internal/controller/dashboard/sidebar.go @@ -26,7 +26,7 @@ import ( // - Tenant Info (/openapi-ui/{clusterName}/{namespace}/factory/info-details/info) // - All other sections are built from CRDs where spec.dashboard != nil. // - Categories are ordered strictly as: -// Marketplace, IaaS, PaaS, NaaS, , Resources, Administration +// Marketplace, IaaS, PaaS, NaaS, , Resources, Backups, Administration // - Items within each category: sort by Weight (desc), then Label (A→Z). func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { // Build the full menu once. @@ -112,6 +112,15 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack keysAndTags["secrets"] = []any{"secret-sidebar"} keysAndTags["ingresses"] = []any{"ingress-sidebar"} + // Add sidebar for backups.cozystack.io Plan resource + keysAndTags["plans"] = []any{"plan-sidebar"} + + // Add sidebar for backups.cozystack.io BackupJob resource + keysAndTags["backupjobs"] = []any{"backupjob-sidebar"} + + // Add sidebar for backups.cozystack.io Backup resource + keysAndTags["backups"] = []any{"backup-sidebar"} + // 3) Sort items within each category by Weight (desc), then Label (A→Z) for cat := range categories { sort.Slice(categories[cat], func(i, j int) bool { @@ -123,10 +132,10 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack } // 4) Order categories strictly: - // Marketplace (hardcoded), IaaS, PaaS, NaaS, , Resources, Administration + // Marketplace (hardcoded), IaaS, PaaS, NaaS, , Resources, Backups (hardcoded), Administration (hardcoded) orderedCats := orderCategoryLabels(categories) - // 5) Build menuItems (hardcode "Marketplace"; then dynamic categories; then hardcode "Administration") + // 5) Build menuItems (hardcode "Marketplace"; then dynamic categories; then hardcode "Backups" and "Administration") menuItems := []any{ map[string]any{ "key": "marketplace", @@ -142,8 +151,8 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack } for _, cat := range orderedCats { - // Skip "Marketplace" and "Administration" here since they're hardcoded - if strings.EqualFold(cat, "Marketplace") || strings.EqualFold(cat, "Administration") { + // Skip "Marketplace", "Backups", and "Administration" here since they're hardcoded + if strings.EqualFold(cat, "Marketplace") || strings.EqualFold(cat, "Backups") || strings.EqualFold(cat, "Administration") { continue } children := []any{} @@ -163,6 +172,29 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack } } + // Add hardcoded Backups section + menuItems = append(menuItems, map[string]any{ + "key": "backups", + "label": "Backups", + "children": []any{ + map[string]any{ + "key": "plans", + "label": "Plans", + "link": "/openapi-ui/{clusterName}/{namespace}/api-table/backups.cozystack.io/v1alpha1/plans", + }, + map[string]any{ + "key": "backupjobs", + "label": "BackupJobs", + "link": "/openapi-ui/{clusterName}/{namespace}/api-table/backups.cozystack.io/v1alpha1/backupjobs", + }, + map[string]any{ + "key": "backups", + "label": "Backups", + "link": "/openapi-ui/{clusterName}/{namespace}/api-table/backups.cozystack.io/v1alpha1/backups", + }, + }, + }) + // Add hardcoded Administration section menuItems = append(menuItems, map[string]any{ "key": "administration", @@ -201,6 +233,9 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack "stock-project-factory-kube-service-details", "stock-project-factory-kube-secret-details", "stock-project-factory-kube-ingress-details", + "stock-project-factory-plan-details", + "stock-project-factory-backupjob-details", + "stock-project-factory-backup-details", "stock-project-api-form", "stock-project-api-table", "stock-project-builtin-form", diff --git a/internal/controller/dashboard/static_helpers.go b/internal/controller/dashboard/static_helpers.go index 4c8aae30..af46674b 100644 --- a/internal/controller/dashboard/static_helpers.go +++ b/internal/controller/dashboard/static_helpers.go @@ -1023,6 +1023,27 @@ func createReadyColumn() map[string]any { } } +// createApplicationRefColumn creates a column that displays +// applicationRef in the format "Kind.apiGroup/name" +func createApplicationRefColumn(name string) map[string]any { + return map[string]any{ + "name": name, + "type": "factory", + "customProps": map[string]any{ + "disableEventBubbling": true, + "items": []any{ + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "id": "application-ref-text", + "text": "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}", + }, + }, + }, + }, + } +} + // createConverterBytesColumn creates a column with ConverterBytes component func createConverterBytesColumn(name, jsonPath string) map[string]any { return map[string]any{ diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go index 619d36f5..4c290c49 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -371,6 +371,30 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid createCustomColumnWithJsonPath("Name", ".metadata.name", "TenantNamespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.name']['-']}/factory/marketplace"), createTimestampColumn("Created", ".metadata.creationTimestamp"), }), + + // Stock namespace backups cozystack io v1alpha1 plans + createCustomColumnsOverride("stock-namespace-/backups.cozystack.io/v1alpha1/plans", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "Plan", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/plan-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createApplicationRefColumn("Application"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Stock namespace backups cozystack io v1alpha1 backupjobs + createCustomColumnsOverride("stock-namespace-/backups.cozystack.io/v1alpha1/backupjobs", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "BackupJob", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/backupjob-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createStringColumn("Phase", ".status.phase"), + createApplicationRefColumn("Application"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), + + // Stock namespace backups cozystack io v1alpha1 backups + createCustomColumnsOverride("stock-namespace-/backups.cozystack.io/v1alpha1/backups", []any{ + createCustomColumnWithJsonPath("Name", ".metadata.name", "Backup", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/backup-details/{reqsJsonPath[0]['.metadata.name']['-']}"), + createStringColumn("Phase", ".status.phase"), + createApplicationRefColumn("Application"), + createTimestampColumn("Taken At", ".spec.takenAt"), + createTimestampColumn("Created", ".metadata.creationTimestamp"), + }), } } @@ -1433,6 +1457,434 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { } workloadmonitorSpec := createFactorySpec("workloadmonitor-details", []any{"workloadmonitor-sidebar"}, []any{"/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloadmonitors/{6}"}, workloadmonitorHeader, workloadmonitorTabs) + // Plan details factory using unified approach + planConfig := UnifiedResourceConfig{ + Name: "plan-details", + ResourceType: "factory", + Kind: "Plan", + Plural: "plans", + Title: "plan", + } + planTabs := []any{ + map[string]any{ + "key": "details", + "label": "Details", + "children": []any{ + contentCard("details-card", map[string]any{ + "marginBottom": "24px", + }, []any{ + antdText("details-title", true, "Plan details", map[string]any{ + "fontSize": 20, + "marginBottom": "12px", + }), + spacer("details-spacer", 16), + antdRow("details-grid", []any{48, 12}, []any{ + antdCol("col-left", 12, []any{ + antdFlexVertical("col-left-stack", 24, []any{ + antdFlexVertical("meta-name-block", 4, []any{ + antdText("meta-name-label", true, "Name", nil), + parsedText("meta-name-value", "{reqsJsonPath[0]['.metadata.name']['-']}", nil), + }), + antdFlexVertical("meta-namespace-block", 8, []any{ + antdText("meta-namespace-label", true, "Namespace", nil), + antdFlex("header-row", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": "NS", + "title": "namespace", + "style": map[string]any{ + "backgroundColor": "#a25792ff", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "15px", + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + }, + map[string]any{ + "type": "antdLink", + "data": map[string]any{ + "id": "namespace-link", + "text": "{reqsJsonPath[0]['.metadata.namespace']['-']}", + "href": "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace", + }, + }, + }), + }), + antdFlexVertical("meta-created-block", 4, []any{ + antdText("time-label", true, "Created", nil), + antdFlex("time-block", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "time-icon", + "text": "🌐", + }, + }, + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "formatter": "timestamp", + "id": "time-value", + "text": "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}", + }, + }, + }), + }), + }), + }), + antdCol("col-right", 12, []any{ + antdFlexVertical("col-right-stack", 24, []any{ + antdFlexVertical("spec-application-ref-block", 4, []any{ + antdText("application-ref-label", true, "Application", nil), + parsedText("application-ref-value", "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}", nil), + }), + antdFlexVertical("spec-storage-ref-block", 4, []any{ + antdText("storage-ref-label", true, "Storage", nil), + parsedText("storage-ref-value", "{reqsJsonPath[0]['.spec.storageRef.kind']['-']}.{reqsJsonPath[0]['.spec.storageRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.storageRef.name']['-']}", nil), + }), + antdFlexVertical("spec-strategy-ref-block", 4, []any{ + antdText("strategy-ref-label", true, "Strategy", nil), + parsedText("strategy-ref-value", "{reqsJsonPath[0]['.spec.strategyRef.kind']['-']}.{reqsJsonPath[0]['.spec.strategyRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.strategyRef.name']['-']}", nil), + }), + antdFlexVertical("spec-schedule-type-block", 4, []any{ + antdText("schedule-type-label", true, "Schedule Type", nil), + parsedText("schedule-type-value", "{reqsJsonPath[0]['.spec.schedule.type']['-']}", nil), + }), + antdFlexVertical("spec-schedule-cron-block", 4, []any{ + antdText("schedule-cron-label", true, "Schedule Cron", nil), + parsedText("schedule-cron-value", "{reqsJsonPath[0]['.spec.schedule.cron']['-']}", nil), + }), + }), + }), + }), + }), + }, + }, + } + planSpec := createUnifiedFactory(planConfig, planTabs, []any{"/api/clusters/{2}/k8s/apis/backups.cozystack.io/v1alpha1/namespaces/{3}/plans/{6}"}) + + // BackupJob details factory using unified approach + backupJobConfig := UnifiedResourceConfig{ + Name: "backupjob-details", + ResourceType: "factory", + Kind: "BackupJob", + Plural: "backupjobs", + Title: "backupjob", + } + backupJobTabs := []any{ + map[string]any{ + "key": "details", + "label": "Details", + "children": []any{ + contentCard("details-card", map[string]any{ + "marginBottom": "24px", + }, []any{ + antdText("details-title", true, "BackupJob details", map[string]any{ + "fontSize": 20, + "marginBottom": "12px", + }), + spacer("details-spacer", 16), + antdRow("details-grid", []any{48, 12}, []any{ + antdCol("col-left", 12, []any{ + antdFlexVertical("col-left-stack", 24, []any{ + antdFlexVertical("meta-name-block", 4, []any{ + antdText("meta-name-label", true, "Name", nil), + parsedText("meta-name-value", "{reqsJsonPath[0]['.metadata.name']['-']}", nil), + }), + antdFlexVertical("meta-namespace-block", 8, []any{ + antdText("meta-namespace-label", true, "Namespace", nil), + antdFlex("header-row", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": "NS", + "title": "namespace", + "style": map[string]any{ + "backgroundColor": "#a25792ff", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "15px", + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + }, + map[string]any{ + "type": "antdLink", + "data": map[string]any{ + "id": "namespace-link", + "text": "{reqsJsonPath[0]['.metadata.namespace']['-']}", + "href": "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace", + }, + }, + }), + }), + antdFlexVertical("meta-created-block", 4, []any{ + antdText("time-label", true, "Created", nil), + antdFlex("time-block", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "time-icon", + "text": "🌐", + }, + }, + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "formatter": "timestamp", + "id": "time-value", + "text": "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}", + }, + }, + }), + }), + }), + }), + antdCol("col-right", 12, []any{ + antdFlexVertical("col-right-stack", 24, []any{ + antdFlexVertical("status-phase-block", 4, []any{ + antdText("phase-label", true, "Phase", nil), + parsedText("phase-value", "{reqsJsonPath[0]['.status.phase']['-']}", nil), + }), + antdFlexVertical("spec-plan-ref-block", 4, []any{ + antdText("plan-ref-label", true, "Plan Ref", nil), + parsedText("plan-ref-value", "{reqsJsonPath[0]['.spec.planRef.name']['-']}", nil), + }), + antdFlexVertical("spec-application-ref-block", 4, []any{ + antdText("application-ref-label", true, "Application", nil), + parsedText("application-ref-value", "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}", nil), + }), + antdFlexVertical("spec-storage-ref-block", 4, []any{ + antdText("storage-ref-label", true, "Storage", nil), + parsedText("storage-ref-value", "{reqsJsonPath[0]['.spec.storageRef.kind']['-']}.{reqsJsonPath[0]['.spec.storageRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.storageRef.name']['-']}", nil), + }), + antdFlexVertical("spec-strategy-ref-block", 4, []any{ + antdText("strategy-ref-label", true, "Strategy", nil), + parsedText("strategy-ref-value", "{reqsJsonPath[0]['.spec.strategyRef.name']['-']}", nil), + }), + antdFlexVertical("status-backup-ref-block", 4, []any{ + antdText("backup-ref-label", true, "Backup Ref", nil), + parsedText("backup-ref-value", "{reqsJsonPath[0]['.status.backupRef.name']['-']}", nil), + }), + antdFlexVertical("status-started-at-block", 4, []any{ + antdText("started-at-label", true, "Started At", nil), + antdFlex("time-block", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "time-icon", + "text": "🌐", + }, + }, + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "formatter": "timestamp", + "id": "time-value", + "text": "{reqsJsonPath[0]['.status.startedAt']['-']}", + }, + }, + }), + }), + antdFlexVertical("status-completed-at-block", 4, []any{ + antdText("completed-at-label", true, "Completed At", nil), + antdFlex("time-block", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "time-icon", + "text": "🌐", + }, + }, + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "formatter": "timestamp", + "id": "time-value", + "text": "{reqsJsonPath[0]['.status.completedAt']['-']}", + }, + }, + }), + }), + antdFlexVertical("status-message-block", 4, []any{ + antdText("message-label", true, "Message", nil), + parsedText("message-value", "{reqsJsonPath[0]['.status.message']['-']}", nil), + }), + }), + }), + }), + }), + }, + }, + } + backupJobSpec := createUnifiedFactory(backupJobConfig, backupJobTabs, []any{"/api/clusters/{2}/k8s/apis/backups.cozystack.io/v1alpha1/namespaces/{3}/backupjobs/{6}"}) + + // Backup details factory using unified approach + backupConfig := UnifiedResourceConfig{ + Name: "backup-details", + ResourceType: "factory", + Kind: "Backup", + Plural: "backups", + Title: "backup", + } + backupTabs := []any{ + map[string]any{ + "key": "details", + "label": "Details", + "children": []any{ + contentCard("details-card", map[string]any{ + "marginBottom": "24px", + }, []any{ + antdText("details-title", true, "Backup details", map[string]any{ + "fontSize": 20, + "marginBottom": "12px", + }), + spacer("details-spacer", 16), + antdRow("details-grid", []any{48, 12}, []any{ + antdCol("col-left", 12, []any{ + antdFlexVertical("col-left-stack", 24, []any{ + antdFlexVertical("meta-name-block", 4, []any{ + antdText("meta-name-label", true, "Name", nil), + parsedText("meta-name-value", "{reqsJsonPath[0]['.metadata.name']['-']}", nil), + }), + antdFlexVertical("meta-namespace-block", 8, []any{ + antdText("meta-namespace-label", true, "Namespace", nil), + antdFlex("header-row", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "header-badge", + "text": "NS", + "title": "namespace", + "style": map[string]any{ + "backgroundColor": "#a25792ff", + "borderRadius": "20px", + "color": "#fff", + "display": "inline-block", + "fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif", + "fontSize": "15px", + "fontWeight": 400, + "lineHeight": "24px", + "minWidth": 24, + "padding": "0 9px", + "textAlign": "center", + "whiteSpace": "nowrap", + }, + }, + }, + map[string]any{ + "type": "antdLink", + "data": map[string]any{ + "id": "namespace-link", + "text": "{reqsJsonPath[0]['.metadata.namespace']['-']}", + "href": "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace", + }, + }, + }), + }), + antdFlexVertical("meta-created-block", 4, []any{ + antdText("time-label", true, "Created", nil), + antdFlex("time-block", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "time-icon", + "text": "🌐", + }, + }, + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "formatter": "timestamp", + "id": "time-value", + "text": "{reqsJsonPath[0]['.metadata.creationTimestamp']['-']}", + }, + }, + }), + }), + }), + }), + antdCol("col-right", 12, []any{ + antdFlexVertical("col-right-stack", 24, []any{ + antdFlexVertical("status-phase-block", 4, []any{ + antdText("phase-label", true, "Phase", nil), + parsedText("phase-value", "{reqsJsonPath[0]['.status.phase']['-']}", nil), + }), + antdFlexVertical("spec-taken-at-block", 4, []any{ + antdText("taken-at-label", true, "Taken At", nil), + antdFlex("time-block", 6, []any{ + map[string]any{ + "type": "antdText", + "data": map[string]any{ + "id": "time-icon", + "text": "🌐", + }, + }, + map[string]any{ + "type": "parsedText", + "data": map[string]any{ + "formatter": "timestamp", + "id": "time-value", + "text": "{reqsJsonPath[0]['.spec.takenAt']['-']}", + }, + }, + }), + }), + antdFlexVertical("spec-plan-ref-block", 4, []any{ + antdText("plan-ref-label", true, "Plan Ref", nil), + parsedText("plan-ref-value", "{reqsJsonPath[0]['.spec.planRef.name']['-']}", nil), + }), + antdFlexVertical("spec-application-ref-block", 4, []any{ + antdText("application-ref-label", true, "Application", nil), + parsedText("application-ref-value", "{reqsJsonPath[0]['.spec.applicationRef.kind']['-']}.{reqsJsonPath[0]['.spec.applicationRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.applicationRef.name']['-']}", nil), + }), + antdFlexVertical("spec-storage-ref-block", 4, []any{ + antdText("storage-ref-label", true, "Storage", nil), + parsedText("storage-ref-value", "{reqsJsonPath[0]['.spec.storageRef.kind']['-']}.{reqsJsonPath[0]['.spec.storageRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.storageRef.name']['-']}", nil), + }), + antdFlexVertical("spec-strategy-ref-block", 4, []any{ + antdText("strategy-ref-label", true, "Strategy", nil), + parsedText("strategy-ref-value", "{reqsJsonPath[0]['.spec.strategyRef.kind']['-']}.{reqsJsonPath[0]['.spec.strategyRef.apiGroup']['-']}/{reqsJsonPath[0]['.spec.strategyRef.name']['-']}", nil), + }), + antdFlexVertical("status-artifact-uri-block", 4, []any{ + antdText("artifact-uri-label", true, "Artifact URI", nil), + parsedText("artifact-uri-value", "{reqsJsonPath[0]['.status.artifact.uri']['-']}", nil), + }), + antdFlexVertical("status-artifact-size-block", 4, []any{ + antdText("artifact-size-label", true, "Artifact Size", nil), + parsedText("artifact-size-value", "{reqsJsonPath[0]['.status.artifact.sizeBytes']['-']}", nil), + }), + antdFlexVertical("status-artifact-checksum-block", 4, []any{ + antdText("artifact-checksum-label", true, "Artifact Checksum", nil), + parsedText("artifact-checksum-value", "{reqsJsonPath[0]['.status.artifact.checksum']['-']}", nil), + }), + }), + }), + }), + }), + }, + }, + } + backupSpec := createUnifiedFactory(backupConfig, backupTabs, []any{"/api/clusters/{2}/k8s/apis/backups.cozystack.io/v1alpha1/namespaces/{3}/backups/{6}"}) + return []*dashboardv1alpha1.Factory{ createFactory("marketplace", marketplaceSpec), createFactory("namespace-details", namespaceSpec), @@ -1442,6 +1894,9 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { createFactory("kube-service-details", serviceSpec), createFactory("kube-ingress-details", ingressSpec), createFactory("workloadmonitor-details", workloadmonitorSpec), + createFactory("plan-details", planSpec), + createFactory("backupjob-details", backupJobSpec), + createFactory("backup-details", backupSpec), } } From deb8d22bc880fafee668bf88d664cd70e3fea1d0 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Mon, 29 Dec 2025 22:12:59 +0400 Subject: [PATCH 42/45] [backups] Scaffold a Velero strategy (#1742) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What this PR does This patch adds boilerplate code for a Velero strategy for the backups API. Implementation can be added into the Reconcile function in a subsequent PR. ### ```release-note [backups] Add the boilerplate for a new Velero strategy for backups. ``` ## Summary by CodeRabbit * **New Features** * Added Velero as a supported backup strategy, including a cluster-scoped Velero resource type and CRD for creating Velero-backed strategies. * Backup job controller updated to recognize and dispatch Velero-backed jobs, with status conditions exposed for monitoring progress and health. * Introduced basic reconciliation flow for Velero strategy to enable future behavior and observability. ✏️ Tip: You can customize this high-level summary in your review settings. (cherry picked from commit 715509f57e4a02e04beccf18883b8d0da40b51af) Signed-off-by: Timofei Larkin --- api/backups/strategy/v1alpha1/velero_types.go | 54 +++++++++ .../v1alpha1/zz_generated.deepcopy.go | 96 ++++++++++++++++ cmd/backupstrategy-controller/main.go | 2 +- .../backupcontroller/backupjob_controller.go | 53 +++++++++ .../jobstrategy_controller.go | 18 +-- .../velerostrategy_controller.go | 15 +++ ...trategy.backups.cozystack.io_veleroes.yaml | 106 ++++++++++++++++++ 7 files changed, 326 insertions(+), 18 deletions(-) create mode 100644 api/backups/strategy/v1alpha1/velero_types.go create mode 100644 internal/backupcontroller/backupjob_controller.go create mode 100644 internal/backupcontroller/velerostrategy_controller.go create mode 100644 packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_veleroes.yaml diff --git a/api/backups/strategy/v1alpha1/velero_types.go b/api/backups/strategy/v1alpha1/velero_types.go new file mode 100644 index 00000000..ea61d421 --- /dev/null +++ b/api/backups/strategy/v1alpha1/velero_types.go @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// Package v1alpha1 defines strategy.backups.cozystack.io API types. +// +// Group: strategy.backups.cozystack.io +// Version: v1alpha1 +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + SchemeBuilder.Register(func(s *runtime.Scheme) error { + s.AddKnownTypes(GroupVersion, + &Velero{}, + &VeleroList{}, + ) + return nil + }) +} + +const ( + VeleroStrategyKind = "Velero" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster + +// Velero defines a backup strategy using Velero as the driver. +type Velero struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec VeleroSpec `json:"spec,omitempty"` + Status VeleroStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// VeleroList contains a list of Velero backup strategies. +type VeleroList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Velero `json:"items"` +} + +// VeleroSpec specifies the desired strategy for backing up with Velero. +type VeleroSpec struct{} + +type VeleroStatus struct { + Conditions []metav1.Condition `json:"conditions,omitempty"` +} diff --git a/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go b/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go index 5c300bfe..50ee19a4 100644 --- a/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go +++ b/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go @@ -121,3 +121,99 @@ func (in *JobStatus) DeepCopy() *JobStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Velero) DeepCopyInto(out *Velero) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Velero. +func (in *Velero) DeepCopy() *Velero { + if in == nil { + return nil + } + out := new(Velero) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Velero) 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 *VeleroList) DeepCopyInto(out *VeleroList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Velero, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroList. +func (in *VeleroList) DeepCopy() *VeleroList { + if in == nil { + return nil + } + out := new(VeleroList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VeleroList) 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 *VeleroSpec) DeepCopyInto(out *VeleroSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroSpec. +func (in *VeleroSpec) DeepCopy() *VeleroSpec { + if in == nil { + return nil + } + out := new(VeleroSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VeleroStatus) DeepCopyInto(out *VeleroStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroStatus. +func (in *VeleroStatus) DeepCopy() *VeleroStatus { + if in == nil { + return nil + } + out := new(VeleroStatus) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/backupstrategy-controller/main.go b/cmd/backupstrategy-controller/main.go index c44f0dc8..61edc40a 100644 --- a/cmd/backupstrategy-controller/main.go +++ b/cmd/backupstrategy-controller/main.go @@ -147,7 +147,7 @@ func main() { os.Exit(1) } - if err = (&backupcontroller.BackupJobStrategyReconciler{ + if err = (&backupcontroller.BackupJobReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { diff --git a/internal/backupcontroller/backupjob_controller.go b/internal/backupcontroller/backupjob_controller.go new file mode 100644 index 00000000..c16a0ec1 --- /dev/null +++ b/internal/backupcontroller/backupjob_controller.go @@ -0,0 +1,53 @@ +package backupcontroller + +import ( + "context" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" +) + +// BackupVeleroStrategyReconciler reconciles BackupJob with a strategy referencing +// Velero.strategy.backups.cozystack.io objects. +type BackupJobReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *BackupJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + j := &backupsv1alpha1.BackupJob{} + err := r.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: req.Name}, j) + if err != nil { + if apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + if j.Spec.StrategyRef.APIGroup == nil || *j.Spec.StrategyRef.APIGroup != strategyv1alpha1.GroupVersion.Group { + return ctrl.Result{}, nil + } + switch j.Spec.StrategyRef.Kind { + case strategyv1alpha1.JobStrategyKind: + return r.reconcileJob(ctx, j) + case strategyv1alpha1.VeleroStrategyKind: + return r.reconcileVelero(ctx, j) + default: + return ctrl.Result{}, nil + } + +} + +// SetupWithManager registers our controller with the Manager and sets up watches. +func (r *BackupJobReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&backupsv1alpha1.BackupJob{}). + Complete(r) +} diff --git a/internal/backupcontroller/jobstrategy_controller.go b/internal/backupcontroller/jobstrategy_controller.go index 4da02e0b..e52e38f2 100644 --- a/internal/backupcontroller/jobstrategy_controller.go +++ b/internal/backupcontroller/jobstrategy_controller.go @@ -3,29 +3,13 @@ package backupcontroller import ( "context" - "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" ) -// BackupJobStrategyReconciler reconciles BackupJob with a strategy referencing -// Job.strategy.backups.cozystack.io objects. -type BackupJobStrategyReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -func (r *BackupJobStrategyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *BackupJobReconciler) reconcileJob(ctx context.Context, j *backupsv1alpha1.BackupJob) (ctrl.Result, error) { _ = log.FromContext(ctx) return ctrl.Result{}, nil } - -// SetupWithManager registers our controller with the Manager and sets up watches. -func (r *BackupJobStrategyReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&backupsv1alpha1.BackupJob{}). - Complete(r) -} diff --git a/internal/backupcontroller/velerostrategy_controller.go b/internal/backupcontroller/velerostrategy_controller.go new file mode 100644 index 00000000..85049b7b --- /dev/null +++ b/internal/backupcontroller/velerostrategy_controller.go @@ -0,0 +1,15 @@ +package backupcontroller + +import ( + "context" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" +) + +func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1alpha1.BackupJob) (ctrl.Result, error) { + _ = log.FromContext(ctx) + return ctrl.Result{}, nil +} diff --git a/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_veleroes.yaml b/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_veleroes.yaml new file mode 100644 index 00000000..4433c7cb --- /dev/null +++ b/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_veleroes.yaml @@ -0,0 +1,106 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: veleroes.strategy.backups.cozystack.io +spec: + group: strategy.backups.cozystack.io + names: + kind: Velero + listKind: VeleroList + plural: veleroes + singular: velero + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Velero defines a backup strategy using Velero as the driver. + properties: + apiVersion: + 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 + kind: + 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 + metadata: + type: object + spec: + description: VeleroSpec specifies the desired strategy for backing up + with Velero. + type: object + status: + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} From 16d82b13adf0e1b900cf3766a8bb66663bac72d5 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Mon, 5 Jan 2026 16:29:38 +0400 Subject: [PATCH 43/45] Velero backup controller impl (#1762) ## What this PR does Implement a controller for BackupJobs referencing a Velero strategy. Creates a Backup.velero.io according to the template in the `Velero.strategy.backups.cozystack.io`. ### Release note ```release-note [backups] Implement a backup strategy controller for Velero strategies. ``` Signed-off-by: Timofei Larkin Co-authored-by: Andrey Kolkov Co-authored-by: Timofei Larkin (cherry picked from commit 069a3ca9b0364af6f87fe86b7ddd7cfe29e55728) Signed-off-by: Timofei Larkin --- api/backups/strategy/v1alpha1/velero_types.go | 11 +- .../v1alpha1/zz_generated.deepcopy.go | 19 +- api/backups/v1alpha1/backupjob_types.go | 7 + api/backups/v1alpha1/groupversion_info.go | 7 +- cmd/backup-controller/main.go | 13 + examples/desired-backup.yaml | 20 + go.mod | 57 +- go.sum | 118 +-- hack/e2e-apps/backup.bats.disabled | 226 +++++ .../backupcontroller/backupjob_controller.go | 48 +- .../velerostrategy_controller.go | 614 ++++++++++++- internal/template/template.go | 68 ++ internal/template/template_test.go | 68 ++ .../backups.cozystack.io_backupjobs.yaml | 8 +- .../backup-controller/templates/rbac.yaml | 21 + .../backup-controller/templates/strategy.yaml | 5 + .../strategy.backups.cozystack.io_jobs.yaml | 845 ++++++++++++++---- 17 files changed, 1896 insertions(+), 259 deletions(-) create mode 100644 examples/desired-backup.yaml create mode 100755 hack/e2e-apps/backup.bats.disabled create mode 100644 internal/template/template.go create mode 100644 internal/template/template_test.go create mode 100644 packages/system/backup-controller/templates/strategy.yaml diff --git a/api/backups/strategy/v1alpha1/velero_types.go b/api/backups/strategy/v1alpha1/velero_types.go index ea61d421..9461c753 100644 --- a/api/backups/strategy/v1alpha1/velero_types.go +++ b/api/backups/strategy/v1alpha1/velero_types.go @@ -6,6 +6,7 @@ package v1alpha1 import ( + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -47,7 +48,15 @@ type VeleroList struct { } // VeleroSpec specifies the desired strategy for backing up with Velero. -type VeleroSpec struct{} +type VeleroSpec struct { + Template VeleroTemplate `json:"template"` +} + +// VeleroTemplate describes the data a backup.velero.io should have when +// templated from a Velero backup strategy. +type VeleroTemplate struct { + Spec velerov1.BackupSpec `json:"spec"` +} type VeleroStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` diff --git a/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go b/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go index 50ee19a4..c1989ac9 100644 --- a/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go +++ b/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go @@ -127,7 +127,7 @@ func (in *Velero) DeepCopyInto(out *Velero) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -184,6 +184,7 @@ func (in *VeleroList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VeleroSpec) DeepCopyInto(out *VeleroSpec) { *out = *in + in.Template.DeepCopyInto(&out.Template) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroSpec. @@ -217,3 +218,19 @@ func (in *VeleroStatus) DeepCopy() *VeleroStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VeleroTemplate) DeepCopyInto(out *VeleroTemplate) { + *out = *in + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroTemplate. +func (in *VeleroTemplate) DeepCopy() *VeleroTemplate { + if in == nil { + return nil + } + out := new(VeleroTemplate) + in.DeepCopyInto(out) + return out +} diff --git a/api/backups/v1alpha1/backupjob_types.go b/api/backups/v1alpha1/backupjob_types.go index 1230b209..c27b5347 100644 --- a/api/backups/v1alpha1/backupjob_types.go +++ b/api/backups/v1alpha1/backupjob_types.go @@ -21,6 +21,11 @@ func init() { }) } +const ( + OwningJobNameLabel = thisGroup + "/owned-by.BackupJobName" + OwningJobNamespaceLabel = thisGroup + "/owned-by.BackupJobNamespace" +) + // BackupJobPhase represents the lifecycle phase of a BackupJob. type BackupJobPhase string @@ -85,6 +90,8 @@ type BackupJobStatus struct { // The field indexing on applicationRef will be needed later to display per-app backup resources. // +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",priority=0 // +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.apiGroup` // +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.kind` // +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.name` diff --git a/api/backups/v1alpha1/groupversion_info.go b/api/backups/v1alpha1/groupversion_info.go index 8cdd663a..87ad6f38 100644 --- a/api/backups/v1alpha1/groupversion_info.go +++ b/api/backups/v1alpha1/groupversion_info.go @@ -25,8 +25,13 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) +const ( + thisGroup = "backups.cozystack.io" + thisVersion = "v1alpha1" +) + var ( - GroupVersion = schema.GroupVersion{Group: "backups.cozystack.io", Version: "v1alpha1"} + GroupVersion = schema.GroupVersion{Group: thisGroup, Version: thisVersion} SchemeBuilder = runtime.NewSchemeBuilder(addGroupVersion) AddToScheme = SchemeBuilder.AddToScheme ) diff --git a/cmd/backup-controller/main.go b/cmd/backup-controller/main.go index d8436659..a99f9350 100644 --- a/cmd/backup-controller/main.go +++ b/cmd/backup-controller/main.go @@ -35,8 +35,10 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" "github.com/cozystack/cozystack/internal/backupcontroller" + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" // +kubebuilder:scaffold:imports ) @@ -49,6 +51,8 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(backupsv1alpha1.AddToScheme(scheme)) + utilruntime.Must(strategyv1alpha1.AddToScheme(scheme)) + utilruntime.Must(velerov1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -155,6 +159,15 @@ func main() { os.Exit(1) } + if err = (&backupcontroller.BackupJobReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("backup-controller"), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "BackupJob") + os.Exit(1) + } + // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/examples/desired-backup.yaml b/examples/desired-backup.yaml new file mode 100644 index 00000000..c06b8f82 --- /dev/null +++ b/examples/desired-backup.yaml @@ -0,0 +1,20 @@ +apiVersion: backups.cozystack.io/v1alpha1 +kind: BackupJob +metadata: + name: desired-backup + namespace: tenant-root + labels: + backups.cozystack.io/triggered-by: manual +spec: + applicationRef: + apiGroup: apps.cozystack.io + kind: VirtualMachine + name: vm1 + storageRef: + apiGroup: apps.cozystack.io + kind: Bucket + name: test-bucket + strategyRef: + apiGroup: strategy.backups.cozystack.io + kind: Velero + name: velero-strategy-default \ No newline at end of file diff --git a/go.mod b/go.mod index 6ae20909..ee771e5d 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,8 @@ require ( github.com/onsi/gomega v1.33.1 github.com/prometheus/client_golang v1.19.1 github.com/robfig/cron/v3 v3.0.1 - github.com/spf13/cobra v1.8.1 - github.com/stretchr/testify v1.9.0 + github.com/spf13/cobra v1.9.1 + github.com/vmware-tanzu/velero v1.17.1 go.uber.org/zap v1.27.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.31.2 @@ -77,40 +77,41 @@ require ( github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.etcd.io/etcd/api/v3 v3.5.16 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect - go.etcd.io/etcd/client/v3 v3.5.16 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.etcd.io/etcd/api/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.7.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/net v0.45.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/time v0.12.0 // indirect + golang.org/x/tools v0.37.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/grpc v1.73.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index 88c81e7b..bf792037 100644 --- a/go.sum +++ b/go.sum @@ -139,12 +139,12 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -173,46 +173,50 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/vmware-tanzu/velero v1.17.1 h1:ldKeiTuUwkThOw7zrUucNA1NwnLG66zl13YetWAoE0I= +github.com/vmware-tanzu/velero v1.17.1/go.mod h1:3KTxuUN6Un38JzmYAX+8U6j2k6EexGoNNxa8jrJML8U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= -go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= -go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= -go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= -go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E= -go.etcd.io/etcd/client/v2 v2.305.13 h1:RWfV1SX5jTU0lbCvpVQe3iPQeAHETWdOTb6pxhd77C8= -go.etcd.io/etcd/client/v2 v2.305.13/go.mod h1:iQnL7fepbiomdXMb3om1rHq96htNNGv2sJkEcZGDRRg= -go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE= -go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= -go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M= -go.etcd.io/etcd/pkg/v3 v3.5.13/go.mod h1:N+4PLrp7agI/Viy+dUYpX7iRtSPvKq+w8Y14d1vX+m0= -go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= -go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= -go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= -go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= +go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= +go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= +go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= +go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= +go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= +go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= +go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg= +go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= +go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -232,10 +236,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -250,10 +254,10 @@ golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -266,16 +270,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/hack/e2e-apps/backup.bats.disabled b/hack/e2e-apps/backup.bats.disabled new file mode 100755 index 00000000..0a5c13d9 --- /dev/null +++ b/hack/e2e-apps/backup.bats.disabled @@ -0,0 +1,226 @@ +#!/usr/bin/env bats + +# Test variables - stored for teardown +TEST_NAMESPACE='tenant-test' +TEST_BUCKET_NAME='test-backup-bucket' +TEST_VM_NAME='test-backup-vm' +TEST_BACKUPJOB_NAME='test-backup-job' + +teardown() { + # Clean up resources (runs even if test fails) + namespace="${TEST_NAMESPACE}" + bucket_name="${TEST_BUCKET_NAME}" + vm_name="${TEST_VM_NAME}" + backupjob_name="${TEST_BACKUPJOB_NAME}" + + # Clean up port-forward if still running + pkill -f "kubectl.*port-forward.*seaweedfs-s3" 2>/dev/null || true + + # Clean up Velero resources in cozy-velero namespace + # Find Velero backup by pattern matching namespace-backupjob + for backup in $(kubectl -n cozy-velero get backups.velero.io -o jsonpath='{.items[*].metadata.name}' 2>/dev/null || true); do + if echo "$backup" | grep -q "^${namespace}-${backupjob_name}-"; then + kubectl -n cozy-velero delete backups.velero.io ${backup} --wait=false 2>/dev/null || true + fi + done + + # Clean up BackupStorageLocation and VolumeSnapshotLocation (named: namespace-backupjob) + BSL_NAME="${namespace}-${backupjob_name}" + kubectl -n cozy-velero delete backupstoragelocations.velero.io ${BSL_NAME} --wait=false 2>/dev/null || true + kubectl -n cozy-velero delete volumesnapshotlocations.velero.io ${BSL_NAME} --wait=false 2>/dev/null || true + + # Clean up Velero credentials secret + SECRET_NAME="backup-${namespace}-${backupjob_name}-s3-credentials" + kubectl -n cozy-velero delete secret ${SECRET_NAME} --wait=false 2>/dev/null || true + + # Clean up BackupJob + kubectl -n ${namespace} delete backupjob ${backupjob_name} --wait=false 2>/dev/null || true + + # Clean up Virtual Machine + kubectl -n ${namespace} delete virtualmachines.apps.cozystack.io ${vm_name} --wait=false 2>/dev/null || true + + # Clean up Bucket + kubectl -n ${namespace} delete bucket.apps.cozystack.io ${bucket_name} --wait=false 2>/dev/null || true + + # Clean up temporary files + rm -f /tmp/bucket-backup-credentials.json +} + +print_log() { + echo "# $1" >&3 +} + +@test "Create Backup for Virtual Machine" { + # Test variables + bucket_name="${TEST_BUCKET_NAME}" + vm_name="${TEST_VM_NAME}" + backupjob_name="${TEST_BACKUPJOB_NAME}" + namespace="${TEST_NAMESPACE}" + + print_log "Step 0:Ensure BackupJob and Velero strategy CRDs are installed" + kubectl apply -f packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml + kubectl apply -f packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_veleroes.yaml + # Wait for CRDs to be ready + kubectl wait --for condition=established --timeout=30s crd backupjobs.backups.cozystack.io + kubectl wait --for condition=established --timeout=30s crd veleroes.strategy.backups.cozystack.io + + # Ensure velero-strategy-default resource exists + kubectl apply -f packages/system/backup-controller/templates/strategy.yaml + + print_log "Step 1: Create the bucket resource" + kubectl apply -f - < /tmp/bucket-backup-credentials.json + ACCESS_KEY=$(jq -r '.spec.secretS3.accessKeyID' /tmp/bucket-backup-credentials.json) + SECRET_KEY=$(jq -r '.spec.secretS3.accessSecretKey' /tmp/bucket-backup-credentials.json) + BUCKET_NAME=$(jq -r '.spec.bucketName' /tmp/bucket-backup-credentials.json) + + print_log "Step 2: Create the Virtual Machine" + kubectl apply -f - </dev/null); do + if echo "$backup" | grep -q "^${namespace}-${backupjob_name}-"; then + VELERO_BACKUP_NAME=$backup + VELERO_BACKUP_PHASE=$(kubectl -n cozy-velero get backups.velero.io $backup -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + break + fi + done + + print_log "Verify Velero Backup was found" + [ -n "$VELERO_BACKUP_NAME" ] + + echo '# Wait for Velero Backup to complete' >&3 + until kubectl -n cozy-velero get backups.velero.io ${VELERO_BACKUP_NAME} -o jsonpath='{.status.phase}' | grep -q 'Completed\|Failed'; do + sleep 5 + done + + print_log "Verify Velero Backup is Completed" + timeout 90 sh -ec "until [ \"\$(kubectl -n cozy-velero get backups.velero.io ${VELERO_BACKUP_NAME} -o jsonpath='{.status.phase}' 2>/dev/null)\" = \"Completed\" ]; do sleep 30; done" + + # Final verification + VELERO_BACKUP_PHASE=$(kubectl -n cozy-velero get backups.velero.io ${VELERO_BACKUP_NAME} -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + [ "$VELERO_BACKUP_PHASE" = "Completed" ] + + print_log "Step 4: Verify S3 has backup data" + # Start port-forwarding to S3 service (with timeout to keep it alive) + bash -c 'timeout 100s kubectl port-forward service/seaweedfs-s3 -n tenant-root 8333:8333 > /dev/null 2>&1 &' + + # Wait for port-forward to be ready + timeout 30 sh -ec "until nc -z localhost 8333; do sleep 1; done" + + # Wait a bit for backup data to be written to S3 + sleep 30 + + # Set up MinIO client with insecure flag (use environment variable for all commands) + export MC_INSECURE=1 + mc alias set local https://localhost:8333 $ACCESS_KEY $SECRET_KEY + + # Verify backup directory exists in S3 + BACKUP_PATH="${BUCKET_NAME}/backups/${VELERO_BACKUP_NAME}" + mc ls local/${BACKUP_PATH}/ 2>/dev/null + [ $? -eq 0 ] + + # Verify backup files exist (at least metadata files) + BACKUP_FILES=$(mc ls local/${BACKUP_PATH}/ 2>/dev/null | wc -l || echo "0") + [ "$BACKUP_FILES" -gt "0" ] +} + diff --git a/internal/backupcontroller/backupjob_controller.go b/internal/backupcontroller/backupjob_controller.go index c16a0ec1..0db2e1dd 100644 --- a/internal/backupcontroller/backupjob_controller.go +++ b/internal/backupcontroller/backupjob_controller.go @@ -2,12 +2,18 @@ package backupcontroller import ( "context" + "net/http" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/log" strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" @@ -18,35 +24,69 @@ import ( // Velero.strategy.backups.cozystack.io objects. type BackupJobReconciler struct { client.Client - Scheme *runtime.Scheme + dynamic.Interface + meta.RESTMapper + Scheme *runtime.Scheme + Recorder record.EventRecorder } func (r *BackupJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + logger := log.FromContext(ctx) + logger.Info("reconciling BackupJob", "namespace", req.Namespace, "name", req.Name) + j := &backupsv1alpha1.BackupJob{} err := r.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: req.Name}, j) if err != nil { if apierrors.IsNotFound(err) { + logger.V(1).Info("BackupJob not found, skipping") return ctrl.Result{}, nil } + logger.Error(err, "failed to get BackupJob") return ctrl.Result{}, err } - if j.Spec.StrategyRef.APIGroup == nil || *j.Spec.StrategyRef.APIGroup != strategyv1alpha1.GroupVersion.Group { + + if j.Spec.StrategyRef.APIGroup == nil { + logger.V(1).Info("BackupJob has nil StrategyRef.APIGroup, skipping", "backupjob", j.Name) return ctrl.Result{}, nil } + + if *j.Spec.StrategyRef.APIGroup != strategyv1alpha1.GroupVersion.Group { + logger.V(1).Info("BackupJob StrategyRef.APIGroup doesn't match, skipping", + "backupjob", j.Name, + "expected", strategyv1alpha1.GroupVersion.Group, + "got", *j.Spec.StrategyRef.APIGroup) + return ctrl.Result{}, nil + } + + logger.Info("processing BackupJob", "backupjob", j.Name, "strategyKind", j.Spec.StrategyRef.Kind) switch j.Spec.StrategyRef.Kind { case strategyv1alpha1.JobStrategyKind: return r.reconcileJob(ctx, j) case strategyv1alpha1.VeleroStrategyKind: return r.reconcileVelero(ctx, j) default: + logger.V(1).Info("BackupJob StrategyRef.Kind not supported, skipping", + "backupjob", j.Name, + "kind", j.Spec.StrategyRef.Kind, + "supported", []string{strategyv1alpha1.JobStrategyKind, strategyv1alpha1.VeleroStrategyKind}) return ctrl.Result{}, nil } - } // SetupWithManager registers our controller with the Manager and sets up watches. func (r *BackupJobReconciler) SetupWithManager(mgr ctrl.Manager) error { + cfg := mgr.GetConfig() + var err error + if r.Interface, err = dynamic.NewForConfig(cfg); err != nil { + return err + } + var h *http.Client + if h, err = rest.HTTPClientFor(cfg); err != nil { + return err + } + if r.RESTMapper, err = apiutil.NewDynamicRESTMapper(cfg, h); err != nil { + return err + } return ctrl.NewControllerManagedBy(mgr). For(&backupsv1alpha1.BackupJob{}). Complete(r) diff --git a/internal/backupcontroller/velerostrategy_controller.go b/internal/backupcontroller/velerostrategy_controller.go index 85049b7b..066325c0 100644 --- a/internal/backupcontroller/velerostrategy_controller.go +++ b/internal/backupcontroller/velerostrategy_controller.go @@ -2,14 +2,626 @@ package backupcontroller import ( "context" + "encoding/json" + "fmt" + "reflect" + "time" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + "github.com/cozystack/cozystack/internal/template" + + "github.com/go-logr/logr" + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" ) +func getLogger(ctx context.Context) loggerWithDebug { + return loggerWithDebug{Logger: log.FromContext(ctx)} +} + +// loggerWithDebug wraps a logr.Logger and provides a Debug() method +// that maps to V(1).Info() for convenience. +type loggerWithDebug struct { + logr.Logger +} + +// Debug logs at debug level (equivalent to V(1).Info()) +func (l loggerWithDebug) Debug(msg string, keysAndValues ...interface{}) { + l.Logger.V(1).Info(msg, keysAndValues...) +} + +// S3Credentials holds the discovered S3 credentials from a Bucket storageRef +type S3Credentials struct { + BucketName string + Endpoint string + Region string + AccessKeyID string + AccessSecretKey string +} + +// bucketInfo represents the structure of BucketInfo stored in the secret +type bucketInfo struct { + Spec struct { + BucketName string `json:"bucketName"` + SecretS3 struct { + Endpoint string `json:"endpoint"` + Region string `json:"region"` + AccessKeyID string `json:"accessKeyID"` + AccessSecretKey string `json:"accessSecretKey"` + } `json:"secretS3"` + } `json:"spec"` +} + +const ( + defaultRequeueAfter = 5 * time.Second + defaultActiveJobPollingInterval = defaultRequeueAfter + // Velero requires API objects and secrets to be in the cozy-velero namespace + veleroNamespace = "cozy-velero" + virtualMachinePrefix = "virtual-machine-" +) + +func storageS3SecretName(namespace, backupJobName string) string { + return fmt.Sprintf("backup-%s-%s-s3-credentials", namespace, backupJobName) +} + +func boolPtr(b bool) *bool { + return &b +} + func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1alpha1.BackupJob) (ctrl.Result, error) { - _ = log.FromContext(ctx) + logger := getLogger(ctx) + logger.Debug("reconciling Velero strategy", "backupjob", j.Name, "phase", j.Status.Phase) + + // If already completed, no need to reconcile + if j.Status.Phase == backupsv1alpha1.BackupJobPhaseSucceeded || + j.Status.Phase == backupsv1alpha1.BackupJobPhaseFailed { + logger.Debug("BackupJob already completed, skipping", "phase", j.Status.Phase) + return ctrl.Result{}, nil + } + + // Step 1: On first reconcile, set startedAt (but not phase yet - phase will be set after backup creation) + logger.Debug("checking BackupJob status", "startedAt", j.Status.StartedAt, "phase", j.Status.Phase) + if j.Status.StartedAt == nil { + logger.Debug("setting BackupJob StartedAt") + now := metav1.Now() + j.Status.StartedAt = &now + // Don't set phase to Running yet - will be set after Velero backup is successfully created + if err := r.Status().Update(ctx, j); err != nil { + logger.Error(err, "failed to update BackupJob status") + return ctrl.Result{}, err + } + logger.Debug("set BackupJob StartedAt", "startedAt", j.Status.StartedAt) + } else { + logger.Debug("BackupJob already started", "startedAt", j.Status.StartedAt, "phase", j.Status.Phase) + } + + // Step 2: Resolve inputs - Read Strategy, Storage, Application, optionally Plan + logger.Debug("fetching Velero strategy", "strategyName", j.Spec.StrategyRef.Name) + veleroStrategy := &strategyv1alpha1.Velero{} + if err := r.Get(ctx, client.ObjectKey{Name: j.Spec.StrategyRef.Name}, veleroStrategy); err != nil { + if errors.IsNotFound(err) { + logger.Error(err, "Velero strategy not found", "strategyName", j.Spec.StrategyRef.Name) + return r.markBackupJobFailed(ctx, j, fmt.Sprintf("Velero strategy not found: %s", j.Spec.StrategyRef.Name)) + } + logger.Error(err, "failed to get Velero strategy") + return ctrl.Result{}, err + } + logger.Debug("fetched Velero strategy", "strategyName", veleroStrategy.Name) + + // Step 3: Execute backup logic + // Check if we already created a Velero Backup + // Use human-readable timestamp: YYYY-MM-DD-HH-MM-SS + if j.Status.StartedAt == nil { + logger.Error(nil, "StartedAt is nil after status update, this should not happen") + return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil + } + logger.Debug("checking for existing Velero Backup", "namespace", veleroNamespace) + veleroBackupList := &velerov1.BackupList{} + opts := []client.ListOption{ + client.InNamespace(veleroNamespace), + client.MatchingLabels{ + backupsv1alpha1.OwningJobNamespaceLabel: j.Namespace, + backupsv1alpha1.OwningJobNameLabel: j.Name, + }, + } + + if err := r.List(ctx, veleroBackupList, opts...); err != nil { + logger.Error(err, "failed to get Velero Backup") + return ctrl.Result{}, err + } + + if len(veleroBackupList.Items) == 0 { + // Create Velero Backup + logger.Debug("Velero Backup not found, creating new one") + if err := r.createVeleroBackup(ctx, j, veleroStrategy); err != nil { + logger.Error(err, "failed to create Velero Backup") + return r.markBackupJobFailed(ctx, j, fmt.Sprintf("failed to create Velero Backup: %v", err)) + } + // After successful Velero backup creation, set phase to Running + if j.Status.Phase != backupsv1alpha1.BackupJobPhaseRunning { + logger.Debug("setting BackupJob phase to Running after successful Velero backup creation") + j.Status.Phase = backupsv1alpha1.BackupJobPhaseRunning + if err := r.Status().Update(ctx, j); err != nil { + logger.Error(err, "failed to update BackupJob phase to Running") + return ctrl.Result{}, err + } + } + logger.Debug("created Velero Backup, requeuing") + // Requeue to check status + return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil + } + + if len(veleroBackupList.Items) > 1 { + logger.Error(fmt.Errorf("too many Velero backups for BackupJob"), "found more than one Velero Backup referencing a single BackupJob as owner") + j.Status.Phase = backupsv1alpha1.BackupJobPhaseFailed + if err := r.Status().Update(ctx, j); err != nil { + logger.Error(err, "failed to update BackupJob status") + } + return ctrl.Result{}, nil + } + + veleroBackup := veleroBackupList.Items[0].DeepCopy() + logger.Debug("found existing Velero Backup", "phase", veleroBackup.Status.Phase) + + // If Velero backup exists but phase is not Running, set it to Running + // This handles the case where the backup was created but phase wasn't set yet + if j.Status.Phase != backupsv1alpha1.BackupJobPhaseRunning { + logger.Debug("setting BackupJob phase to Running (Velero backup already exists)") + j.Status.Phase = backupsv1alpha1.BackupJobPhaseRunning + if err := r.Status().Update(ctx, j); err != nil { + logger.Error(err, "failed to update BackupJob phase to Running") + return ctrl.Result{}, err + } + } + + // Check Velero Backup status + phase := string(veleroBackup.Status.Phase) + if phase == "" { + // Still in progress, requeue + return ctrl.Result{RequeueAfter: defaultActiveJobPollingInterval}, nil + } + + // Step 4: On success - Create Backup resource and update status + if phase == "Completed" { + // Check if we already created the Backup resource + if j.Status.BackupRef == nil { + backup, err := r.createBackupResource(ctx, j, veleroBackup) + if err != nil { + return r.markBackupJobFailed(ctx, j, fmt.Sprintf("failed to create Backup resource: %v", err)) + } + + now := metav1.Now() + j.Status.BackupRef = &corev1.LocalObjectReference{Name: backup.Name} + j.Status.CompletedAt = &now + j.Status.Phase = backupsv1alpha1.BackupJobPhaseSucceeded + if err := r.Status().Update(ctx, j); err != nil { + logger.Error(err, "failed to update BackupJob status") + return ctrl.Result{}, err + } + logger.Debug("BackupJob succeeded", "backup", backup.Name) + } + return ctrl.Result{}, nil + } + + // Step 5: On failure + if phase == "Failed" || phase == "PartiallyFailed" { + message := fmt.Sprintf("Velero Backup failed with phase: %s", phase) + if len(veleroBackup.Status.ValidationErrors) > 0 { + message = fmt.Sprintf("%s: %v", message, veleroBackup.Status.ValidationErrors) + } + return r.markBackupJobFailed(ctx, j, message) + } + + // Still in progress (InProgress, New, etc.) + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil +} + +// resolveBucketStorageRef discovers S3 credentials from a Bucket storageRef +// It follows this flow: +// 1. Get the Bucket resource (apps.cozystack.io/v1alpha1) +// 2. Find the BucketAccess that references this bucket +// 3. Get the secret from BucketAccess.spec.credentialsSecretName +// 4. Decode BucketInfo from secret.data.BucketInfo and extract S3 credentials +func (r *BackupJobReconciler) resolveBucketStorageRef(ctx context.Context, storageRef corev1.TypedLocalObjectReference, namespace string) (*S3Credentials, error) { + logger := getLogger(ctx) + + // Step 1: Get the Bucket resource + bucket := &unstructured.Unstructured{} + bucket.SetGroupVersionKind(schema.GroupVersionKind{ + Group: *storageRef.APIGroup, + Version: "v1alpha1", + Kind: storageRef.Kind, + }) + + if *storageRef.APIGroup != "apps.cozystack.io" { + return nil, fmt.Errorf("Unsupported storage APIGroup: %v, expected apps.cozystack.io", storageRef.APIGroup) + } + bucketKey := client.ObjectKey{Namespace: namespace, Name: storageRef.Name} + + if err := r.Get(ctx, bucketKey, bucket); err != nil { + return nil, fmt.Errorf("failed to get Bucket %s: %w", storageRef.Name, err) + } + + // Step 2: Determine the bucket claim name + // For apps.cozystack.io Bucket, the BucketClaim name is typically the same as the Bucket name + // or follows a pattern. Based on the templates, it's usually the Release.Name which equals the Bucket name + bucketName := storageRef.Name + + // Step 3: Get BucketAccess by name (assuming BucketAccess name matches bucketName) + bucketAccess := &unstructured.Unstructured{} + bucketAccess.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "objectstorage.k8s.io", + Version: "v1alpha1", + Kind: "BucketAccess", + }) + + bucketAccessKey := client.ObjectKey{Name: "bucket-" + bucketName, Namespace: namespace} + if err := r.Get(ctx, bucketAccessKey, bucketAccess); err != nil { + return nil, fmt.Errorf("failed to get BucketAccess %s in namespace %s: %w", bucketName, namespace, err) + } + + // Step 4: Get the secret name from BucketAccess + secretName, found, err := unstructured.NestedString(bucketAccess.Object, "spec", "credentialsSecretName") + if err != nil { + return nil, fmt.Errorf("failed to get credentialsSecretName from BucketAccess: %w", err) + } + if !found || secretName == "" { + return nil, fmt.Errorf("credentialsSecretName not found in BucketAccess %s", bucketAccessKey.Name) + } + + // Step 5: Get the secret + secret := &corev1.Secret{} + secretKey := client.ObjectKey{Namespace: namespace, Name: secretName} + if err := r.Get(ctx, secretKey, secret); err != nil { + return nil, fmt.Errorf("failed to get secret %s: %w", secretName, err) + } + + // Step 6: Decode BucketInfo from secret.data.BucketInfo + bucketInfoData, found := secret.Data["BucketInfo"] + if !found { + return nil, fmt.Errorf("BucketInfo key not found in secret %s", secretName) + } + + // Parse JSON value + var info bucketInfo + if err := json.Unmarshal(bucketInfoData, &info); err != nil { + return nil, fmt.Errorf("failed to unmarshal BucketInfo from secret %s: %w", secretName, err) + } + + // Step 7: Extract and return S3 credentials + creds := &S3Credentials{ + BucketName: info.Spec.BucketName, + Endpoint: info.Spec.SecretS3.Endpoint, + Region: info.Spec.SecretS3.Region, + AccessKeyID: info.Spec.SecretS3.AccessKeyID, + AccessSecretKey: info.Spec.SecretS3.AccessSecretKey, + } + + logger.Debug("resolved S3 credentials from Bucket storageRef", + "bucket", storageRef.Name, + "bucketName", creds.BucketName, + "endpoint", creds.Endpoint) + + return creds, nil +} + +// createS3CredsForVelero creates or updates a Kubernetes Secret containing +// Velero S3 credentials in the format expected by Velero's cloud-credentials plugin. +func (r *BackupJobReconciler) createS3CredsForVelero(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, creds *S3Credentials) error { + logger := getLogger(ctx) + secretName := storageS3SecretName(backupJob.Namespace, backupJob.Name) + secretNamespace := veleroNamespace + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: secretNamespace, + }, + Type: corev1.SecretTypeOpaque, + StringData: map[string]string{ + "cloud": fmt.Sprintf(`[default] +aws_access_key_id=%s +aws_secret_access_key=%s + +services = seaweed-s3 +[services seaweed-s3] +s3 = + endpoint_url = %s +`, creds.AccessKeyID, creds.AccessSecretKey, creds.Endpoint), + }, + } + + foundSecret := &corev1.Secret{} + secretKey := client.ObjectKey{Name: secretName, Namespace: secretNamespace} + err := r.Get(ctx, secretKey, foundSecret) + if err != nil && errors.IsNotFound(err) { + // Create the Secret + if err := r.Create(ctx, secret); err != nil { + r.Recorder.Event(backupJob, corev1.EventTypeWarning, "SecretCreationFailed", + fmt.Sprintf("Failed to create Velero credentials secret %s/%s: %v", secretNamespace, secretName, err)) + return fmt.Errorf("failed to create Velero credentials secret: %w", err) + } + logger.Debug("created Velero credentials secret", "secret", secretName) + r.Recorder.Event(backupJob, corev1.EventTypeNormal, "SecretCreated", + fmt.Sprintf("Created Velero credentials secret %s/%s", secretNamespace, secretName)) + } else if err == nil { + // Update if necessary - only update if the secret data has actually changed + // Compare the new secret data with existing secret data + existingData := foundSecret.Data + if existingData == nil { + existingData = make(map[string][]byte) + } + newData := make(map[string][]byte) + for k, v := range secret.StringData { + newData[k] = []byte(v) + } + + // Check if data has changed + dataChanged := false + if len(existingData) != len(newData) { + dataChanged = true + } else { + for k, newVal := range newData { + existingVal, exists := existingData[k] + if !exists || !reflect.DeepEqual(existingVal, newVal) { + dataChanged = true + break + } + } + } + + if dataChanged { + foundSecret.StringData = secret.StringData + foundSecret.Data = nil // Clear .Data so .StringData will be used + if err := r.Update(ctx, foundSecret); err != nil { + r.Recorder.Event(backupJob, corev1.EventTypeWarning, "SecretUpdateFailed", + fmt.Sprintf("Failed to update Velero credentials secret %s/%s: %v", secretNamespace, secretName, err)) + return fmt.Errorf("failed to update Velero credentials secret: %w", err) + } + logger.Debug("updated Velero credentials secret", "secret", secretName) + r.Recorder.Event(backupJob, corev1.EventTypeNormal, "SecretUpdated", + fmt.Sprintf("Updated Velero credentials secret %s/%s", secretNamespace, secretName)) + } else { + logger.Debug("Velero credentials secret data unchanged, skipping update", "secret", secretName) + } + } else if err != nil { + return fmt.Errorf("error checking for existing Velero credentials secret: %w", err) + } + + return nil +} + +// createBackupStorageLocation creates or updates a Velero BackupStorageLocation resource. +func (r *BackupJobReconciler) createBackupStorageLocation(ctx context.Context, bsl *velerov1.BackupStorageLocation) error { + logger := getLogger(ctx) + foundBSL := &velerov1.BackupStorageLocation{} + bslKey := client.ObjectKey{Name: bsl.Name, Namespace: bsl.Namespace} + + err := r.Get(ctx, bslKey, foundBSL) + if err != nil && errors.IsNotFound(err) { + // Create the BackupStorageLocation + if err := r.Create(ctx, bsl); err != nil { + return fmt.Errorf("failed to create BackupStorageLocation: %w", err) + } + logger.Debug("created BackupStorageLocation", "name", bsl.Name, "namespace", bsl.Namespace) + } else if err == nil { + // Update if necessary - use patch to avoid conflicts with Velero's status updates + // Only update if the spec has actually changed + if !reflect.DeepEqual(foundBSL.Spec, bsl.Spec) { + // Retry on conflict since Velero may be updating status concurrently + for i := 0; i < 3; i++ { + if err := r.Get(ctx, bslKey, foundBSL); err != nil { + return fmt.Errorf("failed to get BackupStorageLocation for update: %w", err) + } + foundBSL.Spec = bsl.Spec + if err := r.Update(ctx, foundBSL); err != nil { + if errors.IsConflict(err) && i < 2 { + logger.Debug("conflict updating BackupStorageLocation, retrying", "attempt", i+1) + time.Sleep(100 * time.Millisecond) + continue + } + return fmt.Errorf("failed to update BackupStorageLocation: %w", err) + } + logger.Debug("updated BackupStorageLocation", "name", bsl.Name, "namespace", bsl.Namespace) + return nil + } + } else { + logger.Debug("BackupStorageLocation spec unchanged, skipping update", "name", bsl.Name, "namespace", bsl.Namespace) + } + } else if err != nil { + return fmt.Errorf("error checking for existing BackupStorageLocation: %w", err) + } + + return nil +} + +// createVolumeSnapshotLocation creates or updates a Velero VolumeSnapshotLocation resource. +func (r *BackupJobReconciler) createVolumeSnapshotLocation(ctx context.Context, vsl *velerov1.VolumeSnapshotLocation) error { + logger := getLogger(ctx) + foundVSL := &velerov1.VolumeSnapshotLocation{} + vslKey := client.ObjectKey{Name: vsl.Name, Namespace: vsl.Namespace} + + err := r.Get(ctx, vslKey, foundVSL) + if err != nil && errors.IsNotFound(err) { + // Create the VolumeSnapshotLocation + if err := r.Create(ctx, vsl); err != nil { + return fmt.Errorf("failed to create VolumeSnapshotLocation: %w", err) + } + logger.Debug("created VolumeSnapshotLocation", "name", vsl.Name, "namespace", vsl.Namespace) + } else if err == nil { + // Update if necessary - only update if the spec has actually changed + if !reflect.DeepEqual(foundVSL.Spec, vsl.Spec) { + // Retry on conflict since Velero may be updating status concurrently + for i := 0; i < 3; i++ { + if err := r.Get(ctx, vslKey, foundVSL); err != nil { + return fmt.Errorf("failed to get VolumeSnapshotLocation for update: %w", err) + } + foundVSL.Spec = vsl.Spec + if err := r.Update(ctx, foundVSL); err != nil { + if errors.IsConflict(err) && i < 2 { + logger.Debug("conflict updating VolumeSnapshotLocation, retrying", "attempt", i+1) + time.Sleep(100 * time.Millisecond) + continue + } + return fmt.Errorf("failed to update VolumeSnapshotLocation: %w", err) + } + logger.Debug("updated VolumeSnapshotLocation", "name", vsl.Name, "namespace", vsl.Namespace) + return nil + } + } else { + logger.Debug("VolumeSnapshotLocation spec unchanged, skipping update", "name", vsl.Name, "namespace", vsl.Namespace) + } + } else if err != nil { + return fmt.Errorf("error checking for existing VolumeSnapshotLocation: %w", err) + } + + return nil +} + +func (r *BackupJobReconciler) markBackupJobFailed(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, message string) (ctrl.Result, error) { + logger := getLogger(ctx) + now := metav1.Now() + backupJob.Status.CompletedAt = &now + backupJob.Status.Phase = backupsv1alpha1.BackupJobPhaseFailed + backupJob.Status.Message = message + + // Add condition + backupJob.Status.Conditions = append(backupJob.Status.Conditions, metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionFalse, + Reason: "BackupFailed", + Message: message, + LastTransitionTime: now, + }) + + if err := r.Status().Update(ctx, backupJob); err != nil { + logger.Error(err, "failed to update BackupJob status to Failed") + return ctrl.Result{}, err + } + logger.Debug("BackupJob failed", "message", message) return ctrl.Result{}, nil } + +func (r *BackupJobReconciler) createVeleroBackup(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, strategy *strategyv1alpha1.Velero) error { + logger := getLogger(ctx) + logger.Debug("createVeleroBackup called", "strategy", strategy.Name) + + mapping, err := r.RESTMapping(schema.GroupKind{Group: *backupJob.Spec.ApplicationRef.APIGroup, Kind: backupJob.Spec.ApplicationRef.Kind}) + if err != nil { + return err + } + ns := backupJob.Namespace + if mapping.Scope.Name() != meta.RESTScopeNameNamespace { + ns = "" + } + app, err := r.Resource(mapping.Resource).Namespace(ns).Get(ctx, backupJob.Spec.ApplicationRef.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + veleroBackupSpec, err := template.Template(&strategy.Spec.Template.Spec, app.Object) + if err != nil { + return err + } + veleroBackup := &velerov1.Backup{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("%s.%s-", backupJob.Namespace, backupJob.Name), + Namespace: veleroNamespace, + Labels: map[string]string{ + backupsv1alpha1.OwningJobNameLabel: backupJob.Name, + backupsv1alpha1.OwningJobNamespaceLabel: backupJob.Namespace, + }, + }, + Spec: *veleroBackupSpec, + } + name := veleroBackup.GenerateName + if err := r.Create(ctx, veleroBackup); err != nil { + if veleroBackup.Name != "" { + name = veleroBackup.Name + } + logger.Error(err, "failed to create Velero Backup", "name", veleroBackup.Name) + r.Recorder.Event(backupJob, corev1.EventTypeWarning, "VeleroBackupCreationFailed", + fmt.Sprintf("Failed to create Velero Backup %s/%s: %v", veleroNamespace, name, err)) + return err + } + + logger.Debug("created Velero Backup", "name", veleroBackup.Name, "namespace", veleroBackup.Namespace) + r.Recorder.Event(backupJob, corev1.EventTypeNormal, "VeleroBackupCreated", + fmt.Sprintf("Created Velero Backup %s/%s", veleroNamespace, name)) + return nil +} + +func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, veleroBackup *velerov1.Backup) (*backupsv1alpha1.Backup, error) { + logger := getLogger(ctx) + // Extract artifact information from Velero Backup + // Create a basic artifact referencing the Velero backup + artifact := &backupsv1alpha1.BackupArtifact{ + URI: fmt.Sprintf("velero://%s/%s", backupJob.Namespace, veleroBackup.Name), + } + + // Get takenAt from Velero Backup creation timestamp or status + takenAt := metav1.Now() + if veleroBackup.Status.StartTimestamp != nil { + takenAt = *veleroBackup.Status.StartTimestamp + } else if !veleroBackup.CreationTimestamp.IsZero() { + takenAt = veleroBackup.CreationTimestamp + } + + // Extract driver metadata (e.g., Velero backup name) + driverMetadata := map[string]string{ + "velero.io/backup-name": veleroBackup.Name, + "velero.io/backup-namespace": veleroBackup.Namespace, + } + + backup := &backupsv1alpha1.Backup{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s", backupJob.Name), + Namespace: backupJob.Namespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: backupJob.APIVersion, + Kind: backupJob.Kind, + Name: backupJob.Name, + UID: backupJob.UID, + Controller: boolPtr(true), + }, + }, + }, + Spec: backupsv1alpha1.BackupSpec{ + ApplicationRef: backupJob.Spec.ApplicationRef, + StorageRef: backupJob.Spec.StorageRef, + StrategyRef: backupJob.Spec.StrategyRef, + TakenAt: takenAt, + DriverMetadata: driverMetadata, + }, + Status: backupsv1alpha1.BackupStatus{ + Phase: backupsv1alpha1.BackupPhaseReady, + }, + } + + if backupJob.Spec.PlanRef != nil { + backup.Spec.PlanRef = backupJob.Spec.PlanRef + } + + if artifact != nil { + backup.Status.Artifact = artifact + } + + if err := r.Create(ctx, backup); err != nil { + logger.Error(err, "failed to create Backup resource") + return nil, err + } + + logger.Debug("created Backup resource", "name", backup.Name) + return backup, nil +} diff --git a/internal/template/template.go b/internal/template/template.go new file mode 100644 index 00000000..c206bb3d --- /dev/null +++ b/internal/template/template.go @@ -0,0 +1,68 @@ +package template + +import ( + "bytes" + "encoding/json" + tmpl "text/template" +) + +func Template[T any](obj *T, templateContext map[string]any) (*T, error) { + b, err := json.Marshal(obj) + if err != nil { + return nil, err + } + var unstructured any + err = json.Unmarshal(b, &unstructured) + if err != nil { + return nil, err + } + templateFunc := func(in string) string { + out, err := template(in, templateContext) + if err != nil { + return in + } + return out + } + unstructured = mapAtStrings(unstructured, templateFunc) + b, err = json.Marshal(unstructured) + if err != nil { + return nil, err + } + var out T + err = json.Unmarshal(b, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func mapAtStrings(v any, f func(string) string) any { + switch x := v.(type) { + case map[string]any: + for k, val := range x { + x[k] = mapAtStrings(val, f) + } + return x + case []any: + for i, val := range x { + x[i] = mapAtStrings(val, f) + } + return x + case string: + return f(x) + default: + return v + } +} + +func template(in string, templateContext map[string]any) (string, error) { + tpl, err := tmpl.New("this").Parse(in) + if err != nil { + return "", err + } + var buf bytes.Buffer + if err := tpl.Execute(&buf, templateContext); err != nil { + return "", err + } + return buf.String(), nil +} diff --git a/internal/template/template_test.go b/internal/template/template_test.go new file mode 100644 index 00000000..94ea2210 --- /dev/null +++ b/internal/template/template_test.go @@ -0,0 +1,68 @@ +package template + +import ( + "encoding/json" + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestTemplate_PodTemplateSpec(t *testing.T) { + original := corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pod", + Labels: map[string]string{ + "app": "demo", + }, + Annotations: map[string]string{ + "note": "hello", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "{{ .Release.Name }}", + Image: "nginx:1.21", + Args: []string{"--flag={{ .Values.value }}"}, + Env: []corev1.EnvVar{ + { + Name: "FOO", + Value: "{{ .Release.Namespace }}", + }, + }, + }, + }, + }, + } + templateContext := map[string]any{ + "Release": map[string]any{ + "Name": "foo", + "Namespace": "notdefault", + }, + "Values": map[string]any{ + "value": 3, + }, + } + reference := *original.DeepCopy() + reference.Spec.Containers[0].Name = "foo" + reference.Spec.Containers[0].Args[0] = "--flag=3" + reference.Spec.Containers[0].Env[0].Value = "notdefault" + got, err := Template(&original, templateContext) + if err != nil { + t.Fatalf("Template returned error: %v", err) + } + b1, err := json.Marshal(reference) + t.Logf("reference:\n%s", string(b1)) + if err != nil { + t.Fatalf("failed to marshal reference value: %v", err) + } + b2, err := json.Marshal(got) + t.Logf("got:\n%s", string(b2)) + if err != nil { + t.Fatalf("failed to marshal transformed value: %v", err) + } + if string(b1) != string(b2) { + t.Fatalf("transformed value not equal to reference value, expected: %s, got: %s", string(b1), string(b2)) + } +} diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml index 367ca8e6..74349181 100644 --- a/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml @@ -14,7 +14,11 @@ spec: singular: backupjob scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Phase + type: string + name: v1alpha1 schema: openAPIV3Schema: description: |- @@ -233,3 +237,5 @@ spec: - jsonPath: .spec.applicationRef.name served: true storage: true + subresources: + status: {} diff --git a/packages/system/backup-controller/templates/rbac.yaml b/packages/system/backup-controller/templates/rbac.yaml index 71c4af69..255398e8 100644 --- a/packages/system/backup-controller/templates/rbac.yaml +++ b/packages/system/backup-controller/templates/rbac.yaml @@ -8,4 +8,25 @@ rules: verbs: ["get", "list", "watch"] - apiGroups: ["backups.cozystack.io"] resources: ["backupjobs"] + verbs: ["create", "get", "list", "watch", "update", "patch"] +- apiGroups: ["backups.cozystack.io"] + resources: ["backupjobs/status"] + verbs: ["get", "update", "patch"] +- apiGroups: ["backups.cozystack.io"] + resources: ["backups"] verbs: ["create", "get", "list", "watch"] +- apiGroups: ["apps.cozystack.io"] + resources: ["buckets", "bucketaccesses", "virtualmachines"] + verbs: ["get", "list", "watch"] +- apiGroups: ["objectstorage.k8s.io"] + resources: ["buckets", "bucketaccesses"] + verbs: ["get", "list", "watch"] +- apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get", "list", "watch", "update", "patch"] +- apiGroups: ["kubevirt.io"] + resources: ["virtualmachines"] + verbs: ["get", "list", "watch"] +- apiGroups: ["velero.io"] + resources: ["backups", "backupstoragelocations", "volumesnapshotlocations", "restores"] + verbs: ["create", "get", "list", "watch", "update", "patch"] diff --git a/packages/system/backup-controller/templates/strategy.yaml b/packages/system/backup-controller/templates/strategy.yaml new file mode 100644 index 00000000..68109332 --- /dev/null +++ b/packages/system/backup-controller/templates/strategy.yaml @@ -0,0 +1,5 @@ +apiVersion: strategy.backups.cozystack.io/v1alpha1 +kind: Velero +metadata: + name: velero-strategy-default +spec: {} diff --git a/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_jobs.yaml b/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_jobs.yaml index c40ef22c..b4fea209 100644 --- a/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_jobs.yaml +++ b/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_jobs.yaml @@ -353,7 +353,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -368,7 +367,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -536,7 +534,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -551,7 +548,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -645,8 +641,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: The weights of all of the matched WeightedPodAffinityTerm @@ -717,7 +713,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -732,7 +727,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -900,7 +894,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -915,7 +908,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1048,8 +1040,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -1108,6 +1101,43 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -1170,14 +1200,14 @@ spec: envFrom: description: |- List of sources to populate environment variables in the container. - The keys defined within a source must be a C_IDENTIFIER. All invalid keys - will be reported as an event when the container is starting. When a key exists in multiple + The keys defined within a source may consist of any printable ASCII characters except '='. + When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. items: description: EnvFromSource represents the source of - a set of ConfigMaps + a set of ConfigMaps or Secrets properties: configMapRef: description: The ConfigMap to select from @@ -1198,8 +1228,9 @@ spec: type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend - to each key in the ConfigMap. Must be a C_IDENTIFIER. + description: |- + Optional text to prepend to the name of each environment variable. + May consist of any printable ASCII characters except '='. type: string secretRef: description: The Secret to select from @@ -1250,7 +1281,8 @@ spec: More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -1265,7 +1297,7 @@ spec: x-kubernetes-list-type: atomic type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -1316,8 +1348,8 @@ spec: - port type: object sleep: - description: Sleep represents the duration that - the container should sleep before being terminated. + description: Sleep represents a duration that + the container should sleep. properties: seconds: description: Seconds is the number of seconds @@ -1330,8 +1362,8 @@ spec: tcpSocket: description: |- Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept - for the backward compatibility. There are no validation of this field and - lifecycle hooks will fail in runtime when tcp handler is specified. + for backward compatibility. There is no validation of this field and + lifecycle hooks will fail at runtime when it is specified. properties: host: description: 'Optional: Host name to connect @@ -1363,7 +1395,8 @@ spec: More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -1378,7 +1411,7 @@ spec: x-kubernetes-list-type: atomic type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -1429,8 +1462,8 @@ spec: - port type: object sleep: - description: Sleep represents the duration that - the container should sleep before being terminated. + description: Sleep represents a duration that + the container should sleep. properties: seconds: description: Seconds is the number of seconds @@ -1443,8 +1476,8 @@ spec: tcpSocket: description: |- Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept - for the backward compatibility. There are no validation of this field and - lifecycle hooks will fail in runtime when tcp handler is specified. + for backward compatibility. There is no validation of this field and + lifecycle hooks will fail at runtime when it is specified. properties: host: description: 'Optional: Host name to connect @@ -1463,6 +1496,12 @@ spec: - port type: object type: object + stopSignal: + description: |- + StopSignal defines which signal will be sent to a container when it is being stopped. + If not specified, the default is defined by the container runtime in use. + StopSignal can only be set for Pods with a non-empty .spec.os.name + type: string type: object livenessProbe: description: |- @@ -1472,7 +1511,8 @@ spec: More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -1493,8 +1533,7 @@ spec: format: int32 type: integer grpc: - description: GRPC specifies an action involving - a GRPC port. + description: GRPC specifies a GRPC HealthCheckRequest. properties: port: description: Port number of the gRPC service. @@ -1513,7 +1552,7 @@ spec: - port type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -1581,7 +1620,7 @@ spec: format: int32 type: integer tcpSocket: - description: TCPSocket specifies an action involving + description: TCPSocket specifies a connection to a TCP port. properties: host: @@ -1687,7 +1726,8 @@ spec: More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -1708,8 +1748,7 @@ spec: format: int32 type: integer grpc: - description: GRPC specifies an action involving - a GRPC port. + description: GRPC specifies a GRPC HealthCheckRequest. properties: port: description: Port number of the gRPC service. @@ -1728,7 +1767,7 @@ spec: - port type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -1796,7 +1835,7 @@ spec: format: int32 type: integer tcpSocket: - description: TCPSocket specifies an action involving + description: TCPSocket specifies a connection to a TCP port. properties: host: @@ -1870,7 +1909,7 @@ spec: Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. @@ -1925,10 +1964,10 @@ spec: restartPolicy: description: |- RestartPolicy defines the restart behavior of individual containers in a pod. - This field may only be set for init containers, and the only allowed value is "Always". - For non-init containers or when this field is not specified, + This overrides the pod-level restart policy. When this field is not specified, the restart behavior is defined by the Pod's restart policy and the container type. - Setting the RestartPolicy as "Always" for the init container will have the following effect: + Additionally, setting the RestartPolicy as "Always" for the init container will + have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy "Always" @@ -1940,6 +1979,59 @@ spec: init container is started, or after any startupProbe has successfully completed. type: string + restartPolicyRules: + description: |- + Represents a list of rules to be checked to determine if the + container should be restarted on exit. The rules are evaluated in + order. Once a rule matches a container exit condition, the remaining + rules are ignored. If no rule matches the container exit condition, + the Container-level restart policy determines the whether the container + is restarted or not. Constraints on the rules: + - At most 20 rules are allowed. + - Rules can have the same action. + - Identical rules are not forbidden in validations. + When rules are specified, container MUST set RestartPolicy explicitly + even it if matches the Pod's RestartPolicy. + items: + description: ContainerRestartRule describes how a + container exit is handled. + properties: + action: + description: |- + Specifies the action taken on a container exit if the requirements + are satisfied. The only possible value is "Restart" to restart the + container. + type: string + exitCodes: + description: Represents the exit codes to check + on container exits. + properties: + operator: + description: |- + Represents the relationship between the container exit code(s) and the + specified values. Possible values are: + - In: the requirement is satisfied if the container exit code is in the + set of specified values. + - NotIn: the requirement is satisfied if the container exit code is + not in the set of specified values. + type: string + values: + description: |- + Specifies the set of values to check for container exit codes. + At most 255 elements are allowed. + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic securityContext: description: |- SecurityContext defines the security options the container should be run with. @@ -2146,7 +2238,8 @@ spec: More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -2167,8 +2260,7 @@ spec: format: int32 type: integer grpc: - description: GRPC specifies an action involving - a GRPC port. + description: GRPC specifies a GRPC HealthCheckRequest. properties: port: description: Port number of the gRPC service. @@ -2187,7 +2279,7 @@ spec: - port type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -2255,7 +2347,7 @@ spec: format: int32 type: integer tcpSocket: - description: TCPSocket specifies an action involving + description: TCPSocket specifies a connection to a TCP port. properties: host: @@ -2470,9 +2562,13 @@ spec: options of a pod. properties: name: - description: Required. + description: |- + Name is this DNS resolver option's name. + Required. type: string value: + description: Value is this DNS resolver option's + value. type: string type: object type: array @@ -2556,8 +2652,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -2616,6 +2713,43 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -2678,14 +2812,14 @@ spec: envFrom: description: |- List of sources to populate environment variables in the container. - The keys defined within a source must be a C_IDENTIFIER. All invalid keys - will be reported as an event when the container is starting. When a key exists in multiple + The keys defined within a source may consist of any printable ASCII characters except '='. + When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. items: description: EnvFromSource represents the source of - a set of ConfigMaps + a set of ConfigMaps or Secrets properties: configMapRef: description: The ConfigMap to select from @@ -2706,8 +2840,9 @@ spec: type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend - to each key in the ConfigMap. Must be a C_IDENTIFIER. + description: |- + Optional text to prepend to the name of each environment variable. + May consist of any printable ASCII characters except '='. type: string secretRef: description: The Secret to select from @@ -2755,7 +2890,8 @@ spec: More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -2770,7 +2906,7 @@ spec: x-kubernetes-list-type: atomic type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -2821,8 +2957,8 @@ spec: - port type: object sleep: - description: Sleep represents the duration that - the container should sleep before being terminated. + description: Sleep represents a duration that + the container should sleep. properties: seconds: description: Seconds is the number of seconds @@ -2835,8 +2971,8 @@ spec: tcpSocket: description: |- Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept - for the backward compatibility. There are no validation of this field and - lifecycle hooks will fail in runtime when tcp handler is specified. + for backward compatibility. There is no validation of this field and + lifecycle hooks will fail at runtime when it is specified. properties: host: description: 'Optional: Host name to connect @@ -2868,7 +3004,8 @@ spec: More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -2883,7 +3020,7 @@ spec: x-kubernetes-list-type: atomic type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -2934,8 +3071,8 @@ spec: - port type: object sleep: - description: Sleep represents the duration that - the container should sleep before being terminated. + description: Sleep represents a duration that + the container should sleep. properties: seconds: description: Seconds is the number of seconds @@ -2948,8 +3085,8 @@ spec: tcpSocket: description: |- Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept - for the backward compatibility. There are no validation of this field and - lifecycle hooks will fail in runtime when tcp handler is specified. + for backward compatibility. There is no validation of this field and + lifecycle hooks will fail at runtime when it is specified. properties: host: description: 'Optional: Host name to connect @@ -2968,12 +3105,19 @@ spec: - port type: object type: object + stopSignal: + description: |- + StopSignal defines which signal will be sent to a container when it is being stopped. + If not specified, the default is defined by the container runtime in use. + StopSignal can only be set for Pods with a non-empty .spec.os.name + type: string type: object livenessProbe: description: Probes are not allowed for ephemeral containers. properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -2994,8 +3138,7 @@ spec: format: int32 type: integer grpc: - description: GRPC specifies an action involving - a GRPC port. + description: GRPC specifies a GRPC HealthCheckRequest. properties: port: description: Port number of the gRPC service. @@ -3014,7 +3157,7 @@ spec: - port type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -3082,7 +3225,7 @@ spec: format: int32 type: integer tcpSocket: - description: TCPSocket specifies an action involving + description: TCPSocket specifies a connection to a TCP port. properties: host: @@ -3176,7 +3319,8 @@ spec: description: Probes are not allowed for ephemeral containers. properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -3197,8 +3341,7 @@ spec: format: int32 type: integer grpc: - description: GRPC specifies an action involving - a GRPC port. + description: GRPC specifies a GRPC HealthCheckRequest. properties: port: description: Port number of the gRPC service. @@ -3217,7 +3360,7 @@ spec: - port type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -3285,7 +3428,7 @@ spec: format: int32 type: integer tcpSocket: - description: TCPSocket specifies an action involving + description: TCPSocket specifies a connection to a TCP port. properties: host: @@ -3358,7 +3501,7 @@ spec: Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. @@ -3414,9 +3557,53 @@ spec: description: |- Restart policy for the container to manage the restart behavior of each container within a pod. - This may only be set for init containers. You cannot set this field on - ephemeral containers. + You cannot set this field on ephemeral containers. type: string + restartPolicyRules: + description: |- + Represents a list of rules to be checked to determine if the + container should be restarted on exit. You cannot set this field on + ephemeral containers. + items: + description: ContainerRestartRule describes how a + container exit is handled. + properties: + action: + description: |- + Specifies the action taken on a container exit if the requirements + are satisfied. The only possible value is "Restart" to restart the + container. + type: string + exitCodes: + description: Represents the exit codes to check + on container exits. + properties: + operator: + description: |- + Represents the relationship between the container exit code(s) and the + specified values. Possible values are: + - In: the requirement is satisfied if the container exit code is in the + set of specified values. + - NotIn: the requirement is satisfied if the container exit code is + not in the set of specified values. + type: string + values: + description: |- + Specifies the set of values to check for container exit codes. + At most 255 elements are allowed. + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic securityContext: description: |- Optional: SecurityContext defines the security options the ephemeral container should be run with. @@ -3615,7 +3802,8 @@ spec: description: Probes are not allowed for ephemeral containers. properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -3636,8 +3824,7 @@ spec: format: int32 type: integer grpc: - description: GRPC specifies an action involving - a GRPC port. + description: GRPC specifies a GRPC HealthCheckRequest. properties: port: description: Port number of the gRPC service. @@ -3656,7 +3843,7 @@ spec: - port type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -3724,7 +3911,7 @@ spec: format: int32 type: integer tcpSocket: - description: TCPSocket specifies an action involving + description: TCPSocket specifies a connection to a TCP port. properties: host: @@ -3955,7 +4142,9 @@ spec: hostNetwork: description: |- Host networking requested for this pod. Use the host's network namespace. - If this option is set, the ports that will be used must be specified. + When using HostNetwork you should specify ports so the scheduler is aware. + When `hostNetwork` is true, specified `hostPort` fields in port definitions must match `containerPort`, + and unspecified `hostPort` fields in port definitions are defaulted to match `containerPort`. Default to false. type: boolean hostPID: @@ -3980,6 +4169,19 @@ spec: Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value. type: string + hostnameOverride: + description: |- + HostnameOverride specifies an explicit override for the pod's hostname as perceived by the pod. + This field only specifies the pod's hostname and does not affect its DNS records. + When this field is set to a non-empty string: + - It takes precedence over the values set in `hostname` and `subdomain`. + - The Pod's hostname will be set to this value. + - `setHostnameAsFQDN` must be nil or set to false. + - `hostNetwork` must be set to false. + + This field must be a valid DNS subdomain as defined in RFC 1123 and contain at most 64 characters. + Requires the HostnameOverride feature gate to be enabled. + type: string imagePullSecrets: description: |- ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. @@ -4015,7 +4217,7 @@ spec: Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of - of that value or the sum of the normal containers. Limits are applied to init containers + that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. @@ -4061,8 +4263,9 @@ spec: present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. + description: |- + Name of the environment variable. + May consist of any printable ASCII characters except '='. type: string value: description: |- @@ -4121,6 +4324,43 @@ spec: - fieldPath type: object x-kubernetes-map-type: atomic + fileKeyRef: + description: |- + FileKeyRef selects a key of the env file. + Requires the EnvFiles feature gate to be enabled. + properties: + key: + description: |- + The key within the env file. An invalid key will prevent the pod from starting. + The keys defined within a source may consist of any printable ASCII characters except '='. + During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters. + type: string + optional: + default: false + description: |- + Specify whether the file or its key must be defined. If the file or key + does not exist, then the env var is not published. + If optional is set to true and the specified key does not exist, + the environment variable will not be set in the Pod's containers. + + If optional is set to false and the specified key does not exist, + an error will be returned during Pod creation. + type: boolean + path: + description: |- + The path within the volume from which to select the file. + Must be relative and may not contain the '..' path or start with '..'. + type: string + volumeName: + description: The name of the volume mount + containing the env file. + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic resourceFieldRef: description: |- Selects a resource of the container: only resources limits and requests @@ -4183,14 +4423,14 @@ spec: envFrom: description: |- List of sources to populate environment variables in the container. - The keys defined within a source must be a C_IDENTIFIER. All invalid keys - will be reported as an event when the container is starting. When a key exists in multiple + The keys defined within a source may consist of any printable ASCII characters except '='. + When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. items: description: EnvFromSource represents the source of - a set of ConfigMaps + a set of ConfigMaps or Secrets properties: configMapRef: description: The ConfigMap to select from @@ -4211,8 +4451,9 @@ spec: type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend - to each key in the ConfigMap. Must be a C_IDENTIFIER. + description: |- + Optional text to prepend to the name of each environment variable. + May consist of any printable ASCII characters except '='. type: string secretRef: description: The Secret to select from @@ -4263,7 +4504,8 @@ spec: More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -4278,7 +4520,7 @@ spec: x-kubernetes-list-type: atomic type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -4329,8 +4571,8 @@ spec: - port type: object sleep: - description: Sleep represents the duration that - the container should sleep before being terminated. + description: Sleep represents a duration that + the container should sleep. properties: seconds: description: Seconds is the number of seconds @@ -4343,8 +4585,8 @@ spec: tcpSocket: description: |- Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept - for the backward compatibility. There are no validation of this field and - lifecycle hooks will fail in runtime when tcp handler is specified. + for backward compatibility. There is no validation of this field and + lifecycle hooks will fail at runtime when it is specified. properties: host: description: 'Optional: Host name to connect @@ -4376,7 +4618,8 @@ spec: More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -4391,7 +4634,7 @@ spec: x-kubernetes-list-type: atomic type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -4442,8 +4685,8 @@ spec: - port type: object sleep: - description: Sleep represents the duration that - the container should sleep before being terminated. + description: Sleep represents a duration that + the container should sleep. properties: seconds: description: Seconds is the number of seconds @@ -4456,8 +4699,8 @@ spec: tcpSocket: description: |- Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept - for the backward compatibility. There are no validation of this field and - lifecycle hooks will fail in runtime when tcp handler is specified. + for backward compatibility. There is no validation of this field and + lifecycle hooks will fail at runtime when it is specified. properties: host: description: 'Optional: Host name to connect @@ -4476,6 +4719,12 @@ spec: - port type: object type: object + stopSignal: + description: |- + StopSignal defines which signal will be sent to a container when it is being stopped. + If not specified, the default is defined by the container runtime in use. + StopSignal can only be set for Pods with a non-empty .spec.os.name + type: string type: object livenessProbe: description: |- @@ -4485,7 +4734,8 @@ spec: More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -4506,8 +4756,7 @@ spec: format: int32 type: integer grpc: - description: GRPC specifies an action involving - a GRPC port. + description: GRPC specifies a GRPC HealthCheckRequest. properties: port: description: Port number of the gRPC service. @@ -4526,7 +4775,7 @@ spec: - port type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -4594,7 +4843,7 @@ spec: format: int32 type: integer tcpSocket: - description: TCPSocket specifies an action involving + description: TCPSocket specifies a connection to a TCP port. properties: host: @@ -4700,7 +4949,8 @@ spec: More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -4721,8 +4971,7 @@ spec: format: int32 type: integer grpc: - description: GRPC specifies an action involving - a GRPC port. + description: GRPC specifies a GRPC HealthCheckRequest. properties: port: description: Port number of the gRPC service. @@ -4741,7 +4990,7 @@ spec: - port type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -4809,7 +5058,7 @@ spec: format: int32 type: integer tcpSocket: - description: TCPSocket specifies an action involving + description: TCPSocket specifies a connection to a TCP port. properties: host: @@ -4883,7 +5132,7 @@ spec: Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. @@ -4938,10 +5187,10 @@ spec: restartPolicy: description: |- RestartPolicy defines the restart behavior of individual containers in a pod. - This field may only be set for init containers, and the only allowed value is "Always". - For non-init containers or when this field is not specified, + This overrides the pod-level restart policy. When this field is not specified, the restart behavior is defined by the Pod's restart policy and the container type. - Setting the RestartPolicy as "Always" for the init container will have the following effect: + Additionally, setting the RestartPolicy as "Always" for the init container will + have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy "Always" @@ -4953,6 +5202,59 @@ spec: init container is started, or after any startupProbe has successfully completed. type: string + restartPolicyRules: + description: |- + Represents a list of rules to be checked to determine if the + container should be restarted on exit. The rules are evaluated in + order. Once a rule matches a container exit condition, the remaining + rules are ignored. If no rule matches the container exit condition, + the Container-level restart policy determines the whether the container + is restarted or not. Constraints on the rules: + - At most 20 rules are allowed. + - Rules can have the same action. + - Identical rules are not forbidden in validations. + When rules are specified, container MUST set RestartPolicy explicitly + even it if matches the Pod's RestartPolicy. + items: + description: ContainerRestartRule describes how a + container exit is handled. + properties: + action: + description: |- + Specifies the action taken on a container exit if the requirements + are satisfied. The only possible value is "Restart" to restart the + container. + type: string + exitCodes: + description: Represents the exit codes to check + on container exits. + properties: + operator: + description: |- + Represents the relationship between the container exit code(s) and the + specified values. Possible values are: + - In: the requirement is satisfied if the container exit code is in the + set of specified values. + - NotIn: the requirement is satisfied if the container exit code is + not in the set of specified values. + type: string + values: + description: |- + Specifies the set of values to check for container exit codes. + At most 255 elements are allowed. + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic securityContext: description: |- SecurityContext defines the security options the container should be run with. @@ -5159,7 +5461,8 @@ spec: More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes properties: exec: - description: Exec specifies the action to take. + description: Exec specifies a command to execute + in the container. properties: command: description: |- @@ -5180,8 +5483,7 @@ spec: format: int32 type: integer grpc: - description: GRPC specifies an action involving - a GRPC port. + description: GRPC specifies a GRPC HealthCheckRequest. properties: port: description: Port number of the gRPC service. @@ -5200,7 +5502,7 @@ spec: - port type: object httpGet: - description: HTTPGet specifies the http request + description: HTTPGet specifies an HTTP GET request to perform. properties: host: @@ -5268,7 +5570,7 @@ spec: format: int32 type: integer tcpSocket: - description: TCPSocket specifies an action involving + description: TCPSocket specifies a connection to a TCP port. properties: host: @@ -5486,6 +5788,7 @@ spec: - spec.hostPID - spec.hostIPC - spec.hostUsers + - spec.resources - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile @@ -5635,6 +5938,74 @@ spec: x-kubernetes-list-map-keys: - name x-kubernetes-list-type: map + resources: + description: |- + Resources is the total amount of CPU and Memory resources required by all + containers in the pod. It supports specifying Requests and Limits for + "cpu", "memory" and "hugepages-" resource names only. ResourceClaims are not supported. + + This field enables fine-grained control over resource allocation for the + entire pod, allowing resource sharing among containers in a pod. + + This is an alpha field and requires enabling the PodLevelResources feature + gate. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This field depends on the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object restartPolicy: description: |- Restart policy for all containers within the pod. @@ -5759,6 +6130,32 @@ spec: Note that this field cannot be set when spec.os.name is windows. format: int64 type: integer + seLinuxChangePolicy: + description: |- + seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod. + It has no effect on nodes that do not support SELinux or to volumes does not support SELinux. + Valid values are "MountOption" and "Recursive". + + "Recursive" means relabeling of all files on all Pod volumes by the container runtime. + This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node. + + "MountOption" mounts all eligible Pod volumes with `-o context` mount option. + This requires all Pods that share the same volume to use the same SELinux label. + It is not possible to share the same volume among privileged and unprivileged Pods. + Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes + whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their + CSIDriver instance. Other volumes are always re-labelled recursively. + "MountOption" value is allowed only when SELinuxMount feature gate is enabled. + + If not specified and SELinuxMount feature gate is enabled, "MountOption" is used. + If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes + and "Recursive" for all other volumes. + + This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers. + + All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state. + Note that this field cannot be set when spec.os.name is windows. + type: string seLinuxOptions: description: |- The SELinux context to be applied to all containers. @@ -6096,7 +6493,6 @@ spec: - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -6107,7 +6503,6 @@ spec: - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -6165,6 +6560,8 @@ spec: description: |- awsElasticBlockStore represents an AWS Disk resource that is attached to a kubelet's host machine and then exposed to the pod. + Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree + awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore properties: fsType: @@ -6196,8 +6593,10 @@ spec: - volumeID type: object azureDisk: - description: azureDisk represents an Azure Data Disk - mount on the host and bind mount to the pod. + description: |- + azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type + are redirected to the disk.csi.azure.com CSI driver. properties: cachingMode: description: 'cachingMode is the Host Caching mode: @@ -6236,8 +6635,10 @@ spec: - diskURI type: object azureFile: - description: azureFile represents an Azure File Service - mount on the host and bind mount to the pod. + description: |- + azureFile represents an Azure File Service mount on the host and bind mount to the pod. + Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type + are redirected to the file.csi.azure.com CSI driver. properties: readOnly: description: |- @@ -6256,8 +6657,9 @@ spec: - shareName type: object cephfs: - description: cephFS represents a Ceph FS mount on the - host that shares a pod's lifetime + description: |- + cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. + Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported. properties: monitors: description: |- @@ -6310,6 +6712,8 @@ spec: cinder: description: |- cinder represents a cinder volume attached and mounted on kubelets host machine. + Deprecated: Cinder is deprecated. All operations for the in-tree cinder type + are redirected to the cinder.csi.openstack.org CSI driver. More info: https://examples.k8s.io/mysql-cinder-pd/README.md properties: fsType: @@ -6421,7 +6825,7 @@ spec: csi: description: csi (Container Storage Interface) represents ephemeral storage that is handled by certain external - CSI drivers (Beta feature). + CSI drivers. properties: driver: description: |- @@ -6828,15 +7232,13 @@ spec: volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. If specified, the CSI driver will create or update the volume with the attributes defined in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, - it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass - will be applied to the claim but it's not allowed to reset this field to empty string once it is set. - If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass - will be set by the persistentvolume controller if it exists. + it can be changed after the claim is created. An empty string or nil value indicates that no + VolumeAttributesClass will be applied to the claim. If the claim enters an Infeasible error state, + this field can be reset to its previous value (including nil) to cancel the modification. If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource exists. More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ - (Beta) Using this field requires the VolumeAttributesClass feature gate to be enabled (off by default). type: string volumeMode: description: |- @@ -6892,6 +7294,7 @@ spec: description: |- flexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. + Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead. properties: driver: description: driver is the name of the driver to @@ -6937,9 +7340,9 @@ spec: - driver type: object flocker: - description: flocker represents a Flocker volume attached - to a kubelet's host machine. This depends on the Flocker - control service being running + description: |- + flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. + Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported. properties: datasetName: description: |- @@ -6955,6 +7358,8 @@ spec: description: |- gcePersistentDisk represents a GCE Disk resource that is attached to a kubelet's host machine and then exposed to the pod. + Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree + gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk properties: fsType: @@ -6990,7 +7395,7 @@ spec: gitRepo: description: |- gitRepo represents a git repository at a particular revision. - DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an + Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container. properties: @@ -7014,12 +7419,11 @@ spec: glusterfs: description: |- glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. - More info: https://examples.k8s.io/volumes/glusterfs/README.md + Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. properties: endpoints: - description: |- - endpoints is the endpoint name that details Glusterfs topology. - More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + description: endpoints is the endpoint name that + details Glusterfs topology. type: string path: description: |- @@ -7073,7 +7477,7 @@ spec: The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). - Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath). + Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. properties: pullPolicy: @@ -7098,7 +7502,7 @@ spec: description: |- iscsi represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. - More info: https://examples.k8s.io/volumes/iscsi/README.md + More info: https://kubernetes.io/docs/concepts/storage/volumes/#iscsi properties: chapAuthDiscovery: description: chapAuthDiscovery defines whether support @@ -7223,9 +7627,9 @@ spec: - claimName type: object photonPersistentDisk: - description: photonPersistentDisk represents a PhotonController - persistent disk attached and mounted on kubelets host - machine + description: |- + photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. + Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported. properties: fsType: description: |- @@ -7241,8 +7645,11 @@ spec: - pdID type: object portworxVolume: - description: portworxVolume represents a portworx volume - attached and mounted on kubelets host machine + description: |- + portworxVolume represents a portworx volume attached and mounted on kubelets host machine. + Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type + are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate + is on. properties: fsType: description: |- @@ -7521,6 +7928,111 @@ spec: type: array x-kubernetes-list-type: atomic type: object + podCertificate: + description: |- + Projects an auto-rotating credential bundle (private key and certificate + chain) that the pod can use either as a TLS client or server. + + Kubelet generates a private key and uses it to send a + PodCertificateRequest to the named signer. Once the signer approves the + request and issues a certificate chain, Kubelet writes the key and + certificate chain to the pod filesystem. The pod does not start until + certificates have been issued for each podCertificate projected volume + source in its spec. + + Kubelet will begin trying to rotate the certificate at the time indicated + by the signer using the PodCertificateRequest.Status.BeginRefreshAt + timestamp. + + Kubelet can write a single file, indicated by the credentialBundlePath + field, or separate files, indicated by the keyPath and + certificateChainPath fields. + + The credential bundle is a single file in PEM format. The first PEM + entry is the private key (in PKCS#8 format), and the remaining PEM + entries are the certificate chain issued by the signer (typically, + signers will return their certificate chain in leaf-to-root order). + + Prefer using the credential bundle format, since your application code + can read it atomically. If you use keyPath and certificateChainPath, + your application must make two separate file reads. If these coincide + with a certificate rotation, it is possible that the private key and leaf + certificate you read may not correspond to each other. Your application + will need to check for this condition, and re-read until they are + consistent. + + The named signer controls chooses the format of the certificate it + issues; consult the signer implementation's documentation to learn how to + use the certificates it issues. + properties: + certificateChainPath: + description: |- + Write the certificate chain at this path in the projected volume. + + Most applications should use credentialBundlePath. When using keyPath + and certificateChainPath, your application needs to check that the key + and leaf certificate are consistent, because it is possible to read the + files mid-rotation. + type: string + credentialBundlePath: + description: |- + Write the credential bundle at this path in the projected volume. + + The credential bundle is a single file that contains multiple PEM blocks. + The first PEM block is a PRIVATE KEY block, containing a PKCS#8 private + key. + + The remaining blocks are CERTIFICATE blocks, containing the issued + certificate chain from the signer (leaf and any intermediates). + + Using credentialBundlePath lets your Pod's application code make a single + atomic read that retrieves a consistent key and certificate chain. If you + project them to separate files, your application code will need to + additionally check that the leaf certificate was issued to the key. + type: string + keyPath: + description: |- + Write the key at this path in the projected volume. + + Most applications should use credentialBundlePath. When using keyPath + and certificateChainPath, your application needs to check that the key + and leaf certificate are consistent, because it is possible to read the + files mid-rotation. + type: string + keyType: + description: |- + The type of keypair Kubelet will generate for the pod. + + Valid values are "RSA3072", "RSA4096", "ECDSAP256", "ECDSAP384", + "ECDSAP521", and "ED25519". + type: string + maxExpirationSeconds: + description: |- + maxExpirationSeconds is the maximum lifetime permitted for the + certificate. + + Kubelet copies this value verbatim into the PodCertificateRequests it + generates for this projection. + + If omitted, kube-apiserver will set it to 86400(24 hours). kube-apiserver + will reject values shorter than 3600 (1 hour). The maximum allowable + value is 7862400 (91 days). + + The signer implementation is then free to issue a certificate with any + lifetime *shorter* than MaxExpirationSeconds, but no shorter than 3600 + seconds (1 hour). This constraint is enforced by kube-apiserver. + `kubernetes.io` signers will never issue certificates with a lifetime + longer than 24 hours. + format: int32 + type: integer + signerName: + description: Kubelet's generated CSRs + will be addressed to this signer. + type: string + required: + - keyType + - signerName + type: object secret: description: secret information about the secret data to project @@ -7613,8 +8125,9 @@ spec: x-kubernetes-list-type: atomic type: object quobyte: - description: quobyte represents a Quobyte mount on the - host that shares a pod's lifetime + description: |- + quobyte represents a Quobyte mount on the host that shares a pod's lifetime. + Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported. properties: group: description: |- @@ -7653,7 +8166,7 @@ spec: rbd: description: |- rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. - More info: https://examples.k8s.io/volumes/rbd/README.md + Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. properties: fsType: description: |- @@ -7725,8 +8238,9 @@ spec: - monitors type: object scaleIO: - description: scaleIO represents a ScaleIO persistent - volume attached and mounted on Kubernetes nodes. + description: |- + scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. properties: fsType: default: xfs @@ -7859,8 +8373,9 @@ spec: type: string type: object storageos: - description: storageOS represents a StorageOS volume - attached and mounted on Kubernetes nodes. + description: |- + storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. properties: fsType: description: |- @@ -7905,8 +8420,10 @@ spec: type: string type: object vsphereVolume: - description: vsphereVolume represents a vSphere volume - attached and mounted on kubelets host machine + description: |- + vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. + Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type + are redirected to the csi.vsphere.vmware.com CSI driver. properties: fsType: description: |- From 5b613836eea0100c1a9925e301291cd37c6c550b Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 6 Jan 2026 10:57:08 +0400 Subject: [PATCH 44/45] [backups] add restore jobs controller Signed-off-by: Andrey Kolkov (cherry picked from commit 5e15a75d890454d7fedc53722a2bf630196415ad) Signed-off-by: Timofei Larkin --- api/backups/strategy/v1alpha1/velero_types.go | 3 +- api/backups/v1alpha1/restorejob_types.go | 2 + cmd/backup-controller/main.go | 13 -- cmd/backupstrategy-controller/main.go | 20 +- .../backupcontroller/backupjob_controller.go | 40 +++- .../jobstrategy_controller.go | 5 + .../backupcontroller/restorejob_controller.go | 139 ++++++++++++ .../velerostrategy_controller.go | 206 +++++++++++++++--- 8 files changed, 369 insertions(+), 59 deletions(-) create mode 100644 internal/backupcontroller/restorejob_controller.go diff --git a/api/backups/strategy/v1alpha1/velero_types.go b/api/backups/strategy/v1alpha1/velero_types.go index 9461c753..ae15fa5e 100644 --- a/api/backups/strategy/v1alpha1/velero_types.go +++ b/api/backups/strategy/v1alpha1/velero_types.go @@ -55,7 +55,8 @@ type VeleroSpec struct { // VeleroTemplate describes the data a backup.velero.io should have when // templated from a Velero backup strategy. type VeleroTemplate struct { - Spec velerov1.BackupSpec `json:"spec"` + Spec velerov1.BackupSpec `json:"spec"` + RestoreSpec velerov1.RestoreSpec `json:"restoreSpec"` } type VeleroStatus struct { diff --git a/api/backups/v1alpha1/restorejob_types.go b/api/backups/v1alpha1/restorejob_types.go index 0a64b7cf..1a2dfa00 100644 --- a/api/backups/v1alpha1/restorejob_types.go +++ b/api/backups/v1alpha1/restorejob_types.go @@ -71,6 +71,8 @@ type RestoreJobStatus struct { } // +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",priority=0 // RestoreJob represents a single execution of a restore from a Backup. type RestoreJob struct { diff --git a/cmd/backup-controller/main.go b/cmd/backup-controller/main.go index a99f9350..d8436659 100644 --- a/cmd/backup-controller/main.go +++ b/cmd/backup-controller/main.go @@ -35,10 +35,8 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" - strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" "github.com/cozystack/cozystack/internal/backupcontroller" - velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" // +kubebuilder:scaffold:imports ) @@ -51,8 +49,6 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(backupsv1alpha1.AddToScheme(scheme)) - utilruntime.Must(strategyv1alpha1.AddToScheme(scheme)) - utilruntime.Must(velerov1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -159,15 +155,6 @@ func main() { os.Exit(1) } - if err = (&backupcontroller.BackupJobReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Recorder: mgr.GetEventRecorderFor("backup-controller"), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "BackupJob") - os.Exit(1) - } - // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/cmd/backupstrategy-controller/main.go b/cmd/backupstrategy-controller/main.go index 61edc40a..b9968826 100644 --- a/cmd/backupstrategy-controller/main.go +++ b/cmd/backupstrategy-controller/main.go @@ -35,8 +35,10 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" "github.com/cozystack/cozystack/internal/backupcontroller" + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" // +kubebuilder:scaffold:imports ) @@ -49,6 +51,8 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(backupsv1alpha1.AddToScheme(scheme)) + utilruntime.Must(strategyv1alpha1.AddToScheme(scheme)) + utilruntime.Must(velerov1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -148,10 +152,20 @@ func main() { } if err = (&backupcontroller.BackupJobReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("backup-controller"), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Job") + setupLog.Error(err, "unable to create controller", "controller", "BackupJob") + os.Exit(1) + } + + if err = (&backupcontroller.RestoreJobReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("restore-controller"), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "RestoreJob") os.Exit(1) } diff --git a/internal/backupcontroller/backupjob_controller.go b/internal/backupcontroller/backupjob_controller.go index 0db2e1dd..7f8ce026 100644 --- a/internal/backupcontroller/backupjob_controller.go +++ b/internal/backupcontroller/backupjob_controller.go @@ -2,10 +2,12 @@ package backupcontroller import ( "context" + "fmt" "net/http" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/dynamic" @@ -46,15 +48,11 @@ func (r *BackupJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } if j.Spec.StrategyRef.APIGroup == nil { - logger.V(1).Info("BackupJob has nil StrategyRef.APIGroup, skipping", "backupjob", j.Name) - return ctrl.Result{}, nil + return r.markBackupJobFailed(ctx, j, "StrategyRef.APIGroup is nil") } if *j.Spec.StrategyRef.APIGroup != strategyv1alpha1.GroupVersion.Group { - logger.V(1).Info("BackupJob StrategyRef.APIGroup doesn't match, skipping", - "backupjob", j.Name, - "expected", strategyv1alpha1.GroupVersion.Group, - "got", *j.Spec.StrategyRef.APIGroup) + // skip if the strategy group foreign to this controller return ctrl.Result{}, nil } @@ -65,11 +63,7 @@ func (r *BackupJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( case strategyv1alpha1.VeleroStrategyKind: return r.reconcileVelero(ctx, j) default: - logger.V(1).Info("BackupJob StrategyRef.Kind not supported, skipping", - "backupjob", j.Name, - "kind", j.Spec.StrategyRef.Kind, - "supported", []string{strategyv1alpha1.JobStrategyKind, strategyv1alpha1.VeleroStrategyKind}) - return ctrl.Result{}, nil + return r.markBackupJobFailed(ctx, j, fmt.Sprintf("StrategyRef.Kind not supported: %s", j.Spec.StrategyRef.Kind)) } } @@ -91,3 +85,27 @@ func (r *BackupJobReconciler) SetupWithManager(mgr ctrl.Manager) error { For(&backupsv1alpha1.BackupJob{}). Complete(r) } + +func (r *BackupJobReconciler) markBackupJobFailed(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, message string) (ctrl.Result, error) { + logger := getLogger(ctx) + now := metav1.Now() + backupJob.Status.CompletedAt = &now + backupJob.Status.Phase = backupsv1alpha1.BackupJobPhaseFailed + backupJob.Status.Message = message + + // Add condition + backupJob.Status.Conditions = append(backupJob.Status.Conditions, metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionFalse, + Reason: "BackupFailed", + Message: message, + LastTransitionTime: now, + }) + + if err := r.Status().Update(ctx, backupJob); err != nil { + logger.Error(err, "failed to update BackupJob status to Failed") + return ctrl.Result{}, err + } + logger.Debug("BackupJob failed", "message", message) + return ctrl.Result{}, nil +} diff --git a/internal/backupcontroller/jobstrategy_controller.go b/internal/backupcontroller/jobstrategy_controller.go index e52e38f2..3fac36bd 100644 --- a/internal/backupcontroller/jobstrategy_controller.go +++ b/internal/backupcontroller/jobstrategy_controller.go @@ -13,3 +13,8 @@ func (r *BackupJobReconciler) reconcileJob(ctx context.Context, j *backupsv1alph _ = log.FromContext(ctx) return ctrl.Result{}, nil } + +func (r *RestoreJobReconciler) reconcileJobRestore(ctx context.Context, restoreJob *backupsv1alpha1.RestoreJob, backup *backupsv1alpha1.Backup) (ctrl.Result, error) { + _ = log.FromContext(ctx) + return ctrl.Result{}, nil +} diff --git a/internal/backupcontroller/restorejob_controller.go b/internal/backupcontroller/restorejob_controller.go new file mode 100644 index 00000000..19941959 --- /dev/null +++ b/internal/backupcontroller/restorejob_controller.go @@ -0,0 +1,139 @@ +package backupcontroller + +import ( + "context" + "fmt" + "net/http" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/log" + + strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" +) + +// RestoreJobReconciler reconciles RestoreJob objects. +// It routes RestoreJobs to strategy-specific handlers based on the strategy +// referenced in the Backup that the RestoreJob is restoring from. +type RestoreJobReconciler struct { + client.Client + dynamic.Interface + meta.RESTMapper + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +func (r *RestoreJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx) + logger.Info("reconciling RestoreJob", "namespace", req.Namespace, "name", req.Name) + + restoreJob := &backupsv1alpha1.RestoreJob{} + err := r.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: req.Name}, restoreJob) + if err != nil { + if apierrors.IsNotFound(err) { + logger.V(1).Info("RestoreJob not found, skipping") + return ctrl.Result{}, nil + } + logger.Error(err, "failed to get RestoreJob") + return ctrl.Result{}, err + } + + // If already completed, no need to reconcile + if restoreJob.Status.Phase == backupsv1alpha1.RestoreJobPhaseSucceeded || + restoreJob.Status.Phase == backupsv1alpha1.RestoreJobPhaseFailed { + logger.V(1).Info("RestoreJob already completed, skipping", "phase", restoreJob.Status.Phase) + return ctrl.Result{}, nil + } + + // Step 1: Fetch the referenced Backup + backup := &backupsv1alpha1.Backup{} + backupKey := types.NamespacedName{Namespace: req.Namespace, Name: restoreJob.Spec.BackupRef.Name} + if err := r.Get(ctx, backupKey, backup); err != nil { + return r.markRestoreJobFailed(ctx, restoreJob, fmt.Sprintf("failed to get Backup: %v", err)) + } + + // Step 2: Determine effective strategy from backup.spec.strategyRef + if backup.Spec.StrategyRef.APIGroup == nil { + return r.markRestoreJobFailed(ctx, restoreJob, "Backup has nil StrategyRef.APIGroup") + } + + if *backup.Spec.StrategyRef.APIGroup != strategyv1alpha1.GroupVersion.Group { + return r.markRestoreJobFailed(ctx, restoreJob, + fmt.Sprintf("StrategyRef.APIGroup doesn't match: %s", *backup.Spec.StrategyRef.APIGroup)) + } + + logger.Info("processing RestoreJob", "restorejob", restoreJob.Name, "backup", backup.Name, "strategyKind", backup.Spec.StrategyRef.Kind) + switch backup.Spec.StrategyRef.Kind { + case strategyv1alpha1.JobStrategyKind: + return r.reconcileJobRestore(ctx, restoreJob, backup) + case strategyv1alpha1.VeleroStrategyKind: + return r.reconcileVeleroRestore(ctx, restoreJob, backup) + default: + return r.markRestoreJobFailed(ctx, restoreJob, fmt.Sprintf("StrategyRef.Kind not supported: %s", backup.Spec.StrategyRef.Kind)) + } +} + +// SetupWithManager registers our controller with the Manager and sets up watches. +func (r *RestoreJobReconciler) SetupWithManager(mgr ctrl.Manager) error { + cfg := mgr.GetConfig() + var err error + if r.Interface, err = dynamic.NewForConfig(cfg); err != nil { + return err + } + var h *http.Client + if h, err = rest.HTTPClientFor(cfg); err != nil { + return err + } + if r.RESTMapper, err = apiutil.NewDynamicRESTMapper(cfg, h); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). + For(&backupsv1alpha1.RestoreJob{}). + Complete(r) +} + +// getTargetApplicationRef determines the effective target application reference. +// According to DESIGN.md, if spec.targetApplicationRef is omitted, drivers SHOULD +// restore into backup.spec.applicationRef. +func (r *RestoreJobReconciler) getTargetApplicationRef(restoreJob *backupsv1alpha1.RestoreJob, backup *backupsv1alpha1.Backup) corev1.TypedLocalObjectReference { + if restoreJob.Spec.TargetApplicationRef != nil { + return *restoreJob.Spec.TargetApplicationRef + } + return backup.Spec.ApplicationRef +} + +// markRestoreJobFailed updates the RestoreJob status to Failed with the given message. +func (r *RestoreJobReconciler) markRestoreJobFailed(ctx context.Context, restoreJob *backupsv1alpha1.RestoreJob, message string) (ctrl.Result, error) { + logger := getLogger(ctx) + now := metav1.Now() + restoreJob.Status.CompletedAt = &now + restoreJob.Status.Phase = backupsv1alpha1.RestoreJobPhaseFailed + restoreJob.Status.Message = message + + // Add condition + restoreJob.Status.Conditions = append(restoreJob.Status.Conditions, metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionFalse, + Reason: "RestoreFailed", + Message: message, + LastTransitionTime: now, + }) + + if err := r.Status().Update(ctx, restoreJob); err != nil { + logger.Error(err, "failed to update RestoreJob status to Failed") + return ctrl.Result{}, err + } + logger.Debug("RestoreJob failed", "message", message) + return ctrl.Result{}, nil +} diff --git a/internal/backupcontroller/velerostrategy_controller.go b/internal/backupcontroller/velerostrategy_controller.go index 066325c0..fe2e0f18 100644 --- a/internal/backupcontroller/velerostrategy_controller.go +++ b/internal/backupcontroller/velerostrategy_controller.go @@ -63,11 +63,14 @@ type bucketInfo struct { } const ( - defaultRequeueAfter = 5 * time.Second - defaultActiveJobPollingInterval = defaultRequeueAfter + defaultRequeueAfter = 5 * time.Second + defaultActiveJobPollingInterval = defaultRequeueAfter + defaultRestoreRequeueAfter = 5 * time.Second + defaultActiveRestorePollingInterval = defaultRestoreRequeueAfter // Velero requires API objects and secrets to be in the cozy-velero namespace - veleroNamespace = "cozy-velero" - virtualMachinePrefix = "virtual-machine-" + veleroNamespace = "cozy-velero" + veleroBackupNameMetadataKey = "velero.io/backup-name" + veleroBackupNamespaceMetadataKey = "velero.io/backup-namespace" ) func storageS3SecretName(namespace, backupJobName string) string { @@ -120,7 +123,6 @@ func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1a // Step 3: Execute backup logic // Check if we already created a Velero Backup - // Use human-readable timestamp: YYYY-MM-DD-HH-MM-SS if j.Status.StartedAt == nil { logger.Error(nil, "StartedAt is nil after status update, this should not happen") return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil @@ -488,30 +490,6 @@ func (r *BackupJobReconciler) createVolumeSnapshotLocation(ctx context.Context, return nil } -func (r *BackupJobReconciler) markBackupJobFailed(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, message string) (ctrl.Result, error) { - logger := getLogger(ctx) - now := metav1.Now() - backupJob.Status.CompletedAt = &now - backupJob.Status.Phase = backupsv1alpha1.BackupJobPhaseFailed - backupJob.Status.Message = message - - // Add condition - backupJob.Status.Conditions = append(backupJob.Status.Conditions, metav1.Condition{ - Type: "Ready", - Status: metav1.ConditionFalse, - Reason: "BackupFailed", - Message: message, - LastTransitionTime: now, - }) - - if err := r.Status().Update(ctx, backupJob); err != nil { - logger.Error(err, "failed to update BackupJob status to Failed") - return ctrl.Result{}, err - } - logger.Debug("BackupJob failed", "message", message) - return ctrl.Result{}, nil -} - func (r *BackupJobReconciler) createVeleroBackup(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, strategy *strategyv1alpha1.Velero) error { logger := getLogger(ctx) logger.Debug("createVeleroBackup called", "strategy", strategy.Name) @@ -579,8 +557,8 @@ func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJo // Extract driver metadata (e.g., Velero backup name) driverMetadata := map[string]string{ - "velero.io/backup-name": veleroBackup.Name, - "velero.io/backup-namespace": veleroBackup.Namespace, + veleroBackupNameMetadataKey: veleroBackup.Name, + veleroBackupNamespaceMetadataKey: veleroBackup.Namespace, } backup := &backupsv1alpha1.Backup{ @@ -625,3 +603,169 @@ func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJo logger.Debug("created Backup resource", "name", backup.Name) return backup, nil } + +// reconcileVeleroRestore handles restore operations for Velero strategy. +func (r *RestoreJobReconciler) reconcileVeleroRestore(ctx context.Context, restoreJob *backupsv1alpha1.RestoreJob, backup *backupsv1alpha1.Backup) (ctrl.Result, error) { + logger := getLogger(ctx) + logger.Debug("reconciling Velero strategy restore", "restorejob", restoreJob.Name, "backup", backup.Name) + + // Step 1: On first reconcile, set startedAt and phase = Running + if restoreJob.Status.StartedAt == nil { + logger.Debug("setting RestoreJob StartedAt and phase to Running") + now := metav1.Now() + restoreJob.Status.StartedAt = &now + restoreJob.Status.Phase = backupsv1alpha1.RestoreJobPhaseRunning + if err := r.Status().Update(ctx, restoreJob); err != nil { + logger.Error(err, "failed to update RestoreJob status") + return ctrl.Result{}, err + } + return ctrl.Result{RequeueAfter: defaultRestoreRequeueAfter}, nil + } + + // Step 2: Resolve inputs - Read Strategy, Storage, target Application + logger.Debug("fetching Velero strategy", "strategyName", backup.Spec.StrategyRef.Name) + veleroStrategy := &strategyv1alpha1.Velero{} + if err := r.Get(ctx, client.ObjectKey{Name: backup.Spec.StrategyRef.Name}, veleroStrategy); err != nil { + if errors.IsNotFound(err) { + logger.Error(err, "Velero strategy not found", "strategyName", backup.Spec.StrategyRef.Name) + return r.markRestoreJobFailed(ctx, restoreJob, fmt.Sprintf("Velero strategy not found: %s", backup.Spec.StrategyRef.Name)) + } + logger.Error(err, "failed to get Velero strategy") + return ctrl.Result{}, err + } + logger.Debug("fetched Velero strategy", "strategyName", veleroStrategy.Name) + + // Get Velero backup name from Backup's driverMetadata + veleroBackupName, ok := backup.Spec.DriverMetadata[veleroBackupNameMetadataKey] + if !ok { + return r.markRestoreJobFailed(ctx, restoreJob, fmt.Sprintf("Backup missing Velero backup name in driverMetadata (key: %s)", veleroBackupNameMetadataKey)) + } + + // Step 3: Execute restore logic + // Check if we already created a Velero Restore + logger.Debug("checking for existing Velero Restore", "namespace", veleroNamespace) + veleroRestoreList := &velerov1.RestoreList{} + opts := []client.ListOption{ + client.InNamespace(veleroNamespace), + client.MatchingLabels{ + backupsv1alpha1.OwningJobNameLabel: restoreJob.Name, + backupsv1alpha1.OwningJobNamespaceLabel: restoreJob.Namespace, + }, + } + + if err := r.List(ctx, veleroRestoreList, opts...); err != nil { + logger.Error(err, "failed to get Velero Restore") + return ctrl.Result{}, err + } + + if len(veleroRestoreList.Items) == 0 { + // Create Velero Restore + logger.Debug("Velero Restore not found, creating new one") + if err := r.createVeleroRestore(ctx, restoreJob, backup, veleroStrategy, veleroBackupName); err != nil { + logger.Error(err, "failed to create Velero Restore") + return r.markRestoreJobFailed(ctx, restoreJob, fmt.Sprintf("failed to create Velero Restore: %v", err)) + } + logger.Debug("created Velero Restore, requeuing") + // Requeue to check status + return ctrl.Result{RequeueAfter: defaultRestoreRequeueAfter}, nil + } + + if len(veleroRestoreList.Items) > 1 { + logger.Error(fmt.Errorf("too many Velero restores for RestoreJob"), "found more than one Velero Restore referencing a single RestoreJob as owner") + return r.markRestoreJobFailed(ctx, restoreJob, "found multiple Velero Restores for this RestoreJob") + } + + veleroRestore := veleroRestoreList.Items[0].DeepCopy() + logger.Debug("found existing Velero Restore", "phase", veleroRestore.Status.Phase) + + // Check Velero Restore status + phase := string(veleroRestore.Status.Phase) + if phase == "" { + // Still in progress, requeue + return ctrl.Result{RequeueAfter: defaultActiveRestorePollingInterval}, nil + } + + // Step 4: On success + if phase == "Completed" { + now := metav1.Now() + restoreJob.Status.CompletedAt = &now + restoreJob.Status.Phase = backupsv1alpha1.RestoreJobPhaseSucceeded + if err := r.Status().Update(ctx, restoreJob); err != nil { + logger.Error(err, "failed to update RestoreJob status") + return ctrl.Result{}, err + } + logger.Debug("RestoreJob succeeded") + return ctrl.Result{}, nil + } + + // Step 5: On failure + if phase == "Failed" || phase == "PartiallyFailed" { + message := fmt.Sprintf("Velero Restore failed with phase: %s", phase) + if veleroRestore.Status.FailureReason != "" { + message = fmt.Sprintf("%s: %s", message, veleroRestore.Status.FailureReason) + } + return r.markRestoreJobFailed(ctx, restoreJob, message) + } + + // Still in progress (InProgress, New, etc.) + return ctrl.Result{RequeueAfter: defaultRestoreRequeueAfter}, nil +} + +// createVeleroRestore creates a Velero Restore resource. +func (r *RestoreJobReconciler) createVeleroRestore(ctx context.Context, restoreJob *backupsv1alpha1.RestoreJob, backup *backupsv1alpha1.Backup, strategy *strategyv1alpha1.Velero, veleroBackupName string) error { + logger := getLogger(ctx) + logger.Debug("createVeleroRestore called", "strategy", strategy.Name, "veleroBackupName", veleroBackupName) + + // Determine target application reference + targetAppRef := r.getTargetApplicationRef(restoreJob, backup) + + // Get the target application object for templating + mapping, err := r.RESTMapping(schema.GroupKind{Group: *targetAppRef.APIGroup, Kind: targetAppRef.Kind}) + if err != nil { + return fmt.Errorf("failed to get REST mapping for target application: %w", err) + } + ns := restoreJob.Namespace + if mapping.Scope.Name() != meta.RESTScopeNameNamespace { + ns = "" + } + app, err := r.Resource(mapping.Resource).Namespace(ns).Get(ctx, targetAppRef.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get target application: %w", err) + } + + // Template the restore spec from the strategy + veleroRestoreSpec, err := template.Template(&strategy.Spec.Template.RestoreSpec, app.Object) + if err != nil { + return fmt.Errorf("failed to template Velero Restore spec: %w", err) + } + + // Set the backupName in the spec (required by Velero) + veleroRestoreSpec.BackupName = veleroBackupName + + veleroRestore := &velerov1.Restore{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("%s.%s-", restoreJob.Namespace, restoreJob.Name), + Namespace: veleroNamespace, + Labels: map[string]string{ + backupsv1alpha1.OwningJobNameLabel: restoreJob.Name, + backupsv1alpha1.OwningJobNamespaceLabel: restoreJob.Namespace, + }, + }, + Spec: *veleroRestoreSpec, + } + name := veleroRestore.GenerateName + if err := r.Create(ctx, veleroRestore); err != nil { + if veleroRestore.Name != "" { + name = veleroRestore.Name + } + logger.Error(err, "failed to create Velero Restore", "name", veleroRestore.Name) + r.Recorder.Event(restoreJob, corev1.EventTypeWarning, "VeleroRestoreCreationFailed", + fmt.Sprintf("Failed to create Velero Restore %s/%s: %v", veleroNamespace, name, err)) + return err + } + + logger.Debug("created Velero Restore", "name", veleroRestore.Name, "namespace", veleroRestore.Namespace) + r.Recorder.Event(restoreJob, corev1.EventTypeNormal, "VeleroRestoreCreated", + fmt.Sprintf("Created Velero Restore %s/%s", veleroNamespace, name)) + return nil +} From e63ed41feeefe7e10e217af7a59973238b3d3234 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Thu, 8 Jan 2026 19:42:27 +0300 Subject: [PATCH 45/45] [backups] Demo, DO NOT MERGE Signed-off-by: Timofei Larkin --- .../v1alpha1/zz_generated.deepcopy.go | 1 + go.mod | 94 +- go.sum | 212 ++-- .../backups.cozystack.io_restorejobs.yaml | 8 +- ...trategy.backups.cozystack.io_veleroes.yaml | 919 ++++++++++++++++++ pkg/cmd/server/start.go | 31 +- pkg/cmd/server/start_test.go | 12 +- pkg/generated/openapi/zz_generated.openapi.go | 49 +- 8 files changed, 1154 insertions(+), 172 deletions(-) diff --git a/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go b/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go index c1989ac9..ae5d178c 100644 --- a/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go +++ b/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go @@ -223,6 +223,7 @@ func (in *VeleroStatus) DeepCopy() *VeleroStatus { func (in *VeleroTemplate) DeepCopyInto(out *VeleroTemplate) { *out = *in in.Spec.DeepCopyInto(&out.Spec) + in.RestoreSpec.DeepCopyInto(&out.RestoreSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroTemplate. diff --git a/go.mod b/go.mod index ee771e5d..0ddb053d 100644 --- a/go.mod +++ b/go.mod @@ -2,38 +2,41 @@ module github.com/cozystack/cozystack -go 1.23.0 +go 1.24.0 + +toolchain go1.24.5 require ( github.com/fluxcd/helm-controller/api v1.1.0 - github.com/go-logr/logr v1.4.2 + github.com/go-logr/logr v1.4.3 github.com/go-logr/zapr v1.3.0 github.com/google/gofuzz v1.2.0 - github.com/onsi/ginkgo/v2 v2.19.0 - github.com/onsi/gomega v1.33.1 - github.com/prometheus/client_golang v1.19.1 + github.com/onsi/ginkgo/v2 v2.22.0 + github.com/onsi/gomega v1.36.1 + github.com/prometheus/client_golang v1.22.0 github.com/robfig/cron/v3 v3.0.1 github.com/spf13/cobra v1.9.1 + github.com/stretchr/testify v1.10.0 github.com/vmware-tanzu/velero v1.17.1 go.uber.org/zap v1.27.0 gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.31.2 - k8s.io/apiextensions-apiserver v0.31.2 - k8s.io/apimachinery v0.31.2 - k8s.io/apiserver v0.31.2 - k8s.io/client-go v0.31.2 - k8s.io/component-base v0.31.2 + k8s.io/api v0.34.1 + k8s.io/apiextensions-apiserver v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/apiserver v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/component-base v0.34.1 k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2 - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 - sigs.k8s.io/controller-runtime v0.19.0 - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 + sigs.k8s.io/controller-runtime v0.22.4 + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 ) require ( + cel.dev/expr v0.24.0 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -41,38 +44,37 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/kustomize v1.6.1 // indirect github.com/fluxcd/pkg/apis/meta v1.6.1 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/cel-go v0.21.0 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/cel-go v0.26.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/moby/spdystream v0.4.0 // indirect + github.com/moby/spdystream v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pkg/errors v0.9.1 // indirect @@ -80,7 +82,7 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.etcd.io/etcd/api/v3 v3.6.4 // indirect @@ -97,16 +99,18 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.31.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.40.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.45.0 // indirect + golang.org/x/net v0.42.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/term v0.33.0 // indirect + golang.org/x/text v0.27.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.37.0 // indirect + golang.org/x/tools v0.34.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect @@ -116,11 +120,13 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/kms v0.31.2 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + k8s.io/kms v0.34.1 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) // See: issues.k8s.io/135537 -replace k8s.io/apimachinery => github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614 +//replace k8s.io/apimachinery => github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614 diff --git a/go.sum b/go.sum index bf792037..d77d26e0 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -18,9 +18,7 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614 h1:jH9elECUvhiIs3IMv3oS5k1JgCLVsSK6oU4dmq5gyW8= -github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -28,12 +26,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= -github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/helm-controller/api v1.1.0 h1:NS5Wm3U6Kv4w7Cw2sDOV++vf2ecGfFV00x1+2Y3QcOY= @@ -42,13 +40,13 @@ github.com/fluxcd/pkg/apis/kustomize v1.6.1 h1:22FJc69Mq4i8aCxnKPlddHhSMyI4UPkQk github.com/fluxcd/pkg/apis/kustomize v1.6.1/go.mod h1:5dvQ4IZwz0hMGmuj8tTWGtarsuxW0rWsxJOwC6i+0V8= github.com/fluxcd/pkg/apis/meta v1.6.1 h1:maLhcRJ3P/70ArLCY/LF/YovkxXbX+6sTWZwZQBeNq0= github.com/fluxcd/pkg/apis/meta v1.6.1/go.mod h1:YndB/gxgGZmKfqpAfFxyCDNFJFP0ikpeJzs66jwq280= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -66,50 +64,48 @@ github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= -github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -117,23 +113,26 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= -github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= -github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -149,36 +148,38 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/vmware-tanzu/velero v1.17.1 h1:ldKeiTuUwkThOw7zrUucNA1NwnLG66zl13YetWAoE0I= github.com/vmware-tanzu/velero v1.17.1/go.mod h1:3KTxuUN6Un38JzmYAX+8U6j2k6EexGoNNxa8jrJML8U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= @@ -223,11 +224,15 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -236,34 +241,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= -golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -287,37 +292,44 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= -k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= -k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= -k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= -k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= -k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= -k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= -k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= -k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= -k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.31.2 h1:pyx7l2qVOkClzFMIWMVF/FxsSkgd+OIGH7DecpbscJI= -k8s.io/kms v0.31.2/go.mod h1:OZKwl1fan3n3N5FFxnW5C4V3ygrah/3YXeJWS3O6+94= -k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2 h1:GKE9U8BH16uynoxQii0auTjmmmuZ3O0LFMN6S0lPPhI= -k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= -sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +k8s.io/kms v0.34.1 h1:iCFOvewDPzWM9fMTfyIPO+4MeuZ0tcZbugxLNSHFG4w= +k8s.io/kms v0.34.1/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= +sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml index 607a69b2..400737fc 100644 --- a/packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_restorejobs.yaml @@ -14,7 +14,11 @@ spec: singular: restorejob scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Phase + type: string + name: v1alpha1 schema: openAPIV3Schema: description: RestoreJob represents a single execution of a restore from a @@ -166,3 +170,5 @@ spec: type: object served: true storage: true + subresources: + status: {} diff --git a/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_veleroes.yaml b/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_veleroes.yaml index 4433c7cb..76408ce1 100644 --- a/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_veleroes.yaml +++ b/packages/system/backupstrategy-controller/definitions/strategy.backups.cozystack.io_veleroes.yaml @@ -39,6 +39,925 @@ spec: spec: description: VeleroSpec specifies the desired strategy for backing up with Velero. + properties: + template: + description: |- + VeleroTemplate describes the data a backup.velero.io should have when + templated from a Velero backup strategy. + properties: + restoreSpec: + description: RestoreSpec defines the specification for a Velero + restore. + properties: + backupName: + description: |- + BackupName is the unique name of the Velero backup to restore + from. + type: string + excludedNamespaces: + description: |- + ExcludedNamespaces contains a list of namespaces that are not + included in the restore. + items: + type: string + nullable: true + type: array + excludedResources: + description: |- + ExcludedResources is a slice of resource names that are not + included in the restore. + items: + type: string + nullable: true + type: array + existingResourcePolicy: + description: ExistingResourcePolicy specifies the restore + behavior for the Kubernetes resource to be restored + nullable: true + type: string + hooks: + description: Hooks represent custom behaviors that should + be executed during or post restore. + properties: + resources: + items: + description: |- + RestoreResourceHookSpec defines one or more RestoreResrouceHooks that should be executed based on + the rules defined for namespaces, resources, and label selector. + properties: + excludedNamespaces: + description: ExcludedNamespaces specifies the namespaces + to which this hook spec does not apply. + items: + type: string + nullable: true + type: array + excludedResources: + description: ExcludedResources specifies the resources + to which this hook spec does not apply. + items: + type: string + nullable: true + type: array + includedNamespaces: + description: |- + IncludedNamespaces specifies the namespaces to which this hook spec applies. If empty, it applies + to all namespaces. + items: + type: string + nullable: true + type: array + includedResources: + description: |- + IncludedResources specifies the resources to which this hook spec applies. If empty, it applies + to all resources. + items: + type: string + nullable: true + type: array + labelSelector: + description: LabelSelector, if specified, filters + the resources to which this hook spec applies. + nullable: true + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: Name is the name of this hook. + type: string + postHooks: + description: PostHooks is a list of RestoreResourceHooks + to execute during and after restoring a resource. + items: + description: RestoreResourceHook defines a restore + hook for a resource. + properties: + exec: + description: Exec defines an exec restore + hook. + properties: + command: + description: Command is the command and + arguments to execute from within a container + after a pod has been restored. + items: + type: string + minItems: 1 + type: array + container: + description: |- + Container is the container in the pod where the command should be executed. If not specified, + the pod's first container is used. + type: string + execTimeout: + description: |- + ExecTimeout defines the maximum amount of time Velero should wait for the hook to complete before + considering the execution a failure. + type: string + onError: + description: OnError specifies how Velero + should behave if it encounters an error + executing this hook. + enum: + - Continue + - Fail + type: string + waitForReady: + description: WaitForReady ensures command + will be launched when container is Ready + instead of Running. + nullable: true + type: boolean + waitTimeout: + description: |- + WaitTimeout defines the maximum amount of time Velero should wait for the container to be Ready + before attempting to run the command. + type: string + required: + - command + type: object + init: + description: Init defines an init restore + hook. + properties: + initContainers: + description: InitContainers is list of + init containers to be added to a pod + during its restore. + items: + type: object + x-kubernetes-preserve-unknown-fields: true + type: array + x-kubernetes-preserve-unknown-fields: true + timeout: + description: Timeout defines the maximum + amount of time Velero should wait for + the initContainers to complete. + type: string + type: object + type: object + type: array + required: + - name + type: object + type: array + type: object + includeClusterResources: + description: |- + IncludeClusterResources specifies whether cluster-scoped resources + should be included for consideration in the restore. If null, defaults + to true. + nullable: true + type: boolean + includedNamespaces: + description: |- + IncludedNamespaces is a slice of namespace names to include objects + from. If empty, all namespaces are included. + items: + type: string + nullable: true + type: array + includedResources: + description: |- + IncludedResources is a slice of resource names to include + in the restore. If empty, all resources in the backup are included. + items: + type: string + nullable: true + type: array + itemOperationTimeout: + description: |- + ItemOperationTimeout specifies the time used to wait for RestoreItemAction operations + The default value is 4 hour. + type: string + labelSelector: + description: |- + LabelSelector is a metav1.LabelSelector to filter with + when restoring individual objects from the backup. If empty + or nil, all objects are included. Optional. + nullable: true + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceMapping: + additionalProperties: + type: string + description: |- + NamespaceMapping is a map of source namespace names + to target namespace names to restore into. Any source + namespaces not included in the map will be restored into + namespaces of the same name. + type: object + orLabelSelectors: + description: |- + OrLabelSelectors is list of metav1.LabelSelector to filter with + when restoring individual objects from the backup. If multiple provided + they will be joined by the OR operator. LabelSelector as well as + OrLabelSelectors cannot co-exist in restore request, only one of them + can be used + items: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nullable: true + type: array + preserveNodePorts: + description: PreserveNodePorts specifies whether to restore + old nodePorts from backup. + nullable: true + type: boolean + resourceModifier: + description: ResourceModifier specifies the reference to JSON + resource patches that should be applied to resources before + restoration. + nullable: true + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + restorePVs: + description: |- + RestorePVs specifies whether to restore all included + PVs from snapshot + nullable: true + type: boolean + restoreStatus: + description: |- + RestoreStatus specifies which resources we should restore the status + field. If nil, no objects are included. Optional. + nullable: true + properties: + excludedResources: + description: ExcludedResources specifies the resources + to which will not restore the status. + items: + type: string + nullable: true + type: array + includedResources: + description: |- + IncludedResources specifies the resources to which will restore the status. + If empty, it applies to all resources. + items: + type: string + nullable: true + type: array + type: object + scheduleName: + description: |- + ScheduleName is the unique name of the Velero schedule to restore + from. If specified, and BackupName is empty, Velero will restore + from the most recent successful backup created from this schedule. + type: string + uploaderConfig: + description: UploaderConfig specifies the configuration for + the restore. + nullable: true + properties: + parallelFilesDownload: + description: ParallelFilesDownload is the concurrency + number setting for restore. + type: integer + writeSparseFiles: + description: WriteSparseFiles is a flag to indicate whether + write files sparsely or not. + nullable: true + type: boolean + type: object + type: object + spec: + description: BackupSpec defines the specification for a Velero + backup. + properties: + csiSnapshotTimeout: + description: |- + CSISnapshotTimeout specifies the time used to wait for CSI VolumeSnapshot status turns to + ReadyToUse during creation, before returning error as timeout. + The default value is 10 minute. + type: string + datamover: + description: |- + DataMover specifies the data mover to be used by the backup. + If DataMover is "" or "velero", the built-in data mover will be used. + type: string + defaultVolumesToFsBackup: + description: |- + DefaultVolumesToFsBackup specifies whether pod volume file system backup should be used + for all volumes by default. + nullable: true + type: boolean + defaultVolumesToRestic: + description: |- + DefaultVolumesToRestic specifies whether restic should be used to take a + backup of all pod volumes by default. + + Deprecated: this field is no longer used and will be removed entirely in future. Use DefaultVolumesToFsBackup instead. + nullable: true + type: boolean + excludedClusterScopedResources: + description: |- + ExcludedClusterScopedResources is a slice of cluster-scoped + resource type names to exclude from the backup. + If set to "*", all cluster-scoped resource types are excluded. + The default value is empty. + items: + type: string + nullable: true + type: array + excludedNamespaceScopedResources: + description: |- + ExcludedNamespaceScopedResources is a slice of namespace-scoped + resource type names to exclude from the backup. + If set to "*", all namespace-scoped resource types are excluded. + The default value is empty. + items: + type: string + nullable: true + type: array + excludedNamespaces: + description: |- + ExcludedNamespaces contains a list of namespaces that are not + included in the backup. + items: + type: string + nullable: true + type: array + excludedResources: + description: |- + ExcludedResources is a slice of resource names that are not + included in the backup. + items: + type: string + nullable: true + type: array + hooks: + description: Hooks represent custom behaviors that should + be executed at different phases of the backup. + properties: + resources: + description: Resources are hooks that should be executed + when backing up individual instances of a resource. + items: + description: |- + BackupResourceHookSpec defines one or more BackupResourceHooks that should be executed based on + the rules defined for namespaces, resources, and label selector. + properties: + excludedNamespaces: + description: ExcludedNamespaces specifies the namespaces + to which this hook spec does not apply. + items: + type: string + nullable: true + type: array + excludedResources: + description: ExcludedResources specifies the resources + to which this hook spec does not apply. + items: + type: string + nullable: true + type: array + includedNamespaces: + description: |- + IncludedNamespaces specifies the namespaces to which this hook spec applies. If empty, it applies + to all namespaces. + items: + type: string + nullable: true + type: array + includedResources: + description: |- + IncludedResources specifies the resources to which this hook spec applies. If empty, it applies + to all resources. + items: + type: string + nullable: true + type: array + labelSelector: + description: LabelSelector, if specified, filters + the resources to which this hook spec applies. + nullable: true + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: Name is the name of this hook. + type: string + post: + description: |- + PostHooks is a list of BackupResourceHooks to execute after storing the item in the backup. + These are executed after all "additional items" from item actions are processed. + items: + description: BackupResourceHook defines a hook + for a resource. + properties: + exec: + description: Exec defines an exec hook. + properties: + command: + description: Command is the command and + arguments to execute. + items: + type: string + minItems: 1 + type: array + container: + description: |- + Container is the container in the pod where the command should be executed. If not specified, + the pod's first container is used. + type: string + onError: + description: OnError specifies how Velero + should behave if it encounters an error + executing this hook. + enum: + - Continue + - Fail + type: string + timeout: + description: |- + Timeout defines the maximum amount of time Velero should wait for the hook to complete before + considering the execution a failure. + type: string + required: + - command + type: object + required: + - exec + type: object + type: array + pre: + description: |- + PreHooks is a list of BackupResourceHooks to execute prior to storing the item in the backup. + These are executed before any "additional items" from item actions are processed. + items: + description: BackupResourceHook defines a hook + for a resource. + properties: + exec: + description: Exec defines an exec hook. + properties: + command: + description: Command is the command and + arguments to execute. + items: + type: string + minItems: 1 + type: array + container: + description: |- + Container is the container in the pod where the command should be executed. If not specified, + the pod's first container is used. + type: string + onError: + description: OnError specifies how Velero + should behave if it encounters an error + executing this hook. + enum: + - Continue + - Fail + type: string + timeout: + description: |- + Timeout defines the maximum amount of time Velero should wait for the hook to complete before + considering the execution a failure. + type: string + required: + - command + type: object + required: + - exec + type: object + type: array + required: + - name + type: object + nullable: true + type: array + type: object + includeClusterResources: + description: |- + IncludeClusterResources specifies whether cluster-scoped resources + should be included for consideration in the backup. + nullable: true + type: boolean + includedClusterScopedResources: + description: |- + IncludedClusterScopedResources is a slice of cluster-scoped + resource type names to include in the backup. + If set to "*", all cluster-scoped resource types are included. + The default value is empty, which means only related + cluster-scoped resources are included. + items: + type: string + nullable: true + type: array + includedNamespaceScopedResources: + description: |- + IncludedNamespaceScopedResources is a slice of namespace-scoped + resource type names to include in the backup. + The default value is "*". + items: + type: string + nullable: true + type: array + includedNamespaces: + description: |- + IncludedNamespaces is a slice of namespace names to include objects + from. If empty, all namespaces are included. + items: + type: string + nullable: true + type: array + includedResources: + description: |- + IncludedResources is a slice of resource names to include + in the backup. If empty, all resources are included. + items: + type: string + nullable: true + type: array + itemOperationTimeout: + description: |- + ItemOperationTimeout specifies the time used to wait for asynchronous BackupItemAction operations + The default value is 4 hour. + type: string + labelSelector: + description: |- + LabelSelector is a metav1.LabelSelector to filter with + when adding individual objects to the backup. If empty + or nil, all objects are included. Optional. + nullable: true + 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + metadata: + properties: + labels: + additionalProperties: + type: string + type: object + type: object + orLabelSelectors: + description: |- + OrLabelSelectors is list of metav1.LabelSelector to filter with + when adding individual objects to the backup. If multiple provided + they will be joined by the OR operator. LabelSelector as well as + OrLabelSelectors cannot co-exist in backup request, only one of them + can be used. + items: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label 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 + relates 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. + Valid 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, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge 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 + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nullable: true + type: array + orderedResources: + additionalProperties: + type: string + description: |- + OrderedResources specifies the backup order of resources of specific Kind. + The map key is the resource name and value is a list of object names separated by commas. + Each resource name has format "namespace/objectname". For cluster resources, simply use "objectname". + nullable: true + type: object + resourcePolicy: + description: ResourcePolicy specifies the referenced resource + policies that backup should follow + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + snapshotMoveData: + description: SnapshotMoveData specifies whether snapshot data + should be moved + nullable: true + type: boolean + snapshotVolumes: + description: |- + SnapshotVolumes specifies whether to take snapshots + of any PV's referenced in the set of objects included + in the Backup. + nullable: true + type: boolean + storageLocation: + description: StorageLocation is a string containing the name + of a BackupStorageLocation where the backup should be stored. + type: string + ttl: + description: |- + TTL is a time.Duration-parseable string describing how long + the Backup should be retained for. + type: string + uploaderConfig: + description: UploaderConfig specifies the configuration for + the uploader. + nullable: true + properties: + parallelFilesUpload: + description: ParallelFilesUpload is the number of files + parallel uploads to perform when using the uploader. + type: integer + type: object + volumeGroupSnapshotLabelKey: + description: VolumeGroupSnapshotLabelKey specifies the label + key to group PVCs under a VGS. + type: string + volumeSnapshotLocations: + description: VolumeSnapshotLocations is a list containing + names of VolumeSnapshotLocations associated with this backup. + items: + type: string + type: array + type: object + required: + - restoreSpec + - spec + type: object + required: + - template type: object status: properties: diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index 3427e61d..f686756a 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -40,8 +40,9 @@ import ( "k8s.io/apiserver/pkg/endpoints/openapi" genericapiserver "k8s.io/apiserver/pkg/server" genericoptions "k8s.io/apiserver/pkg/server/options" + "k8s.io/apiserver/pkg/util/compatibility" utilfeature "k8s.io/apiserver/pkg/util/feature" - utilversionpkg "k8s.io/apiserver/pkg/util/version" + basecompatibility "k8s.io/component-base/compatibility" "k8s.io/component-base/featuregate" baseversion "k8s.io/component-base/version" netutils "k8s.io/utils/net" @@ -87,7 +88,7 @@ func NewCommandStartCozyServer(ctx context.Context, defaults *CozyServerOptions) Short: "Launch an Cozystack API server", Long: "Launch an Cozystack API server", PersistentPreRunE: func(*cobra.Command, []string) error { - return utilversionpkg.DefaultComponentGlobalsRegistry.Set() + return compatibility.DefaultComponentGlobalsRegistry.Set() }, RunE: func(c *cobra.Command, args []string) error { if err := o.Complete(); err != nil { @@ -114,8 +115,8 @@ func NewCommandStartCozyServer(ctx context.Context, defaults *CozyServerOptions) defaultCozyVersion := "1.1" // Register the "Cozy" component in the global component registry, // associating it with its effective version and feature gate configuration. - _, appsFeatureGate := utilversionpkg.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister( - apiserver.CozyComponentName, utilversionpkg.NewEffectiveVersion(defaultCozyVersion), + _, appsFeatureGate := compatibility.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister( + apiserver.CozyComponentName, basecompatibility.NewEffectiveVersionFromString(defaultCozyVersion, "", ""), featuregate.NewVersionedFeatureGate(version.MustParse(defaultCozyVersion)), ) @@ -126,19 +127,19 @@ func NewCommandStartCozyServer(ctx context.Context, defaults *CozyServerOptions) })) // Register the standard kube component if it is not already registered in the global registry. - _, _ = utilversionpkg.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister( - utilversionpkg.DefaultKubeComponent, - utilversionpkg.NewEffectiveVersion(baseversion.DefaultKubeBinaryVersion), + _, _ = compatibility.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister( + basecompatibility.DefaultKubeComponent, + basecompatibility.NewEffectiveVersionFromString(baseversion.DefaultKubeBinaryVersion, "", ""), utilfeature.DefaultMutableFeatureGate, ) // Set the version emulation mapping from the "Cozy" component to the kube component. - utilruntime.Must(utilversionpkg.DefaultComponentGlobalsRegistry.SetEmulationVersionMapping( - apiserver.CozyComponentName, utilversionpkg.DefaultKubeComponent, CozyVersionToKubeVersion, + utilruntime.Must(compatibility.DefaultComponentGlobalsRegistry.SetEmulationVersionMapping( + apiserver.CozyComponentName, basecompatibility.DefaultKubeComponent, CozyVersionToKubeVersion, )) // Add flags from the global component registry. - utilversionpkg.DefaultComponentGlobalsRegistry.AddFlags(flags) + compatibility.DefaultComponentGlobalsRegistry.AddFlags(flags) return cmd } @@ -225,7 +226,7 @@ func (o *CozyServerOptions) Complete() error { func (o CozyServerOptions) Validate(args []string) error { var allErrors []error allErrors = append(allErrors, o.RecommendedOptions.Validate()...) - allErrors = append(allErrors, utilversionpkg.DefaultComponentGlobalsRegistry.Validate()...) + allErrors = append(allErrors, compatibility.DefaultComponentGlobalsRegistry.Validate()...) return utilerrors.NewAggregate(allErrors) } @@ -281,10 +282,10 @@ func (o *CozyServerOptions) Config() (*apiserver.Config, error) { serverConfig.OpenAPIV3Config.PostProcessSpec = buildPostProcessV3(kindSchemas) - serverConfig.FeatureGate = utilversionpkg.DefaultComponentGlobalsRegistry.FeatureGateFor( - utilversionpkg.DefaultKubeComponent, + serverConfig.FeatureGate = compatibility.DefaultComponentGlobalsRegistry.FeatureGateFor( + basecompatibility.DefaultKubeComponent, ) - serverConfig.EffectiveVersion = utilversionpkg.DefaultComponentGlobalsRegistry.EffectiveVersionFor( + serverConfig.EffectiveVersion = compatibility.DefaultComponentGlobalsRegistry.EffectiveVersionFor( apiserver.CozyComponentName, ) @@ -324,7 +325,7 @@ func CozyVersionToKubeVersion(ver *version.Version) *version.Version { if ver.Major() != 1 { return nil } - kubeVer := utilversionpkg.DefaultKubeEffectiveVersion().BinaryVersion() + kubeVer := version.MustParse(baseversion.DefaultKubeBinaryVersion) // "1.2" corresponds to kubeVer offset := int(ver.Minor()) - 2 mappedVer := kubeVer.OffsetMinor(offset) diff --git a/pkg/cmd/server/start_test.go b/pkg/cmd/server/start_test.go index da96ed31..ccbb7d4f 100644 --- a/pkg/cmd/server/start_test.go +++ b/pkg/cmd/server/start_test.go @@ -20,13 +20,13 @@ import ( "testing" "k8s.io/apimachinery/pkg/util/version" - utilversion "k8s.io/apiserver/pkg/util/version" + baseversion "k8s.io/component-base/version" "github.com/stretchr/testify/assert" ) func TestCozyEmulationVersionToKubeEmulationVersion(t *testing.T) { - defaultKubeEffectiveVersion := utilversion.DefaultKubeEffectiveVersion() + kubeVer := version.MustParse(baseversion.DefaultKubeBinaryVersion) testCases := []struct { desc string @@ -36,22 +36,22 @@ func TestCozyEmulationVersionToKubeEmulationVersion(t *testing.T) { { desc: "same version as than kube binary", appsEmulationVer: version.MajorMinor(1, 2), - expectedKubeEmulationVer: defaultKubeEffectiveVersion.BinaryVersion(), + expectedKubeEmulationVer: kubeVer, }, { desc: "1 version lower than kube binary", appsEmulationVer: version.MajorMinor(1, 1), - expectedKubeEmulationVer: defaultKubeEffectiveVersion.BinaryVersion().OffsetMinor(-1), + expectedKubeEmulationVer: kubeVer.OffsetMinor(-1), }, { desc: "2 versions lower than kube binary", appsEmulationVer: version.MajorMinor(1, 0), - expectedKubeEmulationVer: defaultKubeEffectiveVersion.BinaryVersion().OffsetMinor(-2), + expectedKubeEmulationVer: kubeVer.OffsetMinor(-2), }, { desc: "capped at kube binary", appsEmulationVer: version.MajorMinor(1, 3), - expectedKubeEmulationVer: defaultKubeEffectiveVersion.BinaryVersion(), + expectedKubeEmulationVer: kubeVer, }, { desc: "no mapping", diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 4202bbac..ba5ab71b 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -2714,6 +2714,13 @@ func schema_pkg_apis_meta_v1_DeleteOptions(ref common.ReferenceCallback) common. }, }, }, + "ignoreStoreReadErrorWithClusterBreakingPotential": { + SchemaProps: spec.SchemaProps{ + Description: "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + Type: []string{"boolean"}, + Format: "", + }, + }, }, }, }, @@ -4601,16 +4608,46 @@ func schema_k8sio_apimachinery_pkg_version_Info(ref common.ReferenceCallback) co Properties: map[string]spec.Schema{ "major": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "Major is the major version of the binary version", + Default: "", + Type: []string{"string"}, + Format: "", }, }, "minor": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "Minor is the minor version of the binary version", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "emulationMajor": { + SchemaProps: spec.SchemaProps{ + Description: "EmulationMajor is the major version of the emulation version", + Type: []string{"string"}, + Format: "", + }, + }, + "emulationMinor": { + SchemaProps: spec.SchemaProps{ + Description: "EmulationMinor is the minor version of the emulation version", + Type: []string{"string"}, + Format: "", + }, + }, + "minCompatibilityMajor": { + SchemaProps: spec.SchemaProps{ + Description: "MinCompatibilityMajor is the major version of the minimum compatibility version", + Type: []string{"string"}, + Format: "", + }, + }, + "minCompatibilityMinor": { + SchemaProps: spec.SchemaProps{ + Description: "MinCompatibilityMinor is the minor version of the minimum compatibility version", + Type: []string{"string"}, + Format: "", }, }, "gitVersion": {